@Emil Sorry. Didn't write that it's when I'm in my car on my way home.😉
-
-
Hi,
Here's a module I programmed for controlling volume and basic Spotify playback functions with Flic Twist, in case someone else has any use for it. To use it, you'll have to register for a Spotify developer account and create a new app from their dashboard, in order to get access tokens etc.
@flichub @Emil: Is it possible to somehow get this added to the existing public Spotify "provider"? Seems a bit tedious/difficult for users without any programming experience to go down this particular rabbit hole.
// main.js const flicapp = require('flicapp'); const http = require('http'); const datastore = require('datastore'); datastore.get('clientID', (err, key) => { const clientID = key // store this first: (datastore.put('clientID', 'YOUR CLIENT ID') datastore.get('clientSecret', (err, key) => { const clientSecret = key // store this first: (datastore.put('clientSecret', 'YOUR CLIENT SECRET') function percent(volume, decimalPlaces = 0) { const percentage = (volume * 100).toFixed(decimalPlaces); return `${percentage}`; } function spotifyStatus() { datastore.get('accessToken', (err, key) => { const accessToken = key if (key !== null) { const options = { url: 'https://api.spotify.com/v1/me/player', method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + accessToken } }; const req = http.makeRequest(options, (error, result) => { if (result.statusCode == 401) { refreshToken(); } if (result.statusCode == 204) { var status = "No active sessions." console.log(status); } if (result.statusCode == 200) { var jresp = JSON.parse(result.content); var status = jresp["is_playing"]; if (status === true) { spotifyPause(); } if (status === false) { spotifyPlay(); } } }) } }) }; function spotifyPlay() { datastore.get('accessToken', (err, key) => { const accessToken = key if (key !== null) { const options = { url: 'https://api.spotify.com/v1/me/player/play', method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + accessToken } }; const req = http.makeRequest(options, (error, result) => { if (result.statusCode == 401) { refreshToken(); } }) } }) }; function spotifyPause() { datastore.get('accessToken', (err, key) => { const accessToken = key if (key !== null) { const options = { url: 'https://api.spotify.com/v1/me/player/pause', method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + accessToken } }; const req = http.makeRequest(options, (error, result) => { if (result.statusCode == 401) { refreshToken(); } }) } }) }; function spotifyNext() { datastore.get('accessToken', (err, key) => { const accessToken = key if (key !== null) { const options = { url: 'https://api.spotify.com/v1/me/player/next', method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + accessToken } }; const req = http.makeRequest(options, (error, result) => { if (result.statusCode == 401) { refreshToken(); } }) } }) }; function spotifyVolume(volume) { datastore.get('accessToken', (err, key) => { const accessToken = key if (key !== null) { const options = { url: 'https://api.spotify.com/v1/me/player/volume?volume_percent=' + volume, method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + accessToken } }; const req = http.makeRequest(options, (error, result) => { if (result.statusCode == 401) { refreshToken(); } if (result.statusCode == 404) { console.log("No active sessions.") } }) } }) }; function refreshToken() { datastore.get('refreshToken', (err, key) => { const refreshToken = key if (key !== null) { const options = { url: 'https://accounts.spotify.com/api/token', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, content: "refresh_token=" + refreshToken + "&scope=user-read-playback-state%2C+user-modify-playback-state%2C+user-follow-read%2C+user-library-read%2C+streaming%2C+user-read-playback-position%2C+user-top-read%2C+user-read-currently-playing%2C+&client_id=" + clientID + "&client_secret=" + clientSecret + "&grant_type=refresh_token" }; const req = http.makeRequest(options, (error, result) => { if (!error && result.statusCode === 200) { var jresp = JSON.parse(result.content); datastore.put('accessToken', jresp["access_token"]) spotifyStatus(); } }) } }) }; flicapp.on("actionMessage", function(message) { if (message == 'playtoggle') { spotifyStatus(); } }); flicapp.on("actionMessage", function(message) { if (message == 'next') { spotifyNext(); } }); flicapp.on("virtualDeviceUpdate", function(metaData, values) { if (values.volume && metaData.virtualDeviceId == "spotify") { var volume = percent(values.volume) spotifyVolume(volume); flicapp.virtualDeviceUpdateState("Speaker", "spotify", { volume: values.volume }); } }); }); // clientID var }); // clientSecret varAs you probably can see, I'm not exactly a Javascript wizard, but it works 🙂 Enjoy.
Andreas
-
I just spent too long trying to figure this out since the documentation is not up to date and the chatGPT bot is just wrong.
Anyways I thought I would share how I am handling my first Flic Duo device to trigger different Home Assistant automations using the different buttons and webhooks . Hopefully this might save other people some time and effort.
main.js var buttonManager = require("buttons"); var http = require("http"); buttonManager.on("buttonSingleOrDoubleClickOrHold", function(obj) { var button = buttonManager.getButton(obj.bdaddr); var clickType = obj.isSingleClick ? "click" : obj.isDoubleClick ? "double_click" : "hold"; var payload = JSON.stringify({ button_name: button.name, click_type: clickType, }); var targetUrl = null; if (button.name === "lock") { targetUrl = "http://XXX.XXX.XX.XX:8123/api/webhook/flic-lock"; } else if (button.name === "auto1") { targetUrl = "http://XXX.XXX.XX.XX:8123/api/webhook/flic-leavingHome"; } else if (button.name === "duo1") { if (obj.buttonNumber === 0) { targetUrl = "http://XXX.XXX.XX.XX:8123/api/webhook/flic-duo1-big"; } else if (obj.buttonNumber === 1) { targetUrl = "http://XXX.XXX.XX.XX:8123/api/webhook/flic-duo1-small"; } } if (targetUrl) { http.makeRequest({ url: targetUrl, method: "POST", headers: {"Content-Type": "application/json"}, content: payload, }, function(err, res) { console.log(""); console.log("Sent to: " + targetUrl); console.log("Name: " + button.name); console.log("ID (bdaddr): " + button.bdaddr); console.log("Serial: " + button.serialNumber); console.log("Click Type: " + clickType); console.log("uuid: " + button.uuid); console.log("key: " + button.key); //console.log("Button #: " + obj.buttonNumber); console.log("Status: " + res.statusCode); }); } }); console.log("Script started");obj.buttonNumber returns 0 for the upper bigger button and 1 for the smaller lower button
You can also perform different automations depending on click type using an if statement where you change the keyword click using something like (YAML):
conditions: - condition: template value_template: "{{ trigger.json.click_type == 'click' }}"The HA automation webhook trigger YAML looks something like:
triggers: - allowed_methods: - POST - PUT local_only: true webhook_id: flic-leavingHome trigger: webhookThis might not be the best way to go about this but I would be interested to hear suggestions for improvements. Now do add the rest of the new buttons and model the dimensions as a 3D STL file. It would be nice if flic also provided this as well.
-