A few people have asked me about Diabetes FreeStyle Libre 2 and Home Assistant integration on Facebook, so figured I would post it here too.
I’ve got a solution that works for me using Node Red. Unfortunately it’s not an integration per say due to the closed ecosystem of the Libre. To get around this, it requires an Android phone with the Librelink app installed (I believe this can be the same phone as that with the Libre app installed on, I’m using 2 phones though) This can also be done using the Nightscout integration, but that’s a little bit less complex due to the open ecosystem.
Below is my Node-Red flow, where it uses the Home Assistant app and the “Last Notification” sensor which I’ve restricted to only the LibreLink application.
I listen for the “Low Glucose Alarm” notification, check to see if my wife is home and that it’s night when we’re probably in bed.
It then turns on the bedroom lights, and makes an announcement via the Google Home Hub in the bedroom that blood sugars are low.
An actionable notification is also sent to my wife’s phone, which has an action that lets her set a timer for 20 minutes. After 20 minutes, the Google Home tells her to check her blood sugars and will get another notification on her phone.
If she then selects the “everything is ok” action, it turns the lights off and we can go back to sleep.
I haven’t got around to it yet, however next steps are to read the actual sugar levels from the notifications to see if theres an arrow symbolising her sugar levels are increasing, then automatically dismiss the alarms. Not sure how I feel about this part though, as sometimes the readings might not be what they seem. As you can see, I below I am using a custom function to store the last reading in a HA sensor
If anyone wants the flow, here it is:
[{"id":"6dc0247c.d7210c","type":"subflow","name":"Actionable Notification","info":"[Documentation](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/cookbook/actionable-notifications-subflow-for-android.html)\n","category":"","in":[{"x":84,"y":80,"wires":[{"id":"9d85d137.fe487"}]}],"out":[{"x":1172,"y":128,"wires":[{"id":"974bd48d.c253e8","port":0}]},{"x":1172,"y":176,"wires":[{"id":"974bd48d.c253e8","port":1}]},{"x":1172,"y":224,"wires":[{"id":"974bd48d.c253e8","port":2}]},{"x":960,"y":300,"wires":[{"id":"5bc7345c.07b1cc","port":1}]}],"env":[{"name":"service","type":"str","value":"","ui":{"label":{"en-US":"Notify Service"},"type":"input","opts":{"types":["str"]}}},{"name":"title","type":"str","value":"","ui":{"label":{"en-US":"Title"},"type":"input","opts":{"types":["str"]}}},{"name":"message","type":"str","value":"","ui":{"label":{"en-US":"Message"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Title","type":"str","value":"","ui":{"label":{"en-US":"Action 1 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action1Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 1 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Title","type":"str","value":"","ui":{"label":{"en-US":"Action 2 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action2Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 2 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Title","type":"str","value":"","ui":{"label":{"en-US":"Action 3 Title"},"type":"input","opts":{"types":["str"]}}},{"name":"action3Uri","type":"str","value":"","ui":{"label":{"en-US":"Action 3 URI (optional)"},"type":"input","opts":{"types":["str"]}}},{"name":"userInfo","type":"bool","value":"false","ui":{"label":{"en-US":"Populate User Information"},"type":"checkbox"}},{"name":"sticky","type":"bool","value":"false","ui":{"label":{"en-US":"Sticky"},"type":"checkbox"}},{"name":"group","type":"str","value":"None","ui":{"label":{"en-US":"Group"},"type":"select","opts":{"opts":[{"l":{"en-US":"None"},"v":""},{"l":{"en-US":"Cameras"},"v":"camera"},{"l":{"en-US":"Security"},"v":"security"},{"l":{"en-US":"Garage"},"v":"garage"},{"l":{"en-US":"Laundry Room"},"v":"laundry_room"}]}}},{"name":"color","type":"str","value":"","ui":{"label":{"en-US":"Color"},"type":"input","opts":{"types":["str"]}}},{"name":"timeout","type":"num","value":"","ui":{"label":{"en-US":"Timeout"},"type":"input","opts":{"types":["num"]}}},{"name":"icon","type":"str","value":"","ui":{"label":{"en-US":"Icon"},"type":"input","opts":{"types":["str"]}}},{"name":"priority","type":"str","value":""}],"meta":{},"color":"#DDAA99","outputLabels":["Action 1","Action 2","Action 3","Cleared"],"status":{"x":244,"y":272,"wires":[{"id":"204dbcfc.144ae4","port":0}]}},{"id":"f9e57204.71076","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"const actions = [];\n[1,2,3].forEach(i => {\n const name = `action${i}`\n const id = flow.get(`${name}Id`);\n const title = env.get(`${name}Title`);\n const uri = env.get(`${name}Uri`);\n const action = !!uri.length ? 'URI' : title ? flow.get(`${name}Id`) : undefined;\n \n actions.push({\n action,\n title,\n uri\n });\n});\n\nmsg._originalPayload = msg.payload;\nflow.set('latestMessage', msg);\n\nconst services = env.get('service');\nif(!services) {\n node.status({\n text: 'no services defined',\n shape: 'ring',\n fill: 'red'\n });\n return; \n}\n\nservices.trim().split(/,\\s*/).forEach(service => {\n node.warn(service)\n if(!service) return;\n \n msg.payload = {\n service,\n data: {\n title: env.get('title'),\n message: env.get('message'),\n data: {\n tag: flow.get('notificationTag'),\n actions,\n color: env.get(\"color\"),\n group: env.get(\"group\"),\n sticky: env.get(\"sticky\"),\n timeout: env.get(\"timeout\"),\n icon: env.get(\"icon\"),\n ttl: 0,\n priority: env.get('priority'),\n channel: env.get('priority') === \"high\" ? \"alarm_stream\": \"low\",\n }\n }\n };\n node.send(msg);\n});\n\nnode.done();","outputs":1,"noerr":0,"initialize":"const randomId = () => Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);\n\n[1,2,3].forEach(i => {\n flow.set(`action${i}Id`, `action${i}_${randomId()}`);\n})\n\n\nflow.set('notificationTag', `${env.get('title')}_${randomId()}`);","finalize":"","libs":[],"x":298,"y":80,"wires":[["368c9723.5876f8"]]},{"id":"974bd48d.c253e8","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"eventData.event.action","propertyType":"msg","rules":[{"t":"eq","v":"action1Id","vt":"flow"},{"t":"eq","v":"action2Id","vt":"flow"},{"t":"eq","v":"action3Id","vt":"flow"}],"checkall":"true","repair":false,"outputs":3,"x":1024,"y":176,"wires":[[],[],[]]},{"id":"204dbcfc.144ae4","type":"status","z":"6dc0247c.d7210c","name":"","scope":["f9e57204.71076","5bc7345c.07b1cc","a622c92a.2d9898","368c9723.5876f8"],"x":124,"y":272,"wires":[[]]},{"id":"5bc7345c.07b1cc","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\nconst event = msg.payload.event;\n\nlatestMessage.eventData = msg.payload;\nlatestMessage.payload = latestMessage._originalPayload;\ndelete latestMessage._originalPayload;\n\nif(env.get('userInfo')) {\n const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\n latestMessage.userData = userData;\n}\n\nif(msg.event_type === 'mobile_app_notification_cleared') {\n node.status({\n text: `cleared at: ${getPrettyDate()}`,\n shape: 'dot',\n fill: 'blue'\n });\n \n return [null, latestMessage];\n}\n\nconst index = [1,2,3].find(i => event[`action_${i}_key`] === event.action);\nnode.status({\n text: `${event[`action_${index}_title`]} at: ${getPrettyDate()}`,\n shape: 'dot',\n fill: 'green'\n});\n\nreturn latestMessage;\n\n\nfunction getPrettyDate() {\n return new Date().toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n hour12: false,\n hour: 'numeric',\n minute: 'numeric',\n });\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","x":832,"y":176,"wires":[["974bd48d.c253e8"],[]]},{"id":"8d3bdc0c.37493","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.tag","propertyType":"msg","rules":[{"t":"eq","v":"notificationTag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":432,"y":176,"wires":[["83ad2004.d04d"]]},{"id":"271e4479.b9249c","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","server":"f67d384a.4c4e98","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\"type\": \"config/auth/list\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":822,"y":128,"wires":[["5bc7345c.07b1cc"]]},{"id":"3618f055.6909a","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_cleared","server":"f67d384a.4c4e98","version":1,"event_type":"mobile_app_notification_cleared","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":194,"y":224,"wires":[["8d3bdc0c.37493"]]},{"id":"83ad2004.d04d","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":624,"y":176,"wires":[["271e4479.b9249c"],["5bc7345c.07b1cc"]]},{"id":"9d85d137.fe487","type":"switch","z":"6dc0247c.d7210c","name":"","property":"clear_notification","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","repair":false,"outputs":2,"x":143,"y":80,"wires":[["f9e57204.71076"],["a622c92a.2d9898"]],"l":false},{"id":"a622c92a.2d9898","type":"function","z":"6dc0247c.d7210c","name":"create clear notification","func":"const services = env.get('service');\nif(!services) {\n node.status({\n text: 'no services defined',\n shape: 'ring',\n fill: 'red'\n });\n return; \n}\n\nservices.trim().split(/,\\s*/).forEach(service => {\n if(!service) return;\n \n msg.payload = {\n service,\n data: {\n message: \"clear_notification\",\n data: {\n tag: flow.get('notificationTag'),\n }\n }\n };\n node.send(msg);\n});\n\nnode.done();","outputs":1,"noerr":0,"initialize":"","finalize":"","x":318,"y":128,"wires":[["368c9723.5876f8"]]},{"id":"9bfe567c.3d10c8","type":"server-events","z":"6dc0247c.d7210c","name":"mobile_app_notification_action","server":"f67d384a.4c4e98","version":1,"event_type":"mobile_app_notification_action","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":194,"y":176,"wires":[["8d3bdc0c.37493"]]},{"id":"368c9723.5876f8","type":"api-call-service","z":"6dc0247c.d7210c","name":"","server":"f67d384a.4c4e98","version":3,"debugenabled":false,"service_domain":"notify","service":"","entityId":"","data":"","dataType":"json","mergecontext":"callServiceData","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":550,"y":80,"wires":[[]]},{"id":"c9fbc23b.d07ee","type":"comment","z":"6dc0247c.d7210c","name":"Cleared","info":"","x":990,"y":260,"wires":[]},{"id":"987f9ea0.ffd55","type":"server-state-changed","z":"f7c56fe.870159","name":"P5: Low Glucose Alarm","server":"f67d384a.4c4e98","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.pixel_5_last_notification","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":140,"y":620,"wires":[["6f8140b1.0aa91"]]},{"id":"6f8140b1.0aa91","type":"function","z":"f7c56fe.870159","name":"","func":"var amount = msg.payload\nvar glucose = Number(amount.replace(/[^0-9\\.]+/g,\"\"));\n\nif(glucose === 0) return;\n\nmsg.glucose = glucose;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":620,"wires":[["57e41ffc.75103"]]},{"id":"57e41ffc.75103","type":"ha-entity","z":"f7c56fe.870159","name":"","server":"f67d384a.4c4e98","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"glucose_levels"},{"property":"device_class","value":""},{"property":"icon","value":"mdi:diabetes"},{"property":"unit_of_measurement","value":"mmol"}],"state":"glucose","stateType":"msg","attributes":[],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":510,"y":620,"wires":[[]]},{"id":"40794a2b.4293c4","type":"server-state-changed","z":"f7c56fe.870159","name":"P5: Low Glucose Alarm","server":"f67d384a.4c4e98","version":3,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.pixel_5_last_notification","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"Low Glucose Alarm","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":260,"wires":[["4c28e5a8.41c93c"],[]]},{"id":"fbec10cb.4739d","type":"api-call-service","z":"f7c56fe.870159","name":"Turn Bedroom On","server":"f67d384a.4c4e98","version":3,"debugenabled":false,"service_domain":"scene","service":"turn_on","entityId":"scene.bedroom_in_bed","data":"{\"transition\": 2}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":990,"y":220,"wires":[["9cadea1.53c4118"]]},{"id":"9cadea1.53c4118","type":"api-call-service","z":"f7c56fe.870159","name":"Announce","server":"f67d384a.4c4e98","version":3,"debugenabled":false,"service_domain":"tts","service":"cloud_say","entityId":"media_player.bedroom_hub","data":"{\t \"message\": \"Warning, Rachel has Low Blood Sugars\"\t}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1180,"y":220,"wires":[["d2cf2420.f7a978"]]},{"id":"4c28e5a8.41c93c","type":"delay","z":"f7c56fe.870159","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":400,"y":220,"wires":[["c3cad952.0eacc8"]]},{"id":"64c9647b.6906fc","type":"time-range-switch","z":"f7c56fe.870159","name":"","lat":"","lon":"","startTime":"21:00","endTime":"09:00","startOffset":0,"endOffset":0,"x":770,"y":220,"wires":[["374fa0c8.d6eb8","fbec10cb.4739d"],[]]},{"id":"c3cad952.0eacc8","type":"api-current-state","z":"f7c56fe.870159","name":"Rach home","server":"f67d384a.4c4e98","version":2,"outputs":2,"halt_if":"home","halt_if_type":"str","halt_if_compare":"is","entity_id":"device_tracker.pixel_3a","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"x":590,"y":220,"wires":[["64c9647b.6906fc"],[]]},{"id":"5a487cb6.003c44","type":"trigger-state","z":"f7c56fe.870159","name":"P3: Low Glucose Alarm","server":"f67d384a.4c4e98","version":0,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityid":"sensor.pixel_3a_last_notification","entityidfiltertype":"exact","debugenabled":false,"constraints":[{"targetType":"this_entity","targetValue":"","propertyType":"property","comparatorType":"includes","comparatorValueDatatype":"list","comparatorValue":"Low Glucose Alarm","propertyValue":"new_state.attributes['android.title']"}],"outputs":2,"customoutputs":[],"outputinitially":false,"state_type":"str","x":130,"y":200,"wires":[["4c28e5a8.41c93c"],[]]},{"id":"374fa0c8.d6eb8","type":"api-call-service","z":"f7c56fe.870159","name":"Notify TV","server":"f67d384a.4c4e98","version":3,"debugenabled":false,"service_domain":"notify","service":"living_room_tv","entityId":"","data":"{\"message\":\"Warning, Rachel has Low Blood Sugars\"}","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":980,"y":160,"wires":[[]]},{"id":"d2cf2420.f7a978","type":"subflow:6dc0247c.d7210c","z":"f7c56fe.870159","name":"Low Glucose","env":[{"name":"service","value":"mobile_app_pixel_4a","type":"str"},{"name":"title","value":"GlucoMinder","type":"str"},{"name":"message","value":"Low Glucose Alarm","type":"str"},{"name":"action1Title","value":"Set Alarm (20m)","type":"str"},{"name":"action2Title","value":"Everything's OK","type":"str"},{"name":"userInfo","type":"bool","value":"true"},{"name":"group","value":"","type":"str"},{"name":"color","value":"red","type":"str"},{"name":"icon","value":"mdi:diabetes","type":"str"},{"name":"priority","value":"high","type":"str"}],"x":1350,"y":220,"wires":[["be250f4a.3ae08"],["8f4c8240.24d3a"],[],[]]},{"id":"be250f4a.3ae08","type":"stoptimer","z":"f7c56fe.870159","duration":"20","units":"Minute","payloadtype":"num","payloadval":"0","name":"","x":1020,"y":80,"wires":[["bbcee1f3.1564a"],[]]},{"id":"bbcee1f3.1564a","type":"api-call-service","z":"f7c56fe.870159","name":"Turn Bedroom On","server":"f67d384a.4c4e98","version":3,"debugenabled":false,"service_domain":"scene","service":"turn_on","entityId":"scene.bedroom_in_bed","data":"{\"transition\": 2}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1230,"y":80,"wires":[["f2fbdfcf.c17ae"]]},{"id":"f2fbdfcf.c17ae","type":"api-call-service","z":"f7c56fe.870159","name":"Announce","server":"f67d384a.4c4e98","version":3,"debugenabled":false,"service_domain":"tts","service":"cloud_say","entityId":"media_player.bedroom_hub","data":"{\t \"message\": \"Rachel, it's time to check your bloods\"\t}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1400,"y":80,"wires":[["d2cf2420.f7a978"]]},{"id":"8f4c8240.24d3a","type":"api-call-service","z":"f7c56fe.870159","name":"Set bedroom nightlight","server":"f67d384a.4c4e98","version":3,"debugenabled":false,"service_domain":"scene","service":"turn_on","entityId":"scene.bedroom_rach_nightlight","data":"{\"transition\": 2}","dataType":"jsonata","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1600,"y":220,"wires":[[]]},{"id":"f67d384a.4c4e98","type":"server","name":"Home Assistant","version":1,"legacy":false,"addon":false,"rejectUnauthorizedCerts":false,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]
Anyone else got any other solutions for diabetes?