Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for energy sensor on S31 #4

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

boojew
Copy link

@boojew boojew commented Feb 3, 2019

@boojew
Copy link
Author

boojew commented Feb 4, 2019

Hmm. Found a bug in which the switch doesn’t always respond to MQTT commands. I’ll fix this during the week

@boojew
Copy link
Author

boojew commented Feb 6, 2019

Ok so I checked closer and my PR didnt cause instability with Homemate.. My test box did ;) It's all good now. One thing to note is that I rarely see energy update events on the "wire"; however, when the homemate app is open, it seems to send one every few seconds .- so my guess is that there is a cmd to send to tell it to send them; however, I can't get the stream_decode.py to work. I have the 16 character key, but I always get the complaint about the keysize.. any feedback?

bens-MBP:research babecassis$ python3 stream_decode.py mobile 
Switch sends
Magic: b'hd'
Length: 186 (True)
Type: b'pk'
CRC32: (2406255143, 2406255143, True)
id: b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Traceback (most recent call last):
  File "stream_decode.py", line 70, in <module>
    print(crypto(v))
  File "stream_decode.py", line 34, in crypto
    algorithms.AES(keys[payload[4]]),
  File "/usr/local/lib/python3.7/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.py", line 30, in __init__
    self.key = _verify_key_size(self, key)
  File "/usr/local/lib/python3.7/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.py", line 17, in _verify_key_size
    len(key) * 8, algorithm.name
ValueError: Invalid key size (136) for AES.

@insertjokehere
Copy link
Owner

At a guess, you've either got a trailing new line character at the end of the orvibo.key file, or you've written the base64 encoded version rather than the raw bytes.

Try something like: echo -n kh..... > orvibo.key

Copy link
Owner

@insertjokehere insertjokehere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some notes; generally, good job understanding my code! I know its a bit of a mess

# Close the connection if the switch doesn't send anything in 30 minutes
# See !1
self.request.settimeout(60 * 30)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this block?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish I had a good reason.. Adding it back in.

if 'name' not in self.settings:
self.settings['name'] = "Homemate Switch " + localip

logger.debug("Device settings: {}".format(self.settings))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove? This is important for my use-case; as I run my bridge in a Kubernetes cluster, so the connections to the bridge are NATed, and appear to all come from the same IP. I use the IP that the device itself reports as an ID so I can tell them apart

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah.

def format_response(self, packet, response_payload):
response_payload['cmd'] = packet.json_payload['cmd']
response_payload['serial'] = packet.json_payload['serial']
response_payload['status'] = 0

# if 'energy' in packet.json_payload:
#response_payload['energy'] = packet.json_payload['energy']
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. Done

