Skip to content

Commit

Permalink
Merge pull request #54 from AK5nowman/dev_v2Basic
Browse files Browse the repository at this point in the history
Add V2 Motion and  Contact Sensor support
  • Loading branch information
raetha authored May 14, 2021
2 parents 7198387 + e2e743a commit 61fb8f9
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 44 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ root:
```
### sensors.yaml
This file will store basic information about each sensor paired to the Wyse Sense Bridge. The entries can be modified to set the class type and sensor name as it will show in Home Assistant. Since this file can be automatically generated, Python may automatically quote the MACs or not depending on if they are fully numeric.
This file will store basic information about each sensor paired to the Wyse Sense Bridge. The entries can be modified to set the class type and sensor name as it will show in Home Assistant. Class types can be automatically filled for `opening`, `motion`, and `moisture`, depending on the type of sensor. More class types that Home Assistant recgonizes can be found in the [`binary_sensor` documentation](https://www.home-assistant.io/integrations/binary_sensor/) Since this file can be automatically generated, Python may automatically quote the MACs or not depending on if they are fully numeric.
```yaml
'AAAAAAAA':
class: door
Expand Down
30 changes: 19 additions & 11 deletions wyzesense2mqtt/wyzesense.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,19 +260,27 @@ def _OnSensorAlarm(self, pkt):
timestamp = datetime.datetime.fromtimestamp(timestamp / 1000.0)
sensor_mac = sensor_mac.decode('ascii')
alarm_data = pkt.Payload[17:]

# {sensor_id: "sensor type", "states": ["off state", "on state"]}
contact_ids = {0x01: "switch", 0x0E: "switchv2", "states": ["close", "open"]}
motion_ids = {0x02: "motion", 0x0F: "motionv2", "states": ["inactive", "active"]}
leak_ids = {0x03: "leak", "states": ["dry", "wet"]}

if event_type == 0xA2 or event_type == 0xA1:
if alarm_data[0] == 0x01:
sensor_type = "switch"
sensor_state = "open" if alarm_data[5] == 1 else "close"
elif alarm_data[0] == 0x02:
sensor_type = "motion"
sensor_state = "active" if alarm_data[5] == 1 else "inactive"
elif alarm_data[0] == 0x03:
sensor_type = "leak"
sensor_state = "wet" if alarm_data[5] == 1 else "dry"
sensor = {}
if alarm_data[0] in contact_ids:
sensor = contact_ids
elif alarm_data[0] in motion_ids:
sensor = motion_ids
elif alarm_data[0] in leak_ids:
sensor = leak_ids

if sensor:
sensor_type = sensor[alarm_data[0]]
sensor_state = sensor["states"][alarm_data[5]]
else:
sensor_type = "unknown"
sensor_state = "unknown"
sensor_type = "unknown (" + alarm_data[0] + ")"
sensor_state = "unknown (" + alarm_data[5] + ")"
e = SensorEvent(sensor_mac, timestamp, ("alarm" if event_type == 0xA2 else "status"), (sensor_type, sensor_state, alarm_data[2], alarm_data[8]))
elif event_type == 0xE8:
if alarm_data[0] == 0x03:
Expand Down
61 changes: 29 additions & 32 deletions wyzesense2mqtt/wyzesense2mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@


# Configuration File Locations
CONFIG_PATH = "config/"
SAMPLES_PATH = "samples/"
CONFIG_PATH = "config"
SAMPLES_PATH = "samples"
MAIN_CONFIG_FILE = "config.yaml"
LOGGING_CONFIG_FILE = "logging.yaml"
SENSORS_CONFIG_FILE = "sensors.yaml"

# Simplify mapping of device classes.
# { **dict.fromkeys(['list', 'of', 'possible', 'identifiers'], 'device_class') }
DEVICE_CLASSES = {
**dict.fromkeys([0x01, 0x0E, 'switch', 'switchv2'], 'opening'),
**dict.fromkeys([0x02, 0x0F, 'motion', 'motionv2'], 'motion'),
**dict.fromkeys([0x03, 'leak'], 'moisture')
}

# Read data from YAML file
def read_yaml_file(filename):
Expand Down Expand Up @@ -55,13 +62,13 @@ def write_yaml_file(filename, data):
# Initialize logging
def init_logging():
global LOGGER
if (not os.path.isfile(CONFIG_PATH + LOGGING_CONFIG_FILE)):
if (not os.path.isfile(os.path.join(CONFIG_PATH, LOGGING_CONFIG_FILE))):
print("Copying default logging config file...")
try:
shutil.copy2(SAMPLES_PATH + LOGGING_CONFIG_FILE, CONFIG_PATH)
shutil.copy2(os.path.join(SAMPLES_PATH, LOGGING_CONFIG_FILE), CONFIG_PATH)
except IOError as error:
print(f"Unable to copy default logging config file. {str(error)}")
logging_config = read_yaml_file(CONFIG_PATH + LOGGING_CONFIG_FILE)
logging_config = read_yaml_file(os.path.join(CONFIG_PATH, LOGGING_CONFIG_FILE))

log_path = os.path.dirname(logging_config['handlers']['file']['filename'])
try:
Expand All @@ -80,12 +87,12 @@ def init_config():
LOGGER.debug("Initializing configuration...")

