diff --git a/README-frida.md b/README-frida.md
new file mode 100644
index 0000000..291c3cc
--- /dev/null
+++ b/README-frida.md
@@ -0,0 +1,123 @@
+This is for historical info; it is no longer necessary
+unless you're trying to make sense of the phone application
+or XML code.
+
+## Finding the PSK and IV (no longer necessary)
+
+
+
+You will need to set the dishwasher to "`Local network only`"
+in the setup application so that your phone will connect
+directly to it, rather than going through the cloud services.
+
+You'll also need a rooted Android phone running `frida-server`
+and the `find-psk.frida` script. This will hook the callback
+from the OpenSSL library `hcp::client_psk_callback` that is called
+when OpenSSL has made a connection and now needs to establish
+the PSK.
+
+```
+frida --no-pause -f com.bshg.homeconnect.android.release -U -l find-psk.frida
+```
+
+It should start the Home Connect application and eventually
+print a message like:
+
+```
+psk callback hint 'HCCOM_Local_App'
+psk 32 0x6ee63fb2f0
+ 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
+00000000 0e c8 1f d8 c6 49 fa d8 bc e7 fd 34 33 54 13 d4 .....I.....43T..
+00000010 73 f9 2e 01 fc d8 26 80 49 89 4c 19 d7 2e cd cb s.....&.I.L.....
+```
+
+Which gives you the 32-byte PSK value to copy into the `hcpy` program.
+
+## SSL logging
+
+The Frida script will also dump all of the SSL traffic so that you can
+see different endpoints and things. Not much is documented yet.
+
+Note that the TX from the phone on the websocket is "masked" with an
+repeating 4-byte XOR that is sent in the first part of each messages.
+The script could be augmented to decode those as well.
+The replies from the device are not masked so they can be read in the clear.
+
+## Retrieving home appliance configuration
+
+```
+frida-trace -o initHomeAppliance.log -f "com.bshg.homeconnect.android.release" -U -j '*!initHomeAppliance''
+```
+
+PSK can also be found in the last section of the config as base64url encoded.
+
+```
+echo 'Dsgf2MZJ-ti85_00M1QT1HP5LgH82CaASYlMGdcuzcs"' | tr '_\-"' '/+=' | base64 -d | xxd -g1
+```
+
+The IV is also there for devices that use it. This needs better documentation.
+
+TODO: document the other frida scripts that do `sendmsg()` and `Encrypt()` / `Decrypt()` tracing
+
+
+
+## hcpy
+
+
+
+The `hcpy` tool can contact your device, and if the PSK is correct, it will
+register for notification of events.
+
+```
+RX: {'sID': 2354590730, 'msgID': 3734589701, 'resource': '/ei/initialValues', 'version': 2, 'action': 'POST', 'data': [{'edMsgID': 3182729968}]}
+TX: {"sID":2354590730,"msgID":3734589701,"resource":"/ei/initialValues","version":2,"action":"RESPONSE","data":[{"deviceType":"Application","deviceName":"py-hca","deviceID":"1234"}]}
+TX: {"sID":2354590730,"msgID":3182729968,"resource":"/ci/services","version":1,"action":"GET"}
+TX: {"sID":2354590730,"msgID":3182729969,"resource":"/iz/info","version":1,"action":"GET"}
+TX: {"sID":2354590730,"msgID":3182729970,"resource":"/ei/deviceReady","version":2,"action":"NOTIFY"}
+RX: {'sID': 2354590730, 'msgID': 3182729968, 'resource': '/ci/services', 'version': 1, 'action': 'RESPONSE', 'data': [{'service': 'ci', 'version': 3}, {'service': 'ei', 'version': 2}, {'service': 'iz', 'version': 1}, {'service': 'ni', 'version': 1}, {'service': 'ro', 'version': 1}]}
+RX: {'sID': 2354590730, 'msgID': 3182729969, 'resource': '/iz/info', 'version': 1, 'action': 'RESPONSE', 'data': [{'deviceID': '....', 'eNumber': 'SX65EX56CN/11', 'brand': 'SIEMENS', 'vib': 'SX65EX56CN', 'mac': '....', 'haVersion': '1.4', 'swVersion': '3.2.10.20200911163726', 'hwVersion': '2.0.0.2', 'deviceType': 'Dishwasher', 'deviceInfo': '', 'customerIndex': '11', 'serialNumber': '....', 'fdString': '0201', 'shipSki': '....'}]}
+```
+
+## Feature UID mapping
+
+There are other things that can be hooked in the application
+to get the mappings of the `uid` to actual menu settings and
+XML files of the configuration parameters.
+
+In the `xml/` directory are some of the device descriptions
+and feature maps that the app downloads from the Home Connect
+servers. Note that the XML has unadorned hex, while the
+websocket messages are in decimal.
+
+For instance, when the dishwasher door is closed and then
+re-opened, it sends the messages for `'uid':512`, which is 0x020F hex:
+
+```
+RX: {... 'data': [{'uid': 527, 'value': 1}]}
+RX: {... 'data': [{'uid': 527, 'value': 0}]}
+```
+
+In the `xml/dishwasher-description.xml` there is a `statusList`
+that says uid 0x020f is a readonly value that uses enum 0x0201:
+
+```
+
+```
+
+In the `xml/dishwasher-featuremap.xml` there is a mapping of feature
+reference UIDs to names:
+
+```
+ BSH.Common.Status.DoorState
+```
+
+as well as mappings of enum ids to enum names and values:
+
+```
+
+ Open
+ Closed
+
+```
+
+
diff --git a/README.md b/README.md
index a8964e4..a3cafcf 100644
--- a/README.md
+++ b/README.md
@@ -1,163 +1,253 @@
-
+
# Interface with Home Connect appliances in Python
This is a very, very beta interface for Bosch-Siemens Home Connect
-devices through their local network connection. It has some tools
-to find the TLS PSK (Pre-shared Key) that is used to allow local
-access, and a Python script that can construct the proper Websocket
-interface to subscribe to events.
+devices through their local network connection. Unlike the usual
+reputation of IoT devices for very bad security, BSG seem to have
+done a decent job of designing their system, especially since
+they allow a no-cloud local control configuration. The protocols
+seem sound, use well tested cryptographic libraries (TLS PSK with
+modern ciphres) or well understood primitives (AES-CBC with HMAC),
+and should prevent most any random attacker on your network from being able to
+[take over your appliances to mine cryptocurrency](http://www.antipope.org/charlie/blog-static/2013/12/trust-me.html).
-*WARNING: This is not ready for prime time!*
+*WARNING: This tool not ready for prime time and is still beta!*
-The dishwasher has a local HTTPS port open. Attempting to connect to
-the HTTPS port with `curl` results in a cryptic protocol error
-due to the non-standard cipher selection, `ECDHE-PSK-CHACHA20-POLY1305`.
-PSK also requires that both sides agree on a symetric key,
-so it is necessary to figure out what that key is before any
-further progress can be made.
-
-The clothes washer has a local HTTP port that also responds to websocket
-traffic, although the contents of the frames are AES-CBC encrypted with a key
-derived from the PSK and authenticated with SHA256-HMAC using another
-key derived from the PSK. It is also necessary to find the IV for the AES
-encryption to communicate with the washer.
-
-Despite the usual reputation for bad IoT security, Bosch-Siemens seem to
-have done a decent job of designing their system, especially since they
-considered a no-cloud local control configuration. The protocols seem
-sound and should prevent most any random attacker on your network from
-being able to take over your appliances to mine cryptocurrency.
-
-## Configuring devices
-
-The `hc-login` script perfoms the OAuth process to login to your
-Home Connect account and retrieves a list of all the connected devices
-along with the PSK or AES keys and IV, as well as the device descriptions.
-These are stored into a `config.json` file that can be used by `hc2mqtt`
-to contact the devices and relay their state to your MQTT server.
+## Setup
+
+```
+pip3 -r requirements.txt
+```
+
+Install the Python dependencies; the `sslpsk` one is a little weird
+and we might need to revisit it later.
+
+
+## Authenticate to the cloud servers
+
+```
+hc-login $USERNAME $PASSWORD > config.json
+```
+
+The `hc-login` script perfoms the OAuth process to login to your
+Home Connect account with your usename and password. It
+receives a bearer token that can then be used to retrieves
+a list of all the connected devices, their authentication
+and encryption keys, and XML files that describe all of the
+features and options.
+
+This only needs to be done once or when you add new devices;
+the resulting configuration JSON file *should* be sufficient to
+connect to the devices on your local network, assuming that
+your mDNS or DNS server resolves the names correctly.
+
+
+## Home Connect to MQTT
```
-hc-login username password > config.json
hc2mqtt config.json
```
-## Finding the PSK and IV (no longer necessary)
+This tool will establish websockets to the local devices and
+transform their messages into MQTT JSON messages. The exact
+format is likely to change; it is currently a thin translation
+layer over the XML retrieved from cloud servers during the
+initial configuration.
-
+### Dishwasher
-You will need to set the dishwasher to "`Local network only`"
-in the setup application so that your phone will connect
-directly to it, rather than going through the cloud services.
+
-You'll also need a rooted Android phone running `frida-server`
-and the `find-psk.frida` script. This will hook the callback
-from the OpenSSL library `hcp::client_psk_callback` that is called
-when OpenSSL has made a connection and now needs to establish
-the PSK.
+The dishwasher has a local HTTPS port open, although attempting to connect to
+the HTTPS port with `curl` results in a cryptic protocol error
+due to the non-standard cipher selection, `ECDHE-PSK-CHACHA20-POLY1305`.
+PSK also requires that both sides agree on a symetric key,
+so a special hacked version of `sslpsk` is used to establish the
+connection and then hand control to the Python `websock-client`
+library.
+
+Example message published to `homeconnect/dishwasher`:
```
-frida --no-pause -f com.bshg.homeconnect.android.release -U -l find-psk.frida
+{
+ "state": "Run",
+ "door": "Closed",
+ "remaining": "2:49",
+ "power": true,
+ "lowwaterpressure": false,
+ "aquastop": false,
+ "error": false,
+ "remainingseconds": 10140
+}
```
-It should start the Home Connect application and eventually
-print a message like:
+
+Full state information
+```
+{
+ 'AllowBackendConnection': False,
+ 'BackendConnected': False,
+ 'RemoteControlLevel': 'ManualRemoteStart',
+ 'SoftwareUpdateAvailable': 'Off',
+ 'ConfirmPermanentRemoteStart': 'Off',
+ 'ActiveProgram': 0,
+ 'SelectedProgram': 8192,
+ 'RemoteControlStartAllowed': False,
+ '520': '2022-02-21T16:48:54',
+ 'RemoteControlActive': True,
+ 'AquaStopOccured': 'Off',
+ 'DoorState': 'Open',
+ 'PowerState': 'Off',
+ 'ProgramFinished': 'Off',
+ 'ProgramProgress': 100,
+ 'LowWaterPressure': 'Off',
+ 'RemainingProgramTime': 0,
+ 'ProgramAborted': 'Off',
+ '547': False,
+ 'RemainingProgramTimeIsEstimated': True,
+ 'OperationState': 'Inactive',
+ 'StartInRelative': 0,
+ 'EnergyForecast': 82,
+ 'WaterForecast': 70,
+ 'ConnectLocalWiFi': 'Off',
+ 'SoftwareUpdateTransactionID': 0,
+ 'SoftwareDownloadAvailable': 'Off',
+ 'SoftwareUpdateSuccessful': 'Off',
+ 'ProgramPhase': 'Drying',
+ 'SilenceOnDemandRemainingTime': 0,
+ 'EcoDryActive': False,
+ 'RinseAid': 'R04',
+ 'SensitivityTurbidity': 'Standard',
+ 'ExtraDry': False,
+ 'HotWater': 'ColdWater',
+ 'TimeLight': 'On',
+ 'EcoAsDefault': 'LastProgram',
+ 'SoundLevelSignal': 'Off',
+ 'SoundLevelKey': 'Medium',
+ 'WaterHardness': 'H04',
+ 'DryingAssistantAllPrograms': 'AllPrograms',
+ 'SilenceOnDemandDefaultTime': 1800,
+ 'SpeedOnDemand': False,
+ 'InternalError': 'Off',
+ 'CheckFilterSystem': 'Off',
+ 'DrainingNotPossible': 'Off',
+ 'DrainPumpBlocked': 'Off',
+ 'WaterheaterCalcified': 'Off',
+ 'LowVoltage': 'Off',
+ 'SaltLack': 'Off',
+ 'RinseAidLack': 'Off',
+ 'SaltNearlyEmpty': 'Off',
+ 'RinseAidNearlyEmpty': 'Off',
+ 'MachineCareReminder': 'Off',
+ '5121': False,
+ 'HalfLoad': False,
+ 'IntensivZone': False,
+ 'VarioSpeedPlus': False,
+ '5131': False,
+ '5134': True,
+ 'SilenceOnDemand': False
+}
+```
+
+
+
+### Clothes washer
+
+
+
+The clothes washer has a local HTTP port that also responds to websocket
+traffic, although the contents of the frames are AES-CBC encrypted with a key
+derived from `HMAC(PSK,"ENC")` and authenticated with SHA256-HMAC using another
+key derived from `HMAC(PSK,"MAC")`. The encrypted messages are send as
+binary data over the websocket (type 0x82).
+
+Example message published to `homeconnect/washer`:
```
-psk callback hint 'HCCOM_Local_App'
-psk 32 0x6ee63fb2f0
- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
-00000000 0e c8 1f d8 c6 49 fa d8 bc e7 fd 34 33 54 13 d4 .....I.....43T..
-00000010 73 f9 2e 01 fc d8 26 80 49 89 4c 19 d7 2e cd cb s.....&.I.L.....
+{
+ "state": "Ready",
+ "door": "Closed",
+ "remaining": "3:48",
+ "power": true,
+ "lowwaterpressure": false,
+ "aquastop": false,
+ "error": false,
+ "remainingseconds": 13680
+}
```
-Which gives you the 32-byte PSK value to copy into the `hcpy` program.
-
-## SSL logging
-
-The Frida script will also dump all of the SSL traffic so that you can
-see different endpoints and things. Not much is documented yet.
-
-Note that the TX from the phone on the websocket is "masked" with an
-repeating 4-byte XOR that is sent in the first part of each messages.
-The script could be augmented to decode those as well.
-The replies from the device are not masked so they can be read in the clear.
-
-## Retrieving home appliance configuration
-
+
+Full state information
```
-frida-trace -o initHomeAppliance.log -f "com.bshg.homeconnect.android.release" -U -j '*!initHomeAppliance''
+{
+ 'BackendConnected': False,
+ 'CustomerEnergyManagerPaired': False,
+ 'CustomerServiceConnectionAllowed': False,
+ 'DoorState': 'Open',
+ 'FlexStart': 'Disabled',
+ 'LocalControlActive': False,
+ 'OperationState': 'Ready',
+ 'RemoteControlActive': True,
+ 'RemoteControlStartAllowed': False,
+ 'WiFiSignalStrength': -50,
+ 'LoadInformation': 0,
+ 'AquaStopOccured': 'Off',
+ 'CustomerServiceRequest': 'Off',
+ 'LowWaterPressure': 'Off',
+ 'ProgramFinished': 'Off',
+ 'SoftwareUpdateAvailable': 'Off',
+ 'WaterLevelTooHigh': 'Off',
+ 'DoorNotLockable': 'Off',
+ 'DoorNotUnlockable': 'Off',
+ 'DoorOpen': 'Off',
+ 'FatalErrorOccured': 'Off',
+ 'FoamDetection': 'Off',
+ 'DrumCleanReminder': 'Off',
+ 'PumpError': 'Off',
+ 'ReleaseRinseHoldPending': 'Off',
+ 'EnergyForecast': 20,
+ 'EstimatedTotalProgramTime': 13680,
+ 'FinishInRelative': 13680,
+ 'FlexFinishInRelative': 0,
+ 'ProgramProgress': 0,
+ 'RemainingProgramTime': 13680,
+ 'RemainingProgramTimeIsEstimated': True,
+ 'WaterForecast': 40,
+ 'LoadRecommendation': 10000,
+ 'ProcessPhase': 4,
+ 'ReferToProgram': 0,
+ 'LessIroning': False,
+ 'Prewash': False,
+ 'RinseHold': False,
+ 'RinsePlus': 0,
+ 'SilentWash': False,
+ 'Soak': False,
+ 'SpeedPerfect': False,
+ 'SpinSpeed': 160,
+ 'Stains': 0,
+ 'Temperature': 254,
+ 'WaterPlus': False,
+ 'AllowBackendConnection': False,
+ 'AllowEnergyManagement': False,
+ 'AllowFlexStart': False,
+ 'ChildLock': False,
+ 'Language': 'En',
+ 'PowerState': 'On',
+ 'EndSignalVolume': 'Medium',
+ 'KeySignalVolume': 'Loud',
+ 'EnableDrumCleanReminder': True,
+ 'ActiveProgram': 0,
+ 'SelectedProgram': 28718
+}
```
-
-PSK can also be found in the last section of the config as base64url encoded.
-
-```
-echo 'Dsgf2MZJ-ti85_00M1QT1HP5LgH82CaASYlMGdcuzcs"' | tr '_\-"' '/+=' | base64 -d | xxd -g1
-```
-
-The IV is also there for devices that use it. This needs better documentation.
-
-TODO: document the other frida scripts that do `sendmsg()` and `Encrypt()` / `Decrypt()` tracing
+
+### Coffee Machine
-## hcpy
-
-
-
-The `hcpy` tool can contact your device, and if the PSK is correct, it will
-register for notification of events.
-
-```
-RX: {'sID': 2354590730, 'msgID': 3734589701, 'resource': '/ei/initialValues', 'version': 2, 'action': 'POST', 'data': [{'edMsgID': 3182729968}]}
-TX: {"sID":2354590730,"msgID":3734589701,"resource":"/ei/initialValues","version":2,"action":"RESPONSE","data":[{"deviceType":"Application","deviceName":"py-hca","deviceID":"1234"}]}
-TX: {"sID":2354590730,"msgID":3182729968,"resource":"/ci/services","version":1,"action":"GET"}
-TX: {"sID":2354590730,"msgID":3182729969,"resource":"/iz/info","version":1,"action":"GET"}
-TX: {"sID":2354590730,"msgID":3182729970,"resource":"/ei/deviceReady","version":2,"action":"NOTIFY"}
-RX: {'sID': 2354590730, 'msgID': 3182729968, 'resource': '/ci/services', 'version': 1, 'action': 'RESPONSE', 'data': [{'service': 'ci', 'version': 3}, {'service': 'ei', 'version': 2}, {'service': 'iz', 'version': 1}, {'service': 'ni', 'version': 1}, {'service': 'ro', 'version': 1}]}
-RX: {'sID': 2354590730, 'msgID': 3182729969, 'resource': '/iz/info', 'version': 1, 'action': 'RESPONSE', 'data': [{'deviceID': '....', 'eNumber': 'SX65EX56CN/11', 'brand': 'SIEMENS', 'vib': 'SX65EX56CN', 'mac': '....', 'haVersion': '1.4', 'swVersion': '3.2.10.20200911163726', 'hwVersion': '2.0.0.2', 'deviceType': 'Dishwasher', 'deviceInfo': '', 'customerIndex': '11', 'serialNumber': '....', 'fdString': '0201', 'shipSki': '....'}]}
-```
-
-## Feature UID mapping
-
-There are other things that can be hooked in the application
-to get the mappings of the `uid` to actual menu settings and
-XML files of the configuration parameters.
-
-In the `xml/` directory are some of the device descriptions
-and feature maps that the app downloads from the Home Connect
-servers. Note that the XML has unadorned hex, while the
-websocket messages are in decimal.
-
-For instance, when the dishwasher door is closed and then
-re-opened, it sends the messages for `'uid':512`, which is 0x020F hex:
-
-```
-RX: {... 'data': [{'uid': 527, 'value': 1}]}
-RX: {... 'data': [{'uid': 527, 'value': 0}]}
-```
-
-In the `xml/dishwasher-description.xml` there is a `statusList`
-that says uid 0x020f is a readonly value that uses enum 0x0201:
-
-```
-
-```
-
-In the `xml/dishwasher-featuremap.xml` there is a mapping of feature
-reference UIDs to names:
-
-```
- BSH.Common.Status.DoorState
-```
-
-as well as mappings of enum ids to enum names and values:
-
-```
-
- Open
- Closed
-
-```
+To be written.
+## FRIDA tools
+Moved to [`README-frida.md`](README-frida.md)
diff --git a/hcpy b/hcpy
deleted file mode 100755
index e6cf1d6..0000000
--- a/hcpy
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python3
-# Contact a Bosh-Siemens Home Connect device
-# todo: document how to extract the psk
-import sys
-import json
-import re
-import time
-import io
-from HCSocket import HCSocket, now
-from HCDevice import HCDevice
-
-if len(sys.argv) >= 1 and sys.argv[1] == 'clothes':
- # clothes dryer
- # RX:{'service': 'ro', 'version': 1}
- # {'service': 'ei', 'version': 2}
- # {'service': 'ci', 'version': 2}
- # {'service': 'ni', 'version': 1}]}
- host = '10.1.0.145'
- psk64 = 'KlRQQyG8AkEfRFPr0v7vultz96zcal5lxj2fAc2ohaY'
- iv64 = 'tTUvqcsBldtkhHvDwE2DpQ'
-else:
- # dish washer
- # RX: {"service":"ci","version":3}
- # {"service":"ei","version":2}
- # {"service":"iz","version":1}
- # {"service":"ni","version":1}
- # {"service":"ro","version":1}]}
- host = "10.1.0.133"
- psk64 = "Dsgf2MZJ-ti85_00M1QT1HP5LgH82CaASYlMGdcuzcs="
- iv64 = None # no iv == https
-
-while True:
- try:
- ws = HCSocket(host, psk64, iv64)
- dev = HCDevice(ws)
-
- #ws.debug = True
- ws.reconnect()
-
- while True:
- msg = dev.recv()
- if msg is None:
- break
- if len(msg) > 0:
- print(now(), msg)
-
- except Exception as e:
- print("ERROR", host, e, file=sys.stderr)
-
- time.sleep(5)
diff --git a/images/clotheswasher.jpg b/images/clotheswasher.jpg
new file mode 100644
index 0000000..567bb25
Binary files /dev/null and b/images/clotheswasher.jpg differ
diff --git a/images/dishwasher.jpg b/images/dishwasher.jpg
index 13c0de3..ee3d241 100644
Binary files a/images/dishwasher.jpg and b/images/dishwasher.jpg differ
diff --git a/images/kitchen.jpg b/images/kitchen.jpg
new file mode 100644
index 0000000..13c0de3
Binary files /dev/null and b/images/kitchen.jpg differ
diff --git a/images/laptop.jpg b/images/laptop.jpg
deleted file mode 100644
index ee3d241..0000000
Binary files a/images/laptop.jpg and /dev/null differ