IOS Battery Level

I have done quite a bit of searching and it appears that I would be much better off if I had the question for an android device.

Is anyone aware of a path to automatically get an IOS devices battery level into NR?

I am content transforming any type of input into what I need. I already connect homebridge, MQTT and hubitat to Node Red so getting the data to one of those is the equivalent of getting it to NR for me.

The closest I have been able to find is installing OwnTracks on the device and having the OwnTracks info sent to MQTT. This information includes the current battery level, however, I can't seem to get it to trigger every 5 or 10 minutes like I would desire. This is not unexpected since it is primarily designed track location changes.

Ideally, I would love to find an app that runs in the background and sends battery level to MQTT either whenever it changes or every 5 minutes. I just don't think such an app exists.

Hi Mike,

Unfortunately the Javascript Battery Status API is deprecated and not supported on iOs. Otherwise you could have triggered it e.g. in a template node in the dashboard. And even if it should be supported, it would mean that your dashboard should have been open all the time, to call that API at regular intervals.

So I don't think you can integrate such functionality entirely into Node-RED, which means indeed - like you are already doing - you will need some kind of third party app to deliver that information.

Good luck with it!

Perhaps you can use siri shortcuts, there is an ssh option, email, or even mqtt with pythonista (if you can get it working).

I had examined shortcuts but haven’t been able to get battery level on a recurring basis.

Although you're not using it, Home Assistant can do this using the iCloud integration. So you could investigate if any of the Node Red iCloud nodes provide the same functionality (or delve into the code).

I'm using Home Assistant, and with this integration I can see the battery and charging status of my phone, watch, and Macbook, along with the same details for family members (with Family Sharing).

I can see that OpenHAB has something as well.

I couldn't find a Node.js library on npm but I did find this Python library:

Perhaps that could be translated to something that could be used in Node-RED. It would certainly be useful.

For anyone who is trying to do this, I have found a solution. Unfortunately the solution is a bit of a Frankenstein sort of setup. Part of its complexity is based on the fact that almost all of my setup is in docker instances devoted to a single application (Node-Red, MQTT, etc.). If you don't have this sort of setup it will likely be easier for you set up.

I use MQTT extensively so I knew if I could get a regular data feed into MQTT (like I have from Zigbee2MQTT for many zigbee devices) then all of the rest of my setup for logic would work easily. What I will show here is about getting the data into MQTT. I am sure that you could build a function node (if you do such things) that could parse out the key data and send it out as a Node-Red message, but because I use MQTT, I didn't build such a function node.

First I found this:

This is a python app that "Connects iCloud Accounts/Devices to Homie 4 MQTT convention."

My first thought on finding this was, "Perfect, the data will be in MQTT". Turns out that of course to run a python app, you need python installed. And it turns out that the Node-Red Docker container I run doesn't and seems to not really have any way to install it. I thought thats ok, I can get a Docker container that has python, run iCloud-Homie-4 there and then get what I need out of MQTT. So I got that container up and this part is probably common, however and wherever you run python, I installed iCloud-Homie-4 using the pip command from its webpage "pip install iCloud-Homie-4".

At this point, if you are familiar with python and or don't have a weird Docker setup you probably could just edit the yaml file decribed on the webpage, point it to you MQTT server and be off. Unfortunately while I could edit the yaml file and get it situated, I could not manage to actually start pip install iCloud-Homie-4.

While hacking around trying to get it to work, I learned that the pip install iCloud-Homie-4 install had installed various things needed to work and there was now an executable program on the system called icloud. I think this is yet another set of software developed by someone else that pip install iCloud-Homie-4 calls and then processes the output and sends it to MQTT. But since that program outputs the results to stdout, I didn't actually need the pip install iCloud-Homie-4 portion to work.

So I setup icloud. To do so, from the command line I ran the command icloud --username --password AlaKazam . The next few steps are from memory, so sorry if they aren't exactly right, but it seemed pretty straightforward at the time. Apple wanted a 2FA verification that I should have access to the data and that I hadn't just stolen someones username and password. So it both popped up the 2FA on my IOS devices and gave me the option to have a code texted to me. Because the iCloud-Homie-4 website said to use the text code and not the popup code, that is what I did. I took the code from the text message and put it in to the command line where prompted. That completed the authentication. I think I then clicked the approve/that is me button on the popup on the IOS device, but did nothing with code it showed me. That code might have worked, but because the texted code worked, I didn't even try.

At this point, I could now run either of these commands (note that neither includes the password):
icloud --username --llist
icloud --username --list

list gave a short readout of all of the devices on my account, but not the battery level. llist give a lot of information about all of the devices on the account.

This was great, I got what I needed, except this is not the docker instance running Node-Red. How do I get it there? After a bunch of stops and starts, I gave in and just installed Node-Red on this Docker container. Inside this Node-Red instance, I created this flow that uses an exec node to run the icloud program, takes the stdout results, runs them through a function node that parses the input into a series of msg objects suitable for sending to my MQTT server. In case anyone wants it here is the flow:

