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

Dongle v47 support #61

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion wyzesense2mqtt/wyzesense.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class Packet(object):
NOTIFY_SENSOR_SCAN = MAKE_CMD(TYPE_ASYNC, 0x20)
NOITFY_SYNC_TIME = MAKE_CMD(TYPE_ASYNC, 0x32)
NOTIFY_EVENT_LOG = MAKE_CMD(TYPE_ASYNC, 0x35)
NOTIFY_HMS_EVENT = MAKE_CMD(TYPE_ASYNC, 0x55)

def __init__(self, cmd, payload=bytes()):
self._cmd = cmd
Expand Down Expand Up @@ -238,6 +239,16 @@ def __str__(self):
s += "AlarmEvent: sensor_type=%s, state=%s, battery=%d, signal=%d" % self.Data
elif self.Type == 'status':
s += "StatusEvent: sensor_type=%s, state=%s, battery=%d, signal=%d" % self.Data
elif self.Type == 'water':
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should either be 'state', or preferably we should change this and the leak portion to 'leak' or 'water'.

s += "WaterEvent: water=%d ext_water=%d has_ext=%d battery=%d signal=%d" % self.Data
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do the values of ext_water and has_ext represent?

elif self.Type == 'climate':
s += "ClimateEvent: temperature=%d humidity=%d battery=%d signal=%d" % self.Data
elif self.Type == 'keypadMode':
s += "KeypadModeEvent: mode=%d, battery=%d signal=%d" % self.Data
elif self.Type == 'keypadMotion':
drinfernoo marked this conversation as resolved.
Show resolved Hide resolved
s += "KeypadModeEvent: motion=%d, battery=%d signal=%d" % self.Data
elif self.Type == 'keypadPin':
s+= "KeyPadPinEvent: pin=%s battery=%d signal=%d" % self.Data
else:
s += "RawEvent: type=%s, data=%s" % (self.Type, bytes_to_hex(self.Data))
return s
Expand Down Expand Up @@ -288,7 +299,13 @@ def _OnSensorAlarm(self, pkt):
# is reporting way to high to actually be humidity.
sensor_type = "leak:temperature"
sensor_state = "%d.%d" % (alarm_data[5], alarm_data[6])
e = SensorEvent(sensor_mac, timestamp, "state", (sensor_type, sensor_state, alarm_data[2], alarm_data[8]))
e = SensorEvent(sensor_mac, timestamp, "state", (sensor_type, sensor_state, alarm_data[2], alarm_data[8]))
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's a mismatch here between what is getting sent to SensorEvent, and what SensorEvent is listening for.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think 'leak' here and where I mentioned above are probably the most obvious choices.

else:
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the value of alarm_data[0] for a climate sensor?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, is event_type == 0xE8 also sent by the climate sensors? That event type was added in #32 for leak sensors, but not sure if it also applies to climate sensors.

I found a potentially relevant discussion here: #29 (comment), but there was never any answer from @jellybob 🤔

temperature = alarm_data[0x05]
humidity = alarm_data[0x07]
Copy link
Collaborator

Choose a reason for hiding this comment

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

The comment leftover from #32 mentions that alarm_data[7] here might not actually represent humidity accurately... what did you find?

battery = alarm_data[0x02]
signal = alarm_data[0x0A]
drinfernoo marked this conversation as resolved.
Show resolved Hide resolved
e = SensorEvent(sensor_mac, timestamp, "climate", (temperature, humidity, battery, signal))
else:
e = SensorEvent(sensor_mac, timestamp, "raw_%02X" % event_type, alarm_data)

Expand All @@ -305,6 +322,46 @@ def _OnEventLog(self, pkt):
msg = pkt.Payload[9:]
log.info("LOG: time=%s, data=%s", tm.isoformat(), bytes_to_hex(msg))

def _OnHMSEvent(self, pkt):
drinfernoo marked this conversation as resolved.
Show resolved Hide resolved
payloadSubType = pkt.Payload[0x0E]
if payloadSubType == 0x12:
#Water sensor event
typeByte, mac = struct.unpack_from(">B8s", pkt.Payload);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Like this and the next line are duplicated, along with some common payload data (like battery)...

mac = mac.decode('ascii')
water = pkt.Payload[0x0F]
ext_water = pkt.Payload[0x10]
has_ext = pkt.Payload[0x11]
signal = pkt.Payload[0x14]
battery = pkt.Payload[0x0C]
e = SensorEvent(mac, datetime.datetime.utcnow(), "water", (water, ext_water, has_ext, battery, signal))
self.__on_event(self, e)
elif payloadSubType == 0x0A or payloadSubType == 0x02:
typeByte, mac = struct.unpack_from(">B8s", pkt.Payload);
drinfernoo marked this conversation as resolved.
Show resolved Hide resolved
mac = mac.decode('ascii')
battery = pkt.Payload[0x0C]
Copy link
Collaborator

Choose a reason for hiding this comment

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

This one is consistently (for me anyways) a value of around 150... How can we figure the actual battery value from that?

