This is a very, very beta interface for Bosch-Siemens Home Connect devices through their local network connection. Unlike most IoT devices that have a reputation 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.
WARNING: This tool not ready for prime time and is still beta!
Base DerSep/hcpy-ui
- Home Assistant Ingress Web UI for login and setup
- Add-on startup tuned for Supervisor (MQTT auto-discovery + fallback)
- AppArmor profile enabled and validated
Fork changes [Asmir1975/HCPY-ui]
- Login fixed: the retired
homeconnectegw.com/account/detailsendpoint (now HTTP 404) replaced with the current Home Connect API (paired-appliances- per-appliance
encryption-information), in both the Web UI andhc-login.py; region hosts EU/NA/CN.
- per-appliance
- Hob programs collected into the program select instead of many individual sensor entities (backport of hcpy2-0/hcpy#260).
- Less MQTT traffic: only changed values are republished instead of the full device state on every message - easier on the Home Assistant recorder (backport of hcpy2-0/hcpy#236).
- Cleaner hob by default: static hob zone metadata is skipped in the bundled
discovery.yaml(backport of hcpy2-0/hcpy#262). - Ambient light fix: the effect state topic uses the device name instead of a
hardcoded
hood(hcpy2-0/hcpy#273). - ProgramPhase reset: appliances no longer show a stale phase (e.g. "Drying") after a program finishes or is aborted (backport of hcpy2-0/hcpy#263).
- Watchdog simplified: the 5-minute force-reconnect on message silence is removed;
dead sockets are detected by the WebSocket ping/pong keepalive. The
/watchdogheartbeat topic is kept. - Login and bridge robustness: a device with a failed description download is no longer
saved to
devices.json, an unknown feature UID no longer aborts the login, and the MQTT bridge skips a device without features instead of crashing. - Small fixes and cleanup: dead "update devices" button removed, token-save errors logged, dead per-device ZIP write removed.
- Bridge and watchdog hardening: the addon now stops (letting Supervisor restart it) if the MQTT bridge dies unexpectedly, and a broker disconnect mid-update no longer causes a value to be silently skipped.
- Web UI redesign: a darker, guided setup page with a 1-2-3 login wizard, a connection-state plug indicator, and the addon log in a collapsible section. Same login flow and endpoints, only the look changed.
- Small fixes and cleanup: login cloud requests now time out instead of hanging, unused
bs4/lxmldependencies removed, config and test docs corrected. - Small fixes (0.5.21): clearer Web UI status wording, log endpoint bounds, completed bridge guard for hand-edited device entries, documentation corrections.
- Built on the upstream hcpy2-0/hcpy project by @Meatballs1 and contributors. Thank you.
- Login workflow based on bruestel/homeconnect-profile-downloader.
Follow the instructions in the wiki
To avoid running into issues later with your default python installs, it's recommended to use a py virtual env for doing this. Go to your desired test directory, and:
python3 -m venv venv
source venv/bin/activate
git clone https://github.com/Asmir1975/HCPY-ui
cd HCPY-ui
pip3 install -r requirements.txtInstall the Python dependencies; the sslpsk one is a little weird
and we might need to revisit it later.
Alternatively an environment can be built with docker and/or docker-compose which has the necessary dependencies.
Installing sslpsk needs some extra steps:
- The openssl package installed via brew:
brew install openssl, and - Install
sslpskseparately with flags:LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip3 install sslpsk - Rest of the requirements should be fine with
pip3 install -r requirements.txt
hcpy needs a one-time login to the Home Connect cloud to download each
appliance's local keys and feature description into devices.json. In this fork
that runs through the Home Assistant Web UI (Ingress) - no SSH, no manual
code/state copying.
-
Open the addon's Home Connect Setup Web UI and click Login-Seite öffnen.
-
Sign in with your Home Connect account (email + password). There is no CAPTCHA.
-
After signing in, the browser tries to open a
hcauth://auth/prod?...URL and fails ("no registered handler"). The full URL is shown in the browser Console (F12) as a Failed to launch 'hcauth://…' line. Click that link - a new tab opens whose address bar holds the complete redirect URL. -
Copy that complete
hcauth://auth/prod?code=…URL (it must containcode=), paste it into the Web UI field and click Geräte konfigurieren. -
hcpy exchanges the code for a token, pulls all paired appliances and writes
devices.jsonautomatically.
This only needs to be done once, or when you add a new appliance.
Device names: keep them plain ASCII (
a-z, no umlauts, accents or spaces) the name is used directly as the MQTT topic and Home Assistant entity ID.
Command-line alternative (local / headless)
hc-login.py runs the same OAuth flow on the command line:
hc-login.py config/devices.jsonIt prints the login URL and prompts for the code and state from the same
hcauth://… redirect (read them from the F12 Console line above). The token is then
exchanged for the device list, keys and XML feature descriptions.
Use the following ./config/config.ini example:
devices_file = "./config/devices.json"
mqtt_host = "localhost"
mqtt_username = "mqtt"
mqtt_password = "password"
mqtt_port = 1883
mqtt_prefix = "homeconnect/"
mqtt_ssl = False
mqtt_cafile = None
mqtt_certfile = None
mqtt_keyfile = None
mqtt_clientname="hcpy"
ha_discovery = True # See section on "Home Assistant autodiscovery"
hc2mqtt.py --config config/config.iniThis 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.
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:
{
"state": "Run",
"door": "Closed",
"remaining": "2:49",
"power": true,
"lowwaterpressure": false,
"aquastop": false,
"error": false,
"remainingseconds": 10140
}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
}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:
{
"state": "Ready",
"door": "Closed",
"remaining": "3:48",
"power": true,
"lowwaterpressure": false,
"aquastop": false,
"error": false,
"remainingseconds": 13680
}Full state information
{
'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
}Example message published to homeconnect/coffeemaker:
Full state information
{
'LastSelectedBeverage': 8217,
'LocalControlActive': False,
'PowerSupplyError': 'Off',
'DripTrayNotInserted': 'Off',
'DripTrayFull': 'Off',
'WaterFilterShouldBeChanged': 'Off',
'WaterTankEmpty': 'Off',
'WaterTankNearlyEmpty': 'Off',
'BrewingUnitIsMissing': 'Off',
'SelectedProgram': 0,
'MacchiatoPause': '5Sec',
'ActiveProgram': 0,
'BeverageCountdownWaterfilter': 48,
'BeverageCountdownCalcNClean': 153,
'RemoteControlStartAllowed': True,
'EmptyDripTray': 'Off',
'BeverageCountdownDescaling': 153,
'EmptyDripTrayRemoveContainer': 'Off',
'BeverageCounterRistrettoEspresso': 177,
'AllowBackendConnection': True,
'BeverageCounterHotWater': 37351,
'RemindForMilkAfter': 'Off',
'BeverageCounterFrothyMilk': 22,
'BeverageCounterCoffeeAndMilk': 1077,
'CustomerServiceRequest': 'Off',
'4645': 0,
'CoffeeMilkOrder': 'FirstCoffee',
'BackendConnected': True,
'BeverageCounterCoffee': 21,
'Enjoy': 'Off',
'UserMode': 'Barista',
'PlaceEmptyGlassUnderOutlet': 'Off',
'WaterTankNotInserted': 'Off',
'PlaylistRunning': False,
'BeverageCounterPowderCoffee': 9,
'DemoModeActive': False,
'CleanBrewingUnit': 'Off',
'WaterHardness': 'Medium',
'CloseDoor': 'Off',
'EmptyMilkTank': 'Off',
'SpecialRinsing': 'Off',
'AllowConsumerInsights': False,
'SwitchOffAfter': '01Hours15Minutes',
'4681': 0,
'LastSelectedCoffeeWorldBeverage': 20514,
'BrightnessDisplay': 7,
'CleanMilkTank': 'Off',
'NotEnoughWaterForThisKindOfBeverage': 'Off',
'ChildLock': False,
'4666': 0,
'Language': 'De',
'MilkContainerConnected': 'Off',
'SoftwareUpdateAvailable': 'Off',
'LeaveProfilesAutomatically': True,
'RemoveWaterFilter': 'Off',
'OperationState': 'Inactive',
'BeverageCounterHotMilk': 9,
'4362': 0,
'MilkTubeRemoved': 'Off',
'DeviceIsToCold4C': 'Off',
'SystemHasRunDry': 'Off',
'DeviceShouldBeDescaled': 'Off',
'PowerState': 'Standby',
'DeviceShouldBeCleaned': 'Off',
'DeviceShouldBeCalcNCleaned': 'Off',
'BeanContainerEmpty': 'Off',
'MilkStillOK': 'Off',
'CoffeeOutletMissing': 'Off',
'MilkReminder': 'Off',
'RefillEmptyWaterTank': 'Off',
'RefillEmptyBeanContainer': 'Off',
'UnderOverVoltage': 'Off',
'NotEnoughPomaceCapacityForThisKindOfBeverage': 'Off',
'AdjustGrindSetting': 'Off',
'InsertWaterFilter': 'Off',
'FillDescaler': 'Off',
'CleanFillWaterTank': 'Off',
'PlaceContainerUnderOutlet': 'Off',
'SwitchOffPower30sekBackOn': 'Off',
'ThrowCleaningDiscInTheDrawer': 'Off',
'RemoveMilkContainer': 'Off',
'RemoveContainerUnderOutlet': 'Off',
'MilkContainerRemoved': 'Off',
'ServiceProgramFinished': 'Off',
'DeviceDescalingOverdue': 'Off',
'DeviceDescalingBlockage': 'Off',
'CustomerServiceConnectionAllowed': False,
'BeverageCountdownCleaning': 38,
'ProcessPhase': 'None'
}Whereas the reading of the status is very beta, this is very very alpha. There is some basic error handling, but don't expect that everything will work.
In your config file you can find items that contain readWrite or writeOnly, some of them contain values so you know what to provide, ie:
"539": {
"name": "BSH.Common.Setting.PowerState",
"access": "readWrite",
"available": "true",
"refCID": "03",
"refDID": "80",
"values": {
"2": "On",
"3": "Standby"
}
},With this information you can build the JSON object you can send over mqtt to change the power state
Topic: homeconnect/[devicename]/set, ie homeconnect/coffeemaker/set
Payload:
{"uid":539,"value":2}As for now, the results will be displayed by the script only, there is no response to an mqtt topic.
There are properties that do not require predefined values, debugging is required to see what is needed. Here are some of those values found through debugging:
Set the time:
{"uid":520,"value":"2023-07-07T15:01:21"}Synchronize with time server, false is disabled
{"uid":547,"value":false}The MQTT client listens on {prefix}/{devicename}/activeProgram for a JSON message to start a program. The JSON should be in the following format:
{"program":{uid},"options":[{"uid":{uid},"value":{value}}]}To start a dishwasher on eco mode (Dishcare.Dishwasher.Program.Eco50):
{"program":8196}To start a dishwasher on eco mode in 10 miuntes (BSH.Common.Option.StartInRelative):
{"program":8196,"options":[{"uid":558,"value":600}]}An example test from HomeAssistant Developer Tools -> Actions would be:
action: mqtt.publish
data:
topic: "homeconnect/hood/activeProgram"
payload: '{"program":55307,"options":[{"uid":55308,"value":1}]}'The program value can also take the display name of the program (e.g. "Dishcare.Dishwasher.Program.Eco50") instead of a numeric UID, but this processing is not currently on the options.
- Sometimes when the device is off, there is the error
ERROR [ip] [Errno 113] No route to host ERROR [ip] [Errno 113] No route to hostcould also happen if you connect the device to an ssid which is isolated from the internal network. Make sure you connect devices to the "same" network.- There is a lot more information available, like the status of a program that is currently active. This needs to be integrated if possible. For now only the values that relate to the
config.jsonare published
It's possible to allow Home Assistant to automatically discover the device using
MQTT auto-discovery messages. With ha_discovery = True in config.ini or by
passing --ha-discovery on the commandline, hcpy will publish
HA discovery messages
for each recognised property of your devices.







