Pyicloud in place of currently nonfunction iCloud binding

With the iCloud binding not working and no short term prospect for it being revised, I am going to try the pyicloud approach suggested by @Jiri_Kratochvil and @dathbe. As suggested by @peteraquino at iCloud Binding Communication Error - #354 by peteraquino, I’m creating a new thread for this discussion.

After looking at both approaches, I’m leaning towards a hybrid. I want to use iCloud location for multiple iPhone devices for several members of my family. I like the way multiple devices are handled in this iCloud Binding Communication Error - #352 by dathbe by @dathbe, but found the script installation iCloud Binding Communication Error - #348 by Jiri_Kratochvil by @Jiri_Kratochvil easier to follow given my limited java and python knowledge. I’m concerned that the Exec Binding approach may may lead to timeout restrictions if I run it 3 times in a row for the 3 separate device IDs.

I’m running openHAB 3.4.0M1 on a Pi 4 using openhabian. Current system specs from the UI Technical Information details are:

runtimeInfo:
  version: 3.4.0.M1
  buildString: Milestone Build
locale: en-US
systemInfo:
  configFolder: /etc/openhab
  userdataFolder: /var/lib/openhab
  logFolder: /var/log/openhab
  javaVersion: 11.0.16
  javaVendor: Raspbian
  osName: Linux
  osVersion: 5.15.61-v7l+
  osArchitecture: arm
  availableProcessors: 4
  freeMemory: 479311920
  totalMemory: 854589440
  startLevel: 70
bindings:
  - amazonechocontrol
  - astro
  - denonmarantz
  - harmonyhub
  - icalendar
  - icloud
  - ipcamera
  - mail
  - mqtt
  - myq
  - network
  - omatic
  - openweathermap
  - remoteopenhab
  - tplinksmarthome
  - tuya
  - zwave
clientInfo:
  device:
    ios: false
    android: false
    androidChrome: false
    desktop: true
    iphone: false
    ipod: false
    ipad: false
    edge: false
    ie: false
    firefox: false
    macos: false
    windows: true
    cordova: false
    phonegap: false
    electron: false
    nwjs: false
    webView: false
    webview: false
    standalone: false
    os: windows
    pixelRatio: 1.5
    prefersColorScheme: light
  isSecureContext: false
  locationbarVisible: true
  menubarVisible: true
  navigator:
    cookieEnabled: true
    deviceMemory: N/A
    hardwareConcurrency: 4
    language: en-US
    languages:
      - en-US
    onLine: true
    platform: Win32
  screen:
    width: 1707
    height: 1067
    colorDepth: 24
  support:
    touch: false
    pointerEvents: true
    observer: true
    passiveListener: true
    gestures: false
    intersectionObserver: true
  themeOptions:
    dark: light
    filled: true
    pageTransitionAnimation: default
    bars: filled
    homeNavbar: default
    homeBackground: default
    expandableCardAnimation: default
  userAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
    like Gecko) Chrome/106.0.0.0 Safari/537.36
timestamp: 2022-10-17T19:06:17.821Z

I followed the first 2 steps from GitHub - Krata4/icloud: icloud library which is using pyicloud
After that I was able to launch pyicloud, log into iCloud and retrieve the information for my devices using

penhabian@openhab-midway:~ $ icloud --username=gmhyde@gmail.com --list

This returned the list of my devices. But it doesn’t include any family member devices that I was previously able to access through the ICloud binding (and that I see on my iPhone’s FindMy App). Before going any further, I wanted to be sure that I would be able to access devices which are part of my iCloud Family group.

I also have a few questions about making the python script run every 5 minutes, but before addressing that, I want to be sure that when I’m finished, I will be able to get all of the information I need.

Thanks for any suggestions.

2 Likes

Hi,

I can suppot in openhab settings for run every 5 minutes and so on. But i can not help in family member because i dont have account for testing unfortunatly. I have account with 5 devices and i can see all devices in search.

Periodic run in Openhab, you can setup in exec bindings as number of seconds.

I am using my script for two devices more than 3 weeks and it is working fine.

Jirka

Thanks for letting me know. I will go ahead and try the exec binding approach for my devices and hope for a way forward on the other family devices.

I get all the devices as listed in “find my iPhone” incl. the devices of my family members.

Hello.
I am unable to install pyicloud. My OpenHabian does not know the PIP command.
How do I go about it step by step?

First of all thank you very much for publishing the solution wirth the Python Script.
I also installed Python and pyicloud and wanted to test the login into icloud. So I tested with

icloud --username=myaccount@example.com --list

and I typed in my password correctly, but I only get the following errors:

Enter iCloud password for myaccount@example.com:
Bad username or password for myaccount@example.com
Enter iCloud password for myaccount@example.com:
Bad username or password for myaccount@example.com
Enter iCloud password for myaccount@example.com:
Traceback (most recent call last):
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\pyicloud\base.py", line 351, in _authenticate_with_token
    req = self.session.post(
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\requests\sessions.py", line 635, in post
    return self.request("POST", url, data=data, json=json, **kwargs)
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\pyicloud\base.py", line 160, in request
    self._raise_error(code, reason)
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\pyicloud\base.py", line 189, in _raise_error
    raise api_error
pyicloud.exceptions.PyiCloudAPIResponseException: Missing apple_id field

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\pyicloud\cmdline.py", line 191, in main
    api = PyiCloudService(username.strip(), password.strip())
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\pyicloud\base.py", line 271, in __init__
    self.authenticate()
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\pyicloud\base.py", line 335, in authenticate
    self._authenticate_with_token()
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\pyicloud\base.py", line 357, in _authenticate_with_token
    raise PyiCloudFailedLoginException(msg, error) from error
pyicloud.exceptions.PyiCloudFailedLoginException: ('Invalid authentication token.', PyiCloudAPIResponseException('Missing apple_id field'))

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Program Files\Python310\lib\runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "C:\code\python\icloud.exe\__main__.py", line 7, in <module>
  File "C:\Users\marti\AppData\Roaming\Python\Python310\site-packages\pyicloud\cmdline.py", line 262, in main
    raise RuntimeError(message) from err
RuntimeError: Bad username or password for myaccount@example.com

When I login to icloud via the Webpage the same Apple ID and password work flawless.
Do you have any ideas what I am doing wrong?

Thanks for any hints!

First you have to set/store the credentials as described here:

→ means use command without “-list”
→ but I don’t know how exactly it works in Windows, I’m using CentOS
→ in Linux, if you want to use it via OpenHAB, you have to execute the command under openhab user

sudo -u openhab /usr/local/bin/icloud --username your_iCloud_username

→ 2FA is required probably (accept from an i-device and enter 2FA code in prompt), now the credentials can be stored in the keyring and further calls do not require authentication

Thanks for your help. I also tried without “-list” but unfortunately the result is the same

That might be the cause, I do not use 2FA so far and I would like not to activate 2FA.
Is anybody here using the pyicloud solution successfully without 2FA?

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