signal = pkt.Payload[pkt.Payload[0x0A] + 0x0B]
if pkt.Payload[0x0E] == 0x02:
mode = pkt.Payload[0x0F] + 1
e = SensorEvent(mac, datetime.datetime.utcnow(), "keypadMode", (mode, battery, signal))
self.__on_event(self, e)
else:
motion = pkt.Payload[0x0F]
e = SensorEvent(mac, datetime.datetime.utcnow(), "keypadMotion", (motion, battery, signal))
self.__on_event(self, e)
elif payloadSubType == 0x08:
typeByte, mac = struct.unpack_from(">B8s", pkt.Payload);
drinfernoo marked this conversation as resolved.
Show resolved Hide resolved
mac = mac.decode('ascii')
battery = pkt.Payload[0x0C]
signal = pkt.Payload[pkt.Payload[0xA] + 0xB]
drinfernoo marked this conversation as resolved.
Show resolved Hide resolved

pinBytes = pkt.Payload[0xF:(0xF + pkt.Payload[0xA] - 6)]
pinIntList = [str(int) for int in list(pinBytes)]
drinfernoo marked this conversation as resolved.
Show resolved Hide resolved
pinStr = "".join(pinIntList)

e = SensorEvent(mac, datetime.datetime.utcnow(), "keypadPin", (pinStr, battery, signal))
self.__on_event(self, e)


def __init__(self, device, event_handler):
self.__lock = threading.Lock()
self.__fd = os.open(device, os.O_RDWR | os.O_NONBLOCK)
Expand All @@ -317,6 +374,7 @@ def __init__(self, device, event_handler):
Packet.NOITFY_SYNC_TIME: self._OnSyncTime,
Packet.NOTIFY_SENSOR_ALARM: self._OnSensorAlarm,
Packet.NOTIFY_EVENT_LOG: self._OnEventLog,
Packet.NOTIFY_HMS_EVENT: self._OnHMSEvent,
}

self._Start()
Expand Down
101 changes: 100 additions & 1 deletion wyzesense2mqtt/wyzesense2mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# import time

import paho.mqtt.client as mqtt
import wyzesense
#import wyzesense
from retrying import retry


Expand Down Expand Up @@ -466,6 +466,103 @@ def on_event(WYZESENSE_DONGLE, event):

LOGGER.debug(event_payload)

state_topic = f"{CONFIG['self_topic_root']}/{event.MAC}"
mqtt_publish(state_topic, event_payload)
elif(event.Type == "water"):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this one should also probably be 'leak'.

(water, ext_water, has_ext, battery, signal) = event.Data
# Build event payload
event_payload = {
'event': event.Type,
'available': True,
'mac': event.MAC,
'device_class': 'leak',
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll double-check, but I believe the device class Home Assistant uses is moisture, so probably best to use that if so.

'last_seen': event.Timestamp.timestamp(),
'last_seen_iso': event.Timestamp.isoformat(),
'signal_strength': signal * -1,
'battery': battery,
'state' : {
'water' : water,
'ext_water' : ext_water,
'has_ext' : has_ext
}
}
LOGGER.debug(event_payload)

state_topic = f"{CONFIG['self_topic_root']}/{event.MAC}"
mqtt_publish(state_topic, event_payload)
elif(event.Type == "keypadMotion"):
(motion, battery, signal) = event.Data
# Build event payload
event_payload = {
'event': event.Type,
'available': True,
'mac': event.MAC,
'device_class': 'motion',
'last_seen': event.Timestamp.timestamp(),
'last_seen_iso': event.Timestamp.isoformat(),
'signal_strength': signal * -1,
'battery': battery,
'state' : motion
}
LOGGER.debug(event_payload)

state_topic = f"{CONFIG['self_topic_root']}/{event.MAC}"
mqtt_publish(state_topic, event_payload)
elif(event.Type == "keypadMode"):
(mode, battery, signal) = event.Data
# Build event payload
event_payload = {
'event': event.Type,
'available': True,
'mac': event.MAC,
'device_class': 'keypadMode',
'last_seen': event.Timestamp.timestamp(),
'last_seen_iso': event.Timestamp.isoformat(),
'signal_strength': signal * -1,
'battery': battery,
'state' : mode
}
LOGGER.debug(event_payload)

state_topic = f"{CONFIG['self_topic_root']}/{event.MAC}/mode"
mqtt_publish(state_topic, event_payload)
elif(event.Type == "keypadPin"):
(pinBytes, battery, signal) = event.Data
# Build event payload
event_payload = {
'event': event.Type,
'available': True,
'mac': event.MAC,
'device_class': 'keypadPin',
'last_seen': event.Timestamp.timestamp(),
'last_seen_iso': event.Timestamp.isoformat(),
'signal_strength': signal * -1,
'battery': battery,
'state' : pinBytes
}
LOGGER.debug(event_payload)

state_topic = f"{CONFIG['self_topic_root']}/{event.MAC}/pin"
mqtt_publish(state_topic, event_payload)
elif(event.Type == "climate"):
(temperature, humidity, battery, signal) = event.Data
# Build event payload
event_payload = {
'event': event.Type,
'available': True,
'mac': event.MAC,
'device_class': 'climate',
'last_seen': event.Timestamp.timestamp(),
'last_seen_iso': event.Timestamp.isoformat(),
'signal_strength': signal * -1,
'battery': battery,
'state' : {
'temperature' : temperature,
'humidity' : humidity
}
}
LOGGER.debug(event_payload)

state_topic = f"{CONFIG['self_topic_root']}/{event.MAC}"
mqtt_publish(state_topic, event_payload)
else:
Expand All @@ -480,6 +577,8 @@ def on_event(WYZESENSE_DONGLE, event):
# Initialize logging
init_logging()

import wyzesense
drinfernoo marked this conversation as resolved.
Show resolved Hide resolved

# Initialize configuration
init_config()

Expand Down