About
This is project aims to completely reverse engineer the cloud service for the Sure Petcare or SureFlap "Connect" internet enabled pet devices and to provide a local "cloud" for the Connect Hub to connect to rather than using their cloud service. This way the data on your pets movements never leave your network and you are in total control of all your own data.
It was born out of frustration with the cloud service being unstable over Christmas 2020/January 2021 and from a privacy perspective I prefer to not to have all my animals movements being sent off to their cloud service.
This integrates to Home Assistant using MQTT Discovery so a custom Home Assistant integration isn't required and all the entities turn up automatically if MQTT is enabled in Home Assistant. It's fortunate that the Hub also talks MQTT to AWS IoT MQTT endpoint so I have designed PetHubLocal to use a Home Assistant Mosquitto MQTT Broker as the shared broker for the hub too. The issue is the hub needs to connect to a MQTT TLS endpoint with Client Certificate Authentication enabled.
It has been through various refactors and rebuilds to where it is now. Patches are always welcome.
How it works
Below I document how the current and altered boot process works.
Standard Hub boot process
The standard flow the hub boot process is:
- Sync Time via NTP to
pool.ntp.org
HTTPS
Connect to SurePetCare Cloud service on port443
tohub.api.surehub.io
to retrieve credentials and client certificateMQTT TLS
with Client Certificate toa5kzy4c0c0226-ats.iot.us-east-1.amazonaws.com
AWS MOTT IoT endpoint, this hostname is returned from the above response.HTTP
for firmware updating the Hub to SurePetCare Cloud service on port80
tohub.api.surehub.io
Pet Hub Local Hub process
DNS Posion - Repoint hub.api.surehub.io
locally
For pethublocal to work you MUST to update your local DNS server to respond with a different IP address for:
hub.api.surehub.io
Many routers support having local DNS entries or if you are running OpenWRT it is easy, however many ISP supplied routers do not support this feature so you may need a new router to poison the DNS entry. A Mikrotik RB931-2nD is a low-cost option and should work fine, but there are many cheap routers out there that can do it and I am not going to discuss how to set it up, so if you don't feel comfortable setting it all up then this project isn't for you.
Setup - Get everything setup
Download start
and pre-cache Credentials and Firmware:
Using PetHubLocal you can login to the SurePet Browser API to retrieve https://app.api.surehub.io/api/me/start
which contains your profile and all devices including the Hub Serial Number and MAC Address and pets. Using this forms the basis of pethubconfig.json
which is the configuration file holding everything.
Using start_to_pethubconfig
function in functions.py
where it maps the start json
into the pethubconfig.json
format.
Also download https://hub.api.surehub.io/api/credentials
which is requested by the Hub on boot, and includes the Client Certificate the hub needs to use to connect to AWS. The client certificate is added to pethubconfig.json
as it is needed each time the hub boots.
Lastly download the Hub firmware from http://hub.api.surehub.io/api/firmware
which is 77 individual files the hub downloads when flashing the Hubs firmware. This is needed if you are running newer firmware than 2.43
as you need to re-flash the Hub with the 2.43
older firmware over the current firmware version. When downloading the firmware the PetHubLocal code finds the XOR key and long_serial
so soldering the console logs and watching the firmware update process to get the password aka long_serial
is no longer required to use the Client Certificate which is quite useful to have.
- Want to generate your own Client Certificate rather than using the AWS one
- Me In The Middle (not Man in the Middle) the traffic from the Hub to the legitmate cloud using PolarProxy
- Just because its useful to have and who knows it might come in handy in the future as SurePet may remove this feature in a future firmware update
Config file pethubconfig.json
This is the single configuration file everything is put into. It supports multiple hubs, stores the credentials.
{
"Config": {
"Deployment": "Setup",
"Timezone": "Local", <- Print time in UTC or Local
"Last_HA_Init": 1653171031, <- Last time Home Assistant entities were inited
"Get_State": true, <- Download state from devices on startup
"Last_Updated": "2022-05-22 10:10:32",
"Cloud": {
"LoggedIn": true, <- Has logged into SurePet Cloud
"Username": "user", <- Email address
"Password": "password", <- Password, you may want to blank this out
"device_id": "...", <- UUID Device ID generated for auth
"Token": "...", <- JWT to login to SurePet Cloud
"StartJSON": "start-yyyymmdd-hhmmss.json" <- Start json downloaded
},
"Web": {
"Host": "0.0.0.0", <- IP Address to listen to for the http and https web server
"HTTPPort": 80, <- HTTP Port for Hub Firmware update and Browser interface, must be 80 for the Hub.
"HTTPSPort": 443, <- HTTPS Port for Hub Credentials call, must be port 443 for the Hub.
"Cert": "hub.pem", <- HTTPS certificate
"CertKey": "hub.key" <- HTTPS Private Key for certificate
},
"MQTT": {
"Host": "127.0.0.1" <- MQTT Broker that PetHubLocal connects to via port 1883, also given to the Hub in credentials
},
"Firmware": {
"Cache": true, <- Cache Firmware - To be added
"Force_Download": false <- Force Download of Firmware / Credentials - To be added
}
},
"Devices": {
"H0xx-0xxxxxx": { <- Provisioned Hub Serial Number
"Hub": {
"Name": "Hub Name", <- Hub Name
"Product_Id": 1,
"Serial_Number": "H0xx-0xxxxxx",
"Mac_Address": "0000xxxxxxxxxxxx",
"Updated_At": "..",
"Index": "Hub",
"Led_Mode": 0, <- LED Option for Off, Dimmed or Bright
"Pairing_Mode": 0, <- Adoption mode to adopt new devices
"Main_Version": "204",
"State": "Online",
"Device": {
"Hardware": "3",
"Firmware": "2.43"
},
"Registers": "01..00",
"Uptime": "1020",
"UUID": "...",
"Client_Cert": "MII...A==",
"Reconnects": "1"
},
"yyyyyyyyyyyyyyyy": { <- Device MAC Address
"Name": "Device Name", <- Name
"Product_Id": [3|4|6|8], <- Device Type
"Serial_Number": "xxxx-0xxxxxx",
"Mac_Address": "yyyyyyyyyyyyyyyy", <- Device MAC Address
"Index": 2, <- Hub Provisioned Device Index Number
"Updated_At": "..",
"Send_Counter": 20, <- Counter for sending messages to Device for Cat Flap, Feeder and Poseidon
"Receive_Counter": 0, <- Recieve Counter
"Last_Device_Update": xxx, <- Last time device was updated
"Fast_Polling": false,
"Main_Version": "xx",
"Battery": "x.xxx", <- Battery value
"BatteryADC": 0, <- Battery value if returning an ADC value
"Last_Heard": "-453479495",
}
}
},
"Pets": {
"ffffffffff": { <- HDX Tag
"Name": "HDX Tag Name",
"Species": 0,
"Activity": {
"Where": "Inside",
"Time": ".."
}
},
"ccc.nnnnnnnnnnnn": { <- FDX-B Tag
"Name": "FDX-B Tag Name",
"Species": 1, <- Species - Cat = 1, Dog = 2
"Activity": {
"Where": "Inside",
"Time": ".."
},
"Feeding": {
"Change": [
"-0.23", <- Last feed left weight
"-0.05" <- Last feed right weight
],
"Time": "."
},
"Drinking": {
"Change": [
-2.51 <- Last drink weight
],
"Time": "2021-12-29T16:07:02+00:00"
}
}
}
}
There are more fields in the above pethubconfig.json
but it should make sense.
Start - Run PetHubLocal
Once you have a pethubconfig.json
with everything in it and the MQTT Broker is configured to listen on 8883
you are ready to start.
Using Home Assistant MQTT Discovery a number of MQTT Topics are published to with a JSON payload to configure Home Assistant.
Using ha_init_entities
function in functions.py
to create all the Home Assistant entities with topics under homeassistant/[switch|sensor]/pethub/H0xx../config
The following topics are published to with the MQTT retain flag set to true so they persist in Home Assistant after restarts depending on the device type:
Hub
homeassistant/sensor/pethub/H0xx-0xxxxxx_Hub/config
- Where xxx is the Hub Serial Number and will hold all Hub related state information
All connected devices but not the Hub
Device MAC Address = yyyyyyyyyyyyyyyy
homeassistant/sensor/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/config
- Main Device state informationhomeassistant/sensor/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy_battery/config
- Device battery state
Pet Door and Cat Flap
The doors have the Keep In / Out / Curfews as switches to toggle them in HA they need to be added as switches not sensors.
homeassistant/switch/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy_keepin/config
- Keep In Switch and Statehomeassistant/switch/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy_keepout/config
- Keep Out Switch and Statehomeassistant/switch/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy_curfew/config
- Curfews Enabled Switch and State
Feeder
If the feeder has two bowls
homeassistant/sensor/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy_left_weight/config
- Left Scale weighthomeassistant/sensor/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy_right_weight/config
- Right Scale weight
If the feeder has a single bowl
homeassistant/sensor/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy_weight/config
- Scale weight
Poseidon aka Felaqua
homeassistant/sensor/pethub/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy_weight/config
- Scale weight
Pets
homeassistant/sensor/pethub/hhhhhhhhhh/config
- HDX Pet Tag which is a 5 byte hex value in the formathhhhhhhhhh
. The "collar tags" included by SurePet in the Pet Door boxes are HDX.homeassistant/sensor/pethub/ccc-nnnnnnnnnnnn/config
- FDX-B Pet Tag which is a 3 and 12 digit value separated with a.
in the formatccc.nnnnnnnnnnnn
which is the modern injected microchips used in animals with 3 digits for the Country Code, and 12 digits for the National Code which is unique per microchip.
Home Assistant MQTT Discovery Payload
Within each of the above /config
topics a retained json message is published in the Home Assistant MQTT Discovery format, it changes slightly between the device types or pets as I set the Icon according to the device type and for switches , but you get the idea.
{
"name": "Device Name", <- Device or Pet name
"ic": "mdi:door", <- Device or Pet Icon
"uniq_id": "H0xx-0xxxxxx_yyyyyyyyyyyyyyyy", <- Home Assistant Unique ID
"stat_t": "pethub/ha/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/state", <- State Topic, this is single topic holding the state
"val_tpl": "", <- The JSON element in the above topic for this sensor aka State
"json_attr_t": "pethub/ha/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/state", <- The "Attributes" dropdown on the entity
"cmd_t": "pethub/ha/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/KeepIn", <- Only for doors with HA switch entities for HA to switch the KeepIn/Out/Curfew state
"avty_t": "pethub/ha/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/state", <- Availability Topic for online/offline state, only added to devices not pets
"avty_tpl": "", <- Availability Template using the JSON element Availability, only for devices
"device": {
"ids": "yyyyyyyyyyyyyyyy", <- The below groups all entities as a single device in Configuration
"name": "Device Name", <- -> Settings -> MQTT so they all appear as one panel as well
"sw": 2,
"mdl": "Device Type",
"mf": "Pet Hub Local"
}
}
Pet Hub States
Now all the entities are created in Home Assistant they are expecting a pethub
specific device entity Topic. This is a single json topic that holds the whole state of the device. These are also set to retain so that the entities are persisted and their state is known after restarts of MQTT or Home Assistant.
pethub/ha/H0xx-0xxxxxx_Hub/state
- The Hub Statepethub/ha/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/state
- The Device Statepethub/ha/hhhhhhhhhh/state
- HDX Pet Tag statepethub/ha/ccc-nnnnnnnnnnnn/state
- FDX-B Pet Tag state
For door devices the switches are created so the Command Topics cmd_t
to ON/OFF
when you change the state of the switch in Home Assistant.
pethub/ha/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/KeepIn
- Change the Door KeepIn Statepethub/ha/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/KeepOut
- As above, but for KeepOutpethub/ha/H0xx-0xxxxxx_yyyyyyyyyyyyyyyy/Curfew
- Same again but for Curfews
Pet Hub State Payload
Within each of the above /config
topics a retained json message is published in the Home Assistant MQTT Discovery format, it changes slightly between the device types or pets as I set the Icon according to the device type and for switches , but you get the idea.
Hubs
Fairly obvious values specific to the hub, that is also displayed under Attributes
drop down on the entity due to json_attr_t
{
"Availability": "online",
"State": "Online",
"Uptime": "xxx Mins",
"Name": "Hub Name",
"Reconnects": "x",
"Serial": "H0xx-0xxxxxx",
"MAC Address": "0000111111111111",
}
Devices
Device specific json where some of the below values are added only on if the device is a door or feeder.
{
"Availability": "online", <- Availability topic if the device goes offline
"State": "Closed / KeepIn", <- State shown on the main device itself, for Feeders it's Open/Closed, for Doors it's Unlocked/KeepIn/KeepOut etc
"Online": "Online", <- For Poseidon showing if it is online for the device as it doesn't have a state
"Battery": "x.xxx", <- Battery voltage level
"KeepIn": "OFF", <- Current Door KeepIn State, this changes when the KeepIn is enabled showing the switch on / off
"KeepOut": "OFF", <- Current Door KeepOut State, same as above but KeepOut
"Curfew": "OFF", <- Current Door Curfew State, same as above but Curfew
"Curfews": "07:00-08:45", <- The Curfew lock and unlock values if set for the door
"Bowl Count": 2, <- Feeder bowl count
"Close Delay": "FAST", <- Feeder close delay
"Left Target": "50", <- Feeder left target weight showing where the LED indicate goes to
"Right Target": "55", <- Feeder right target weight showing where the LED indicate goes to
"Left Weight": "4.6", <- Feeder left current reported weight
"Right Weight": "3" <- Feeder right current reported weight
}
Pets
As above only if the pet is assigned to a Door, Feeder or Poseidon
{
"State": "Inside", <- State where the Animal is if they came inside, went outside or looked in either direction
"Left Weight": "-0.23", <- Last feed from left bowl
"Right Weight": "-0.05", <- Last feed from right bowl
"Drinking": "-2.51" <- Last drink from Poseidon
}
Web Frontend
The frontend is a basic HTML page with SocketIO to support publishing every event to the site as well as planned end-user features to support adopting new devices, changing Curfew times for doors etc. Still a work in progress as I was working on the core code first.