[{"id":"b1461284.d3add","type":"exec","z":"fd3f9aee.282bf8","command":"icloud --username --llist","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":490,"y":580,"wires":[["a2d35223.10dc1"],["c839104b.0fb98"],[]]},{"id":"c839104b.0fb98","type":"debug","z":"fd3f9aee.282bf8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":750,"y":600,"wires":[]},{"id":"7bb3cc06.1e30b4","type":"mqtt out","z":"fd3f9aee.282bf8","name":"","topic":"","qos":"2","retain":"false","broker":"30ef67df.7df4a8","x":1090,"y":560,"wires":[]},{"id":"a2d35223.10dc1","type":"function","z":"fd3f9aee.282bf8","name":"Format icloud data for MQTT","func":"//split into array based on newline\ntopics =[]\nlet aOS = msg.payload.split('\\n')\n//set destination sub-object\nmsg.parsed = {}\n//loop through all lines\nfor( var i = 0; i < aOS.length; i++ ) {\n    //identify and then do diffent process for IOS device versus reading for that device\n    if ((aOS[i].slice(0,1) !== \" \")&&(aOS[i].slice(0,1) !== \"-\")&& (aOS[i] !== \"\")){\n        //save IOS device name and then create empty sub-object\n        deviceName =aOS[i]\n        msg.parsed[deviceName]={}\n    } else{\n        //ignore the header line that is all dashes and the footer line that is empty\n        if ((aOS[i].slice(0,1) !== \"-\")&&aOS[i] !== \"\"){\n            //parse out the name of the reading\n            dataType= aOS[i].slice(0,20).trim()\n            //if the reading starts with { then it almost JSON\n            if (aOS[i].substring(23).trim().slice(0,1) == \"{\"){\n                cleanedToJSON = aOS[i].substring(23).trim()\n                //reformat False, True, None and ' into false, true, \"None\" and '\n                cleanedToJSON = cleanedToJSON.replace(/False/g, \"false\")\n                cleanedToJSON = cleanedToJSON.replace(/True/g, \"true\")\n                cleanedToJSON = cleanedToJSON.replace(/None/g, '\"None\"')\n                cleanedToJSON = cleanedToJSON.replace(/'/g, '\"')\n                //parse string into object\n                cleanedToJSON = JSON.parse(cleanedToJSON)\n                //place all key:value pairs in the new object into the IOS sub-object\n                for (const [key, value] of Object.entries(cleanedToJSON)) {\n                    msg.parsed[deviceName][key] = value\n                }\n            } else {\n                //put the reading name (key) and the reading value as a key:value pair within the IOS device sub-Object\n                msg.parsed[deviceName][dataType] = aOS[i].substring(23).trim()\n            }\n        }\n    }\n}\n//remove the input\ndelete msg.payload\n//node.warn(msg)\nfor (var [key, value] of Object.entries(msg.parsed)) {\n    for (var [key2, value2] of Object.entries(value)) {\n        //node.warn(`${key}: ${key2}: ${value2}`);\n        key = key.replace(/\\+/g, \"plus\")\n        key2 = key2.replace(/\\+/g, \"plus\")\n        msg.topic = `IOS/${key}/${key2}`\n        topics.push(`IOS/${key}/${key2}`)\n        msg.payload = value2\n        node.send(msg)\n        //msg.parsed[deviceName][key] = value\n        }\n    //node.warn(`${key}: ${value}`);\n    //msg.parsed[deviceName][key] = value\n    }\n//node.warn(topics)\nreturn //msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":840,"y":560,"wires":[["7bb3cc06.1e30b4"]]},{"id":"a04412f2.a97c8","type":"inject","z":"fd3f9aee.282bf8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"600","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":190,"y":580,"wires":[["b1461284.d3add"]]},{"id":"609e98c4.b31448","type":"catch","z":"fd3f9aee.282bf8","name":"","scope":null,"uncaught":false,"x":560,"y":640,"wires":[["c839104b.0fb98"]]},{"id":"30ef67df.7df4a8","type":"mqtt-broker","z":"","name":"Placeholder MQTT","broker":"","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

I think I have anonymized everything, but would appreciate feedback if I left personal information in the sample flow.

So until Apple makes a change that breaks what the icloud app is doing, I have exactly what I need plus a bunch of other data that I may or may not end up using in some way.

Just to give you a fee. for the data provided, here is are several screenshots of the MQTT data in MQTT Explorer:

In particular, I am likely to use batteryLevel, batteryStatus, Latitude and Longitude. Again, if you see that I have left in any personal data, I would appreciate a heads up.

Oh, a few more notes:

  • This info is only as good as what iCloud knows about the devices.
  • I don't know how often IOS devices update iCloud.
  • I have this set to pull the information on a 10 minute interval. I am not sure if any shorter interval makes sense.
  • I started with a 15 minute interval and found that for some devices (including my iPhone), that with the longer interval, the battery reading would sometimes drop to 0 even when it was at 100%. That reading, however, seemed to have caused iCloud to query the device and so the next reading was always accurate. By decreasing this to 10 minutes it seems to have gone away as a problem.
  • I will warn, that you might be tempted to use the lat/long for presence sensing. This won't really work (at least for triggering things on arrival). By only querying every 10 minutes the latency for knowing your device is at home can be 10 minutes plus the latency of how often the device updates iCloud.
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.