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 sensor name to log entries #62

Draft
wants to merge 16 commits into
base: devel
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
4 changes: 2 additions & 2 deletions 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. 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 Expand Up @@ -233,7 +233,7 @@ Once run it will present a menu of its functions:


## Home Assistant
Home Assistant simply needs to be configured with the MQTT broker that the gateway publishes topics to. Once configured, the MQTT integration will automatically add devices for each sensor along with entites for the state, battery_level, and signal_strength. By default these entities will have a device_class of "opening" for contact sensors and "motion" for motion sensors. They will be named for the sensor type and MAC, e.g. Wyze Sense Contact Sensor AABBCCDD. To adjust the device_class to door or window, and set a custom name, update the sensors.yaml configuration file and replace the defaults, then restart WyzeSense2MQTT.
Home Assistant simply needs to be configured with the MQTT broker that the gateway publishes topics to. Once configured, the MQTT integration will automatically add devices for each sensor along with entites for the state, battery_level, and signal_strength. By default these entities will have a device_class of "opening" for contact sensors, "motion" for motion sensors, and "moisture" for leak sensors. They will be named for the sensor type and MAC, e.g. Wyze Sense Contact Sensor AABBCCDD. To adjust the device_class to "door" or "window", and set a custom name, update the sensors.yaml configuration file and replace the defaults, then restart WyzeSense2MQTT. For a comprehensive list of device classes the Home Assistant recognizes, see the [`binary_sensor` documentation](https://www.home-assistant.io/integrations/binary_sensor/).


## Tested On
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.2
2.0
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
69 changes: 33 additions & 36 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,27 +428,20 @@ 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']

if (valid_sensor_mac(event.MAC)):
if valid_sensor_mac(event.MAC):
if (event.Type == "alarm") or (event.Type == "status"):
LOGGER.info(f"State event data: {event}")
(sensor_type, sensor_state, sensor_battery, sensor_signal) = event.Data

# Add sensor if it doesn't already exist
if (event.MAC not in SENSORS):
if event.MAC not in SENSORS:
add_sensor_to_config(event.MAC, sensor_type, None)
if(CONFIG['hass_discovery']):
if CONFIG['hass_discovery']:
send_discovery_topics(event.MAC)

LOGGER.info(f"State event data from {SENSORS[event.MAC]['name']}: {event}")
# Build event payload
event_payload = {
'event': event.Type,
Expand Down