Local Deployment for SureFlap / SurePetCare Connect using only local MQTT Broker

There could be the option to rebuild the Zigbee / Miwi but the protocol wasn’t as easy as it looks and MITMing the hub traffic was a lot easier along with doing the firmware update with a serial adapter connected.

I’m still slowly plugging away with a complete rewrite which is coming along slowly. But I do recommend spending the time to get the certificate out.

Sorted out the documentation for getting the certificate: Hub Certificate | Pet Hub Local

Apologies for the confusing documentation talking about which pins to solder on for the console, cleaned that up and then ran into weirdness with GitHub Pages.

1 Like

Hello I am trying to configure my door.
I am launching the docker.
I start pethub with the firmware download button pressed.
And I get these errors.
Please help me to solve the problem.

web       |  * Running on https://172.19.0.2:443/ (Press CTRL+C to quit)
pethub    | Traceback (most recent call last):
pethub    |   File "/usr/local/lib/python3.8/site-packages/box/box.py", line 514, in __getattr__
pethub    |     value = self.__getitem__(item, _ignore_default=True)
pethub    |   File "/usr/local/lib/python3.8/site-packages/box/box.py", line 509, in __getitem__
pethub    |     raise BoxKeyError(str(err)) from _exception_cause(err)
pethub    | box.exceptions.BoxKeyError: "'devices'"
pethub    |
pethub    | During handling of the above exception, another exception occurred:
pethub    |
pethub    | Traceback (most recent call last):
pethub    |   File "/usr/local/lib/python3.8/site-packages/box/box.py", line 516, in __getattr__
pethub    |     value = object.__getattribute__(self, item)
pethub    | AttributeError: 'Box' object has no attribute 'devices'
pethub    |
pethub    | The above exception was the direct cause of the following exception:
pethub    |
pethub    | Traceback (most recent call last):
pethub    |   File "pethubmqtt.py", line 403, in <module>
pethub    |     for device in pethubinit.devices:
pethub    |   File "/usr/local/lib/python3.8/site-packages/box/box.py", line 530, in __getattr__
pethub    |     raise BoxKeyError(str(err)) from _exception_cause(err)
pethub    | box.exceptions.BoxKeyError: "'Box' object has no attribute 'devices'"
pethub exited with code 1
web       | Post payload : {"serial_number": "H009-XXXXXX", "page": "0", "bootloader_version": "1.177"}
web       | app.py:134: DeprecationWarning: The 'cache_timeout' parameter has been renamed to 'max_age'. The old name will be removed in Flask 2.1.
web       |   response = make_response(send_file(filename,mimetype='text/html',add_etags=False,cache_timeout=-1,last_modified=None))
web       | app.py:134: DeprecationWarning: The 'add_etags' parameter has been renamed to 'etag'. The old name will be removed in Flask 2.1.
web       |   response = make_response(send_file(filename,mimetype='text/html',add_etags=False,cache_timeout=-1,last_modified=None))
web       | [2022-02-21 23:22:00,563] ERROR in app: Exception on /api/firmware [POST]
web       | Traceback (most recent call last):
web       |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2073, in wsgi_app
web       |     response = self.full_dispatch_request()
web       |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1518, in full_dispatch_request
web       |     rv = self.handle_user_exception(e)
web       |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1516, in full_dispatch_request
web       |     rv = self.dispatch_request()
web       |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1502, in dispatch_request
web       |     return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
web       |   File "app.py", line 134, in firmware
web       |     response = make_response(send_file(filename,mimetype='text/html',add_etags=False,cache_timeout=-1,last_modified=None))
web       |   File "/usr/local/lib/python3.8/site-packages/flask/helpers.py", line 612, in send_file
web       |     return werkzeug.utils.send_file(
web       |   File "/usr/local/lib/python3.8/site-packages/werkzeug/utils.py", line 701, in send_file
web       |     stat = os.stat(path)
web       | FileNotFoundError: [Errno 2] No such file or directory: '/web/creds/H009-0XXXXXX-1.177-00.bin'
web       | 198.11.12.96 - - [21/Feb/2022 23:22:00] "POST /api/firmware HTTP/1.1" 500 -

@peterl Did you still need anything from a virgin cat flap? I’m about to unbox one and wanted to find out before adding it to my setup.

Also, any updates on your attempt to rearchitect everything? I’d like to try to dive back into this project again.

Hi Everyone

It’s been a heck of a last 8 months for me, but the code has been completely refactored and is back into a working state.

Created a separate GitHub Org to hold the code rather than it sitting under my account.

Code: GitHub - PetHubLocal/pethublocal: Replacement for SurePetCare "Connect" cloud service connecting via MQTT to Home Assistant

Documentation: Setup PetHubLocal | Pet Hub Local

So basically everything has changed:

  • No longer using SQLite for the database and it’s just a big JSON blob with everything in there
  • Built it all into a single python command line for the HTTP, HTTPS and MQTT Client, so now you can run it fairly easily outside docker should you want to do that.
  • Down to two commands now setup and start where setup checks the local DNS has been poisoned, logs into SurePetCare and downloads the Start JSON, optionally but highly recommended simulates the Hub and downloads the Credentials and Firmware then writes everything out to the pethubconfig.json file.
  • Work in progress web front end I plan to add a lot more functionality to.

Have a look, see if it works for you and how it goes.

2 Likes

I should have some time today to test. Thanks!!!

1 Like

@peterl Is there support for MQTT authentication? My HA MQTT needs id/pass.

Yes @flyize I had added Hub authentication, but not the PetHubLocal, so MQTT Auth is now in there.
In the pethubconfig.json you need:

        "MQTT": {
            "Host": "192.168.100.10",
            "ClientUsername": "client",
            "ClientPassword": "clientpassword",
            "HubUsername": "hub",
            "HubPassword": "hubpassword"
        },

And then both the Hub via TLS on port 8883 and the PetHubLocal Client on 1883 will be able to authenticate to the MQTT Broker.

Is Hub username/password just the cloud creds?

The hub needs to talk MQTT over TLS , and there are two fields in the credentials response that I thought were username and password as they are blank in the payload response. Weirdly thought that I had managed to make the Hub use a Username/Password to login to a MQTT Broker by setting those fields, but when I tried to set them overnight the hub failed to boot with the error:

--- Processing Response: 3575 Bytes Total
0 bytes remain, after 447 reads....     | Tstate 5 |    TCP_RESPONSE_COMPLETE
--- Processing Done

~~~~~~~ Credentials Retrieval Failed ~~~~~~~
Closing Socket

So there doesn’t seem to be a way to make the hub use Username/Password credentials on top of the Client Certificate it passes.
So I don’t think I can make the hub talk to HiveMQ or similar cloud based MQTT Broker using a Username and Password. Which is very unfortunate.

Thank you very much @peterl, for your work on this. It is exactly what I’m looking for!

I’ve had a play with this today and I’ve hit a couple of problems.

Firstly, I’m a little confused by the setup instructions. I’ve created the docker on my unraid server and it seems to be working ok. I can run the setup script and download the start file, credentials, firmware, etc.
For the ip of the MQTT broker I enter the ip of my Home Assistant Blue where mosquitto is running as a plugin and it connects and adds items to autodiscovery just fine.
In the setup, step 4 talks about providing the broker with the certificates/keys so the hub can connect. Is this the same broker? Do the docker and the hub talk to the same broker, just on different ports with different authentication methods. Or do I need a seconds broker?
If it’s just the one, does anyone know how to go about adding the files to the HA addon?

edit: just re-read your post from yesterday and yes, it’s the same broker. I need to have a play with the HA plugin or spin up a mosquitto docker to act as an intermediary.

Second, this could well be my paranoia but thought I’d put it out there.
It is very possible that something happened to cause to my IP address to get a temporary ban from either the Amazon servers or from hub.api.surehub.io. I set this up last night, made the host change on my router and rebooted the hub to get it to reconnect. It didn’t work so I reversed the last step and rebooted the hub again. All I got were the ears flashing alternately red. Left it for a while but no joy. Restepped through everything I’d done looking for things that could be causing it to not connect but didn’t find anything. I tried the hub on a different vlan with fewer firewall rules which didn’t help.

I went to bed and left it unplugged overnight but it had the same behaviour in the morning. This afternoon I tried connecting it via a cellular modem I have and it connected straight away! Back to my main network to set up a vlan which sent everything through a VPN connection and again, it worked when connected via this. Moved it back to the previous vlan to test my conclusion and… it still worked :confused: It’s still plugged into the un-VPNed connection and is working just fine so I either got a 12-24 ip ban or I managed to change something that broke it and then changed it back without realising.

Hey @eviltom
Many of the questions you are looking for are on About | Pet Hub Local

That is the flow how it all plugs together, yes the Hub, Home Assistant and the “PetHubLocal” app all talk to the same broker. The hub only talks MQTT over TLS to port 8883 so that is why I include the additional files in Step 4 to be copied from pethublocal/mqtt at main · PetHubLocal/pethublocal · GitHub so you would need to do the same on the Home Assistant Blue. I am not sure if the MQTT Broker on there runs in a container or on the main host. But I would assume it is on the main host, so you will need to SSH into it and configure it. So YMMV.

I am not sure of the temporary ban thing, but I have personally had all sorts of weird issues with the SurePet cloud infrastructure especially when they change backend IP’s for their infrastructure. It’s AWS hosted and every month or so they seem to re-provision their EC instances on a semi-frequent basis, and the AWS IoT MQTT endpoints seems to jump around and the DNS doesn’t seem to age out so the hub boots, tries to connect to the hostname but gets a RST reset back, then the IPs all change around the next day and happy days.
Try flushing your DNS on your router, do a dig hub.api.surehub.io vs dig hub.api.surehub.io @8.8.8.8 and see if you get wildly different responses back… as I suspect you will.

FWIW, AWS Elastic Load Balancer don’t come with static IPs by default. That’s probably why you see the IPs change regularly.

@peterl I’ve read through all the documentation now (got excited and skipped bits!) and I think I’ve now got my head around it all. Thank you for the comprehensive docs.

Probably not an IP ban then, just a coincidence with the timing.

I’ve had a bash at the MQTT broker in HA, and it reports it’s now listening on 8883. Not sure if the certificates are in the correct place but I’ll cross that bridge when I come to it.

So, I have made the host change on the router, and everything on my network reports that hub.api.surehub.io is now 10.10.30.152, which is the correct ip for the docker container.
I cleared out all the created config files in the docker and ran setup afresh. The setup reports the url is correctly routed to the IP address, successfully connects, downloads the config and creates the pethubconfig.json. So far, so good.
I can restart the docker and watch the logs as it starts the server and creates the home assistant MQTT entries.
It then seems to encounter an error and stops:

ERROR:asyncio:Task exception was never retrieved

future: <Task finished name='Task-2' coro=<mqtt_start() done, defined at /usr/local/lib/python3.9/site-packages/pethublocal/frontend.py:187> exception=AttributeError("'str' object has no attribute 'Bowl_Target'")>
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/pethublocal/frontend.py", line 218, in mqtt_start
ha_init_state = ha_update_state(app['pethubconfig'])
File "/usr/local/lib/python3.9/site-packages/pethublocal/functions.py", line 857, in ha_update_state
'Target': str(device.Bowl_Target[0]),
AttributeError: 'str' object has no attribute 'Bowl_Target'

After that It just sits there. I’m assuming this isn’t a fatal error because if I browse to the IP address of the server I can see the UI, and I can see the http requests in the logs.

I have tried rebooting the hub to get it to attempt to connect to pethublocal. Unfortunately can’t seem to manage it and I get this over serial:

Socket Timed Out !

Closing Socket

 | Talking to: hub.api.surehub.io | Socket Obtained | Socket Opened | ERROR: wolfSSL_connect failed! Error: -188

Socket Opened | ERROR: wolfSSL_connect failed! Error: -373

Socket Opened | Closing Socket

Followed by loads of

Timeout in webState 1

and finally

### AWS Timed Out, at 37500013 seconds. ###

Followed by a stack dump and the watchdog rebooting the hub. Repeat ad infinitum.
I appears from google that it’s not happy with the certificate: -188 = “ASN sig error, no CA signer to verify certificate”

Any ideas? Am I missing something?
There’s not further activity in the server log while all that is going on, but it’s still logging http request so it’s not dead.

Thanks again for your help, and sorry for the wall of text!

Isnt there a way to write custom firmware after which the whole dns poison isnt needed anymore and the hub becomes local without all this? I have been planning on opening the pet door and put an esp zigbee module in it when it becomes available to control the door itself locally without a hub. I was looking for an easier way to do so. But when reading this. im not sure i want to do all this on my home assistant server. which has grown to over 1000 entities and 100s of devices connected to it… Im to afraid things will go wrong. It would have been awsome if someone found a way to just flash some custom firmware on this thing. Does anyone know if people are trying or allready succeeded in doing this?

Check @peterl’s docs. I’m pretty sure he’s done some cursory work to get the stuff to talk to your own hub.

The Bowl_Target is built from your start.json I must have assumed you set a target when you haven’t and not create it in the pethubconfig.json.
What does your start have under the control section for your feeder? Mine is:

            "control": {
                "bowls": {
                    "settings": [
                        {
                            "food_type": 2,
                            "target": 50
                        },
                        {
                            "food_type": 1,
                            "target": 55
                        }
                    ],
                    "type": 4

Which then in my pethubconfig.json becomes:

                "Bowl_Count": 2,
                "Bowl_Target": [
                    50,
                    55
                ],

Put the above into your pethubconfig under your feeder and restart it… and if you could email me your start.json I can see what I missed in the logic. The old “assumptions are the cause of all screwups”… otherwise if you set target weights for your bowls in the surepet app, then re-run the whole process that should fix it too.

The second error I think is because you have something else listening on port 443 on your host. The hub needs to connect to port 443 and there isn’t anything I can do to change that, not much I can do about that, so you need to stop pethublocal, then try with postman or curl to your host as something else will be listening on 443 that will need to get stopped / removed.
My personal experience is running PetHubLocal on a dedicated Raspberry Pi 3+ (Or Odroid C2) or similar SBC is just so much easier as port 443 is always used.

The firmware has 77 ‘pages’ or unique files and each page is XORed with a per device firmware key based on your long_serial aka certificate password key and a two byte CRC16 is calculated on the decrypted file.
The issue I have is yes technically I could byte replace the string with another value and then flash that on, but if I screwed it up I would need a pickit to flash any replacement firmware on and if it went wrong I would have a brick of a hub. My wife is already grumpy with time spent, would be more grumpy if I had to buy more replacement hardware after blowing up the hub.
The CRC16 isn’t calculated how I expected as a standard binary CRC16 and haven’t wanted to spend the time digging through the code to see how it is calculated as I haven’t done much with assembly reverse engineering.
If you do have experience and time I have a Ghidra project of the firmware you are welcome to as there are a few calculations I would love to get to the bottom of but don’t have the experience to find what I am looking for.

1 Like

Ah ha! I have:

            "control": {
                "bowls": {
                    "settings": [
                        {
                            "food_type": 2,
                            "target": 75
                        }
                    ],
                    "type": 1

You have the choice of using either the two half bowls with their own separate food types and targets or one big one that sits across both load cells. We use just the one large bowl. So we have a target set, but only one target that covers both, resulting in just one entry in that array.

                "Bowl_Count": 1,
                "Bowl_Target": [
                    75
                ],
                "Bowl_Weight": [
                    0
                ],
                "Bowl_Delta": [
                    0
                ],

I changed that to:

                "Bowl_Count": 2,
                "Bowl_Target": [
                    75,
                    0
                ],
                "Bowl_Weight": [
                    0,
                    0
                ],
                "Bowl_Delta": [
                    0,
                    0
                ],

And it starts properly. It’s now created the ‘pethub’ topic on the broker which it didn’t before, just the HA auto discovery bits.

Re the port; I’ll go have a play with it again and double check the ports, but I doubt that’s the problem. This docker container has it’s own ip address on the network which was assigned by DHCP. I will double check though.
I’ll also email the file across.
Thanks again.