@@ -300,16 +305,19 @@ def handle(self):
self._mqtt_switch = HomemateSwitch(
self,
name=self.settings['name'],
entity_id=self.entity_id
entity_id=self.client_address[0].replace('.', '_')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

if self._mqtt_sensor is None:
self._mqtt_sensor = HomematePowerSensor(self,
name=self.settings['name'],
entity_id=self.client_address[0].replace('.', '_')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use self.entity_id

@property
def cmd_handlers(self):
return {
0: self.handle_hello,
32: self.handle_heartbeat,
42: self.handle_state_update,
6: self.handle_handshake
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Device handshakes need an ACK at the minimum otherwise they disconnect after awhile

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


return self.handle_default(packet)

def handle_energy_update(self, packet):
if 'energy' in packet.json_payload:
self.energy = packet.json_payload['energy']
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why track the current energy reading as an instance variable?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just thought it was a bit cleaner... any reason not too?

@boojew
Copy link
Author

boojew commented Feb 10, 2019

Thanks! I will clean this all up and try again soon!

@boojew
Copy link
Author

boojew commented Feb 11, 2019

For the key, I get b'khggd54865.....' w/o a new line at the end... I have tried it in that format and without the b''.. No luck. W/o the b'' it is 16 alphanumerics.

Oh and to get the key, I'm doing
python3 find_key.py kepler.apk > orvibo.key

@boojew
Copy link
Author

boojew commented Feb 16, 2019

@insertjokehere - any idea about the key?

@insertjokehere
Copy link
Owner

@boojew I've updated stream_decode.py to accept a keys file in the same format as the main application; give that a go?

@boojew
Copy link
Author

boojew commented Feb 17, 2019

No love...

root@skywarp:/tmp/homemate-bridge/research# python3 stream_decode.py stream.yaml
Traceback (most recent call last):
  File "stream_decode.py", line 14, in <module>
    int(k): base64.b64decode(v) for k, v in json.load(f)
  File "stream_decode.py", line 14, in <dictcomp>
    int(k): base64.b64decode(v) for k, v in json.load(f)
ValueError: too many values to unpack (expected 2)

Keys.json looks like this: {"112": "a2hnZ2Q1NDg2N<redactedbits>"}

I should also mention, stream.yaml is your example file - not one I generated. In case you have 5 minutes, I'm also including my pcap in case you could decode and put in back here - just so I can continue testing the energy capture. Thanks!

s31.txt

@insertjokehere
Copy link
Owner

I've updated stream_decode.py again (missed .items()) - it should work now. The decoded version of you capture is attached

s31_decoded.txt

@mssmison
Copy link

Looks like it's cmd 128 that tells the switch to respond with the power related stuff.
Is there a way to send that command as the heartbeat I guess?

Get power output
@boojew
Copy link
Author

boojew commented Mar 12, 2019

I was able to get this to work. I have commited some code in https://github.com/boojew/homemate-bridge/blob/master/src/homemate_bridge/cli.py and I will tune it over the next few days.

If anyone wants to try it, check out https://hub.docker.com/r/boojew/homemate-bridge. Be sure to pass the pass the keyfile as a volume or env and pass the host as the env variable MQTT_HOST

@mkormendy
Copy link

@boojew hows the tuning going?

@mkormendy
Copy link

mkormendy commented May 28, 2019

@boojew I'm getting this error when I attempt to use your fork of this plugin:

Traceback (most recent call last):
  File "/usr/local/bin/homemate-bridge", line 11, in <module>
    load_entry_point('homemate-bridge', 'console_scripts', 'homemate-bridge')()
  File "/usr/local/lib/python3.7/site-packages/pkg_resources/__init__.py", line 489, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/local/lib/python3.7/site-packages/pkg_resources/__init__.py", line 2793, in load_entry_point
    return ep.load()
  File "/usr/local/lib/python3.7/site-packages/pkg_resources/__init__.py", line 2411, in load
    return self.resolve()
  File "/usr/local/lib/python3.7/site-packages/pkg_resources/__init__.py", line 2417, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "/██████████/homemate-bridge/src/homemate_bridge/cli.py", line 18, in <module>
    from hassdevice.devices import Switch, Sensor
ImportError: cannot import name 'Sensor' from 'hassdevice.devices'

@mkormendy mkormendy mentioned this pull request May 28, 2019
@mike391
Copy link

mike391 commented Sep 30, 2021

what units is the energy variable in? Kwh?

@Eamourinho
Copy link

I know this is ancient, but I just picked up a bunch of S31 switches and was hoping to get 'em integrated into my home assistant instance. Got my MQTT broker running in home assistant, and am able to connect to it from other devices on the network. Got my "PK" encryption key from the kepler.apk. Set up a simple docker-compose in my portainer instance:

version: "3"
services:
  ###########################
  # Homemate to MQTT bridge for S31 Orvibo Switches
  # Fork of https://github.com/insertjokehere/homemate-bridge
  ###########################
  homemate-bridge:
    image: boojew/homemate-bridge
    volumes:
      - /docker/mountpoints/homematebridge/keys.json:/config/keys.json
    environment:
      - MQTT_HOST="192.168.0.115"

But unfortunately I'm getting this error:
socket.gaierror: [Errno -2] Name or service not known

Any ideas what might be going awry here? ':D

I'm able to ping the MQTT broker from other containers within my portainer instance so it should be able to see the 0.115 IP... are there other environment variables I'm missing here? I've got an account configured in home assistant to access the MQTT broker, how would I go about configuring that for this bridge?

@insertjokehere
Copy link
Owner

@Eamourinho - wow, I'm glad that people are still finding this useful.

I don't really remember how any of this works, but two guesses:

  • Paho (the library that we use to connect to MQTT servers) is treating the IP address as a host name and trying to look it up for some reason - if the stack trace points you towards paho.mqtt.client then its probably something like that; not sure how you'd approach solving it
  • Something to do with your container environment not allowing the script to bind to 0.0.0.0 to listen for connections from the switches. I'd suggest adding network_mode: "host" to your compose file and seeing if that does something

While I'm here, @boojew sorry you got such a terse code review back in the day, thanks for putting in the effort to put your changes up for others to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants