Pyicloud in place of currently nonfunction iCloud binding

I activated 2FA now and it works. So 2FA is required.

1 Like

I created a Python script which creates a JSON output containing all devices/attributes listed in:

sudo -u openhab /usr/local/bin/icloud --username icloud_username --llist

The output can be easily parsed with json transformation (I use jython rules).
I guess the only downside will be the required reauthentication every 2 months as mentioned in the pyicloud documentation. But I’ll see …

Script (I put it in /etc/openhab/scripts/)

from pyicloud import PyiCloudService
import subprocess

# functions
def is_int(str):
    try:
        int(str)
        return True
    except ValueError:
        return False

def is_float(str):
    try:
        float(str)
        return True
    except ValueError:
        return False

# icloud username
username = "put_your_icloud_username_here"

inputString = subprocess.check_output(r'/usr/local/bin/icloud --username {0} --llist'.format(username.rstrip()), shell=True).decode(sys.stdout.encoding).strip().split("\n")

# initialize output
outputJson = ''

# read content line by line
for lineContent in inputString :
    if '-----' in lineContent :
        # remove last comma if a new device section begins
        outputJson = outputJson.strip(',')
        # add end bracket for previous device section
        outputJson = outputJson + '},'
    elif not ' - ' in lineContent :
        # new device section does not contain " - "
        outputJson = outputJson + '"' + lineContent.rstrip('\r\n') + '": {'
    elif ' - ' in lineContent :
        # add attribute name
        outputJson = outputJson + '"' + lineContent.split()[0] + '": '
        # add value without quotes when starting with {
        if lineContent.rsplit(" - ",1)[1].rstrip('\r\n').startswith('{') :
            outputJson = outputJson + lineContent.rsplit(" - ",1)[1].rstrip('\r\n') + ','
        # add value without quotes when starting with [
        elif lineContent.rsplit(" - ",1)[1].rstrip('\r\n').startswith('[') :
            outputJson = outputJson + lineContent.rsplit(" - ",1)[1].rstrip('\r\n') + ','
        # add value without quotes when value is integer
        elif is_int(lineContent.rsplit(" - ",1)[1].rstrip('\r\n')) :
            outputJson = outputJson + lineContent.rsplit(" - ",1)[1].rstrip('\r\n') + ','
        # add value without quotes when value is float
        elif is_float(lineContent.rsplit(" - ",1)[1].rstrip('\r\n')) :
            outputJson = outputJson + lineContent.rsplit(" - ",1)[1].rstrip('\r\n') + ','
        else :
            # generally remove " in attribute value to prevent JSON parsing issues
            outputJson = outputJson + '"' + lineContent.rsplit(" - ",1)[1].rstrip('\r\n').replace('"', '' ) + '",'

# clean-up outputJson
outputJson = '{' + outputJson.strip('},') + '} }'
outputJson = outputJson.replace("'", "\"")
outputJson = outputJson.replace("\"True\"", "true")
outputJson = outputJson.replace("True", "true")
outputJson = outputJson.replace("\"False\"", "false")
outputJson = outputJson.replace("False", "false")
outputJson = outputJson.replace(" None, ", " \"None\", ")

# print out result
print(outputJson)

Jython rule (I run it every 5 minutes):

"""
pyicloud control

Authorize once after reboot:
sudo -u openhab /usr/local/bin/icloud --username your_icloud_username
"""
from core.rules import rule
from core.triggers import when
from core.log import logging, LOG_PREFIX
from core import osgi
import sys

from core.actions import ScriptExecution
from core.actions import Transformation
import json

#pyicloud
from core.actions import Exec
from java.time import Duration
import math

log = logging.getLogger("org.eclipse.smarthome.model.script.pyicloud")

@rule("Pyicloud updater", description="updates the iCloud devices", tags=["pyicloud"])
@when("Time cron 5 1/5 * * * ?")
def pyicloudUpdater(event):
    #log.warn("Rule: Pyicloud updater started.")

    output = Exec.executeCommandLine(Duration.ofSeconds(15), "python3", "/etc/openhab/scripts/icloud.py")

    ##################
    # iPhone
    ##################
    #log.warn("iPhone")
    batteryLevel = Transformation.transform("JSONPATH","$.iPhone.batteryLevel",output)
    #log.warn("batteryLevel=" + batteryLevel)
    deviceStatus = Transformation.transform("JSONPATH","$.iPhone.deviceStatus",output)
    #log.warn("deviceStatus=" + deviceStatus)
    timeStamp = Transformation.transform("JSONPATH","$.iPhone.location.timeStamp",output)
    #log.warn("timeStamp=" + timeStamp)
    latitude = Transformation.transform("JSONPATH","$.iPhone.location.latitude",output)
    #log.warn("latitude=" + latitude)
    longitude = Transformation.transform("JSONPATH","$.iPhone.location.longitude",output)
    #log.warn("longitude=" + longitude)
    latitudeAcc = Transformation.transform("JSONPATH","$.iPhone.location.horizontalAccuracy",output)
    #log.warn("latitudeAcc=" + latitudeAcc)
    longitudeAcc = Transformation.transform("JSONPATH","$.iPhone.location.verticalAccuracy",output)
    #log.warn("longitudeAcc=" + longitudeAcc)
    batteryStatus = Transformation.transform("JSONPATH","$.iPhone.batteryStatus",output)
    #log.warn("batteryStatus=" + batteryStatus)

    # update OH items
    if deviceStatus.startswith("20") :
        #log.warn("OK")
        try:
            accuracy = float(math.sqrt((float(latitudeAcc)*float(latitudeAcc)) + (float(longitudeAcc) * float(longitudeAcc))))
        except:
            accuracy = 0.0
        events.postUpdate("iPhone_BatteryLevel",str(int(float(batteryLevel)*100)))
        events.postUpdate("iPhone_Location",str(latitude + ", " + longitude + ", 0.0"))
        events.postUpdate("iPhone_LocationLast",str(timeStamp))
        events.postUpdate("iPhone_LocationAccuracy",str(accuracy))
        events.postUpdate("iPhone_BatteryStatus",str(batteryStatus))
    else :
        events.postUpdate("iPhone_BatteryStatus","Unknown")