# load base config - allows for auto addition of new settings
if (os.path.isfile(SAMPLES_PATH + MAIN_CONFIG_FILE)):
CONFIG = read_yaml_file(SAMPLES_PATH + MAIN_CONFIG_FILE)
if (os.path.isfile(os.path.join(SAMPLES_PATH, MAIN_CONFIG_FILE))):
CONFIG = read_yaml_file(os.path.join(SAMPLES_PATH, MAIN_CONFIG_FILE))

# load user config over base
if (os.path.isfile(CONFIG_PATH + MAIN_CONFIG_FILE)):
user_config = read_yaml_file(CONFIG_PATH + MAIN_CONFIG_FILE)
if (os.path.isfile(os.path.join(CONFIG_PATH, MAIN_CONFIG_FILE))):
user_config = read_yaml_file(os.path.join(CONFIG_PATH, MAIN_CONFIG_FILE))
CONFIG.update(user_config)

# fail on no config
Expand All @@ -96,7 +103,7 @@ def init_config():
# write updated config file if needed
if (CONFIG != user_config):
LOGGER.info("Writing updated config file")
write_yaml_file(CONFIG_PATH + MAIN_CONFIG_FILE, CONFIG)
write_yaml_file(os.path.join(CONFIG_PATH, MAIN_CONFIG_FILE), CONFIG)


# Initialize MQTT client connection
Expand Down Expand Up @@ -141,7 +148,7 @@ def init_wyzesense_dongle():
if (("e024" in line) and ("1a86" in line)):
for device_name in line.split(" "):
if ("hidraw" in device_name):
CONFIG['usb_dongle'] = "/dev/%s" % device_name
CONFIG['usb_dongle'] = f"/dev/{device_name}"
break

LOGGER.info(f"Connecting to dongle {CONFIG['usb_dongle']}")
Expand All @@ -159,12 +166,12 @@ def init_wyzesense_dongle():
def init_sensors():
# Initialize sensor dictionary
global SENSORS
SENSORS = dict()
SENSORS = {}

# Load config file
LOGGER.debug("Reading sensors configuration...")
if (os.path.isfile(CONFIG_PATH + SENSORS_CONFIG_FILE)):
SENSORS = read_yaml_file(CONFIG_PATH + SENSORS_CONFIG_FILE)
if (os.path.isfile(os.path.join(CONFIG_PATH, SENSORS_CONFIG_FILE))):
SENSORS = read_yaml_file(os.path.join(CONFIG_PATH, SENSORS_CONFIG_FILE))
sensors_config_file_found = True
else:
LOGGER.info("No sensors config file found.")
Expand Down Expand Up @@ -192,7 +199,7 @@ def init_sensors():
# Save sensors file if didn't exist
if (not sensors_config_file_found):
LOGGER.info("Writing Sensors Config File")
write_yaml_file(CONFIG_PATH + SENSORS_CONFIG_FILE, SENSORS)
write_yaml_file(os.path.join(CONFIG_PATH, SENSORS_CONFIG_FILE), SENSORS)

# Send discovery topics
if(CONFIG['hass_discovery']):
Expand Down Expand Up @@ -225,17 +232,15 @@ def valid_sensor_mac(sensor_mac):
def add_sensor_to_config(sensor_mac, sensor_type, sensor_version):
global SENSORS
LOGGER.info(f"Adding sensor to config: {sensor_mac}")
SENSORS[sensor_mac] = dict()
SENSORS[sensor_mac]['name'] = f"Wyze Sense {sensor_mac}"
SENSORS[sensor_mac]['class'] = (
"motion" if (sensor_type == "motion")
else "opening"
)
SENSORS[sensor_mac]['invert_state'] = False
SENSORS[sensor_mac] = {
'name': f"Wyze Sense {sensor_mac}",
'class': DEVICE_CLASSES.get(sensor_type),
'invert_state': False
}
if (sensor_version is not None):
SENSORS[sensor_mac]['sw_version'] = sensor_version

write_yaml_file(CONFIG_PATH + SENSORS_CONFIG_FILE, SENSORS)
write_yaml_file(os.path.join(CONFIG_PATH, SENSORS_CONFIG_FILE), SENSORS)


# Delete sensor from config
Expand All @@ -244,7 +249,7 @@ def delete_sensor_from_config(sensor_mac):
LOGGER.info(f"Deleting sensor from config: {sensor_mac}")
try:
del SENSORS[sensor_mac]
write_yaml_file(CONFIG_PATH + SENSORS_CONFIG_FILE, SENSORS)
write_yaml_file(os.path.join(CONFIG_PATH, SENSORS_CONFIG_FILE), SENSORS)
except KeyError:
LOGGER.debug(f"{sensor_mac} not found in SENSORS")

Expand Down Expand Up @@ -380,7 +385,6 @@ def on_message_scan(MQTT_CLIENT, userdata, msg):
LOGGER.debug(f"Scan result: {result}")
if (result):
sensor_mac, sensor_type, sensor_version = result
sensor_type = ("motion" if (sensor_type == 2) else "opening")
if (valid_sensor_mac(sensor_mac)):
if (SENSORS.get(sensor_mac)) is None:
add_sensor_to_config(
Expand Down Expand Up @@ -424,13 +428,6 @@ def on_message_reload(MQTT_CLIENT, userdata, msg):
def on_event(WYZESENSE_DONGLE, event):
global SENSORS

# Simplify mapping of device classes.
DEVICE_CLASSES = {
'leak': 'moisture',
'motion': 'motion',
'switch': 'opening',
}

# List of states that correlate to ON.
STATES_ON = ['active', 'open', 'wet']

Expand Down

0 comments on commit 61fb8f9

Please sign in to comment.