@Emil Sorry. Didn't write that it's when I'm in my car on my way home.😉
-
I have Home Assistant running on my Raspberry Pi 4B as the OS (not docker).
Directly underneath my RPi4 is my Flic Hub LR. Both are connected to my router via Ethernet.
Given that Home Assistant is so popular I, foolishly, believed it would be straightforward to connect the two via the Home Assistant Integrations... it isn't!
Home Assistant is asking for a "pairing code". Flic support in this forum is saying that the code is not available.
I don't have a HomePod and don't want to spend £100 on buying one, though I am an Apple user (phone, tablet, and Macbook).
Surely, there must be a simple way to be able to use my Flic Hub LR within Home Assistant... But how?
-
Half the time I use my Flic button to run play, pause, next, previous, or play playlist on my iPhone, I get the message 'Spotify Web Connect: No active playback session'. This even happens if I've been playing songs on Spotify for more than a few minutes. Reopening the app fixes the problem, but this totally defeats the point of having a button to run these commands.
And forget about playing something from scratch, even if I choose a specific device on which to play it. Are there iOS/Spotify API limitations at play here, or is a Flic-related fix coming?
-
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.