... add additional devices the same way here ...

Hello. i tried connecting to icloud according to GitHub - Krata4/icloud: icloud library which is using pyicloud

Everything is ok until point 7. I’m not sure about point 7.
When i try command in putty “python /etc/openhab/scripts/icloud.py -l “your phone key””, show error:

  File "/etc/openhab/scripts/icloud.py", line 9
    async def icloud():
            ^
SyntaxError: invalid syntax

Item in icloud.py have:
item_name = "Location_Output"

Items:
String Location_Output

Thanks for help

Hello, try python3 instead of python. Async was added in python from version 3.5.
In my case python command using version 2.7 and python3 using version 3.9.

Hello. I am using an openhab on raspberry pi as openhabian. sudo -u openhab icloud --username=xxxx@yyyyyyy.com --list doesn’t work: PermissionError: [Errno 13] Permission denied: ‘/tmp/pyicloud/openhab’. This dir does not exist, but /tmp/pyicloud/openhabian does. Everything seems to work when I use “-u openhabian” but then the openhab command Thing icreated gives error: No pyicloud password for xxxx@yyyyyyy.com could be found in the system keychain. Use the --store-in-keyring command-line option for storing a password for this username.
I have tried all possible options I could think of but cannot get this to work. Any tips?

Hi,

try switch user like “sudo su - openhab -s /bin/bash” and than " icloud –username=xxxx@yyyyyyy.com --list"

Thanks
Jirka

Thanks Jiri, unfortunately same error: PermissionError: [Errno 13] Permission denied: ‘/tmp/pyicloud/openhab’
User openhab is different from user openhabian:

  • openhabian:x:1000:115:,:/home/openhabian:/bin/bash
  • openhab:x:110:115:openhab runtime user,:/var/lib/openhab:/bin/false

This error is appear when?which command…

could it be that you initially used the user openhabian instead of openhab to create the keyring entry ? Thus the folder openhabian was created under /tmp/pyicloud instead of under openhab.

hello, i try rule with exec:

output = Exec.executeCommandLine(Duration.ofSeconds(15), "python3", "/etc/openhab/scripts/icloud.py")

but reply to this command is:

Please enter password for encrypted keyring:

how run icloud.py with keyring password as parameter?

Hello, you have to run script in console firstly. First run is setup of password.

Team,

Not sure if it helps… but I am one more from the many of us trying to get icloud at my openhab, and the history loops always to the same problem with the broken plugin. I was a little bit desperate looking for alternatives, and thanks to this community I found some people experiencing with pyicloud python. I am a python person, and realized with some small coding I can build my own icloud extractor being openhab agnostic. This code can retrieve the info of every icloud device under my account, and by config I can propagate it thru mqtt or thru direct openhab apis to feed data. I also added an specific message to propagate also by mqtt or openhab items that when 2factor credentials are expired, to be noticed

If you are interested, feel free to take a look at GitHub - redcorjo/mqtt_pyicloud . Just published today and still very basic description how to use it. Mainly, ensure to run install pyicloud first at your desktop (I use mac and raspberry), and run under the same account planned to execute this script also the initializing of icloud --username user@domain.com value

Did you see that there is an updated binding available ?

Hi, yes I have done that, because openhab user does not allow me to execute the initial script.

sudo -u openhab icloud –username=xxxx@yyyyyyy.com --list deliver this error message: PermissionError: [Errno 13] Permission denied: ‘/tmp/pyicloud/openhab’. With username -u openhabian it does work. I get pyicloud working from the command line, but not from openhab UI.

I have two openhab related users on system:

  • openhabian:x:1000:115:,:/home/openhabian:/bin/bash
  • openhab:x:110:115:openhab runtime user,:/var/lib/openhab:/bin/false

It looks like openhab is defined as a runtime user and therefor I cannot use the user to create the keyring. Does that make sense?

Hi, can I install the new binding from the paper UI or should I manually install via .jar file?

It is an openHAB 3 binding, there is no PaperUI.
Place the .jar into your addons folder after uninstalling the old binding.

delete the folder with rm -rf /tmp/pyicloud/ (probably you need to execute it with sudo)
afterwards you can authenticate with the openhab user.

Hi, HaKuNa. I cannot authenticate with the openhab user for the earlier mentioned reasons. openhab user is a “runtime” user and gives error message: “PermissionError: [Errno 13] Permission denied: ‘/tmp/pyicloud/openhab’”

Thanks, this icloud binding is working.

I’m sure you are wrong. Your /tmp/pyicloud directory is owned by different user than openhab, therefore you get the permission error …

If you either change the owner of the directory or remove it before reauthenticate with the “correct” user, it will work.

[root@homeserver ~]# ls -al /tmp/ | grep pyicloud
drwxr-xr-x.  3 openhab openhab   60 Nov 20 18:34 pyicloud

[root@homeserver ~]# ls -al /tmp/pyicloud/
total 0
drwxr-xr-x.  3 openhab openhab  60 Nov 20 18:34 .
drwxrwxrwt. 17 root    root    360 Nov 20 18:34 ..
drwx------.  2 openhab openhab  80 Oct 16 09:08 openhab