Freestyle Libre 2 Diabetic Alarms and Integration

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?

2 Likes

Hi, I’d be interested in how you’re using the notifications. The wife’s phone is iOS though not android. Although I’d consider some sort of Android VM to run Libre link if I need another app to forward the notifications.

I have dabbled with fiddler to try and reverse engineer things, but didn’t get too far.

Cheers

James

Notifications are done using the Libre app on Android. Unfortunately it seems the iOS Companion app can’t access notifications. You can also use the Libre Connect app on Android and pull them that way

Hello, I can’t get the trigger to activate with the last notification, the event is displayed but nothing happens, what parameters did you use on “Edit Trigger: state node”?

I’ve change my flow quite a bit, however this is what I’m using

image
image

image

Grazieee!!! Now I try!! :slightly_smiling_face:

Hi, is their a way to use this also with IOS Devices to get the Sensor Data?

if you send data into the cloud with a phone and an app, you could hook into the data with LibreLinkUp as suggested here:

Hello,

I’ve just posted my Node Red method of getting the API data from Libre Link here. I’m hoping someone can make a proper integration.

This LibreLink integration works: GitHub - gillesvs/librelink: Librelink integration for Home Assistant
I use the data in HA to add complications to my Apple Watch to see current BGL and Trend

1 Like

Awesome work. Any chance you could add an option to show the data in mmol/L for us Canadians?