|
|
|
@ -82,10 +82,10 @@ class AmcrestMqtt(object):
|
|
|
|
def mqtt_on_disconnect(self, client, userdata, flags, rc, properties):
|
|
|
|
def mqtt_on_disconnect(self, client, userdata, flags, rc, properties):
|
|
|
|
self.logger.info('MQTT connection closed')
|
|
|
|
self.logger.info('MQTT connection closed')
|
|
|
|
|
|
|
|
|
|
|
|
# if reconnect, lets use a new client_id
|
|
|
|
# if we try to reconnect, lets use a new client_id
|
|
|
|
self.client_id = self.get_new_client_id()
|
|
|
|
self.client_id = self.get_new_client_id()
|
|
|
|
|
|
|
|
|
|
|
|
if time.time() > self.mqtt_connect_time + 10:
|
|
|
|
if time.time() > self.mqtt_connect_time + 20:
|
|
|
|
self.mqttc_create()
|
|
|
|
self.mqttc_create()
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
exit()
|
|
|
|
exit()
|
|
|
|
@ -97,10 +97,14 @@ class AmcrestMqtt(object):
|
|
|
|
self.logger.warn(f'MQTT LOG: {msg}')
|
|
|
|
self.logger.warn(f'MQTT LOG: {msg}')
|
|
|
|
|
|
|
|
|
|
|
|
def mqtt_on_message(self, client, userdata, msg):
|
|
|
|
def mqtt_on_message(self, client, userdata, msg):
|
|
|
|
if not msg or not msg.payload:
|
|
|
|
try:
|
|
|
|
return
|
|
|
|
|
|
|
|
topic = msg.topic
|
|
|
|
topic = msg.topic
|
|
|
|
payload = json.loads(msg.payload)
|
|
|
|
payload = json.loads(msg.payload)
|
|
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
|
|
|
payload = msg.payload.decode('utf-8')
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
|
|
|
self.logger.error('Failed to understand MQTT message, ignoring')
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.logger.info(f'Got MQTT message for {topic} - {payload}')
|
|
|
|
self.logger.info(f'Got MQTT message for {topic} - {payload}')
|
|
|
|
|
|
|
|
|
|
|
|
@ -112,21 +116,25 @@ class AmcrestMqtt(object):
|
|
|
|
components = topic.split('/')
|
|
|
|
components = topic.split('/')
|
|
|
|
|
|
|
|
|
|
|
|
# handle this message if it's for us, otherwise pass along to amcrest API
|
|
|
|
# handle this message if it's for us, otherwise pass along to amcrest API
|
|
|
|
|
|
|
|
try:
|
|
|
|
if components[-2] == self.get_component_slug('service'):
|
|
|
|
if components[-2] == self.get_component_slug('service'):
|
|
|
|
self.handle_service_message(None, payload)
|
|
|
|
self.handle_service_message(None, payload)
|
|
|
|
elif components[-3] == self.get_component_slug('service'):
|
|
|
|
elif components[-3] == self.get_component_slug('service'):
|
|
|
|
self.handle_service_message(components[-1], payload)
|
|
|
|
self.handle_service_message(components[-1], payload)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
if components[-1] == 'set':
|
|
|
|
if components[-1] == 'set':
|
|
|
|
mac = components[-2][-16:]
|
|
|
|
device_id = components[-2].split('-')[1]
|
|
|
|
elif components[-2] == 'set':
|
|
|
|
elif components[-2] == 'set':
|
|
|
|
mac = components[-3][-16:]
|
|
|
|
device_id = components[-3].split('-')[1]
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
self.logger.error(f'UNKNOWN MQTT MESSAGE STRUCTURE: {topic}')
|
|
|
|
self.logger.error(f'UNKNOWN MQTT MESSAGE STRUCTURE: {topic}')
|
|
|
|
return
|
|
|
|
return
|
|
|
|
# ok, lets format the device_id and send to amcrest
|
|
|
|
# ok, lets format the device_id and send to amcrest
|
|
|
|
device_id = ':'.join([mac[i:i+2] for i in range (0, len(mac), 2)])
|
|
|
|
# for Amcrest devices, we use the string as-is (after the vendor name)
|
|
|
|
self.send_command(device_id, payload)
|
|
|
|
self.send_command(device_id, payload)
|
|
|
|
|
|
|
|
except Exception as err:
|
|
|
|
|
|
|
|
self.logger.error(f'Failed to understand MQTT message slug ({topic}): {err}, ignoring')
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
def mqtt_on_subscribe(self, client, userdata, mid, reason_code_list, properties):
|
|
|
|
def mqtt_on_subscribe(self, client, userdata, mid, reason_code_list, properties):
|
|
|
|
rc_list = map(lambda x: x.getName(), reason_code_list)
|
|
|
|
rc_list = map(lambda x: x.getName(), reason_code_list)
|
|
|
|
@ -160,7 +168,7 @@ class AmcrestMqtt(object):
|
|
|
|
self.mqttc.on_subscribe = self.mqtt_on_subscribe
|
|
|
|
self.mqttc.on_subscribe = self.mqtt_on_subscribe
|
|
|
|
self.mqttc.on_log = self.mqtt_on_log
|
|
|
|
self.mqttc.on_log = self.mqtt_on_log
|
|
|
|
|
|
|
|
|
|
|
|
# self.mqttc.will_set(self.get_state_topic(self.service_slug) + '/availability', payload="offline", qos=0, retain=True)
|
|
|
|
self.mqttc.will_set(self.get_discovery_topic('service', 'availability'), payload="offline", qos=self.mqtt_config['qos'], retain=True)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
self.mqttc.connect(
|
|
|
|
self.mqttc.connect(
|
|
|
|
@ -218,7 +226,7 @@ class AmcrestMqtt(object):
|
|
|
|
self.mqttc.publish(
|
|
|
|
self.mqttc.publish(
|
|
|
|
self.get_discovery_topic('service','config'),
|
|
|
|
self.get_discovery_topic('service','config'),
|
|
|
|
json.dumps({
|
|
|
|
json.dumps({
|
|
|
|
'qos': 0,
|
|
|
|
'qos': self.mqtt_config['qos'],
|
|
|
|
'state_topic': state_topic,
|
|
|
|
'state_topic': state_topic,
|
|
|
|
'availability_topic': availability_topic,
|
|
|
|
'availability_topic': availability_topic,
|
|
|
|
'device': {
|
|
|
|
'device': {
|
|
|
|
@ -297,11 +305,13 @@ class AmcrestMqtt(object):
|
|
|
|
first = True
|
|
|
|
first = True
|
|
|
|
self.devices[device_id] = {}
|
|
|
|
self.devices[device_id] = {}
|
|
|
|
self.configs[device_id] = config
|
|
|
|
self.configs[device_id] = config
|
|
|
|
self.devices[device_id]['qos'] = 0
|
|
|
|
self.devices[device_id]['qos'] = self.mqtt_config['qos'],
|
|
|
|
self.devices[device_id]['state_topic'] = self.get_discovery_topic(device_id, 'state')
|
|
|
|
self.devices[device_id]['state_topic'] = self.get_discovery_topic(device_id, 'state')
|
|
|
|
self.devices[device_id]['availability_topic'] = self.get_discovery_topic(device_id, 'availability')
|
|
|
|
self.devices[device_id]['availability_topic'] = self.get_discovery_topic(device_id, 'availability')
|
|
|
|
self.devices[device_id]['command_topic'] = self.get_discovery_topic(device_id, 'set')
|
|
|
|
self.devices[device_id]['command_topic'] = self.get_discovery_topic(device_id, 'set')
|
|
|
|
# self.mqttc.will_set(self.get_state_topic(device_id)+'/availability', payload="offline", qos=0, retain=True)
|
|
|
|
self.mqttc.will_set(self.get_discovery_topic(device_id,'state'), payload=json.dumps({'status': 'offline'}), qos=self.mqtt_config['qos'], retain=True)
|
|
|
|
|
|
|
|
self.mqttc.will_set(self.get_discovery_topic(device_id,'motion'), payload=None, qos=self.mqtt_config['qos'], retain=True)
|
|
|
|
|
|
|
|
self.mqttc.will_set(self.get_discovery_topic(device_id,'availability'), payload='offline', qos=self.mqtt_config['qos'], retain=True)
|
|
|
|
|
|
|
|
|
|
|
|
self.devices[device_id]['device'] = {
|
|
|
|
self.devices[device_id]['device'] = {
|
|
|
|
'name': config['device_name'],
|
|
|
|
'name': config['device_name'],
|
|
|
|
@ -331,9 +341,11 @@ class AmcrestMqtt(object):
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
self.logger.debug(f'Updated device: {self.devices[device_id]['device']['name']}')
|
|
|
|
self.logger.debug(f'Updated device: {self.devices[device_id]['device']['name']}')
|
|
|
|
|
|
|
|
|
|
|
|
# device discovery sent, now it is save to add these to the dict
|
|
|
|
# device discovery sent, now it is save to add these to the
|
|
|
|
|
|
|
|
# dict (so they aren't included in device discovery object itself)
|
|
|
|
self.devices[device_id]['state'] = {}
|
|
|
|
self.devices[device_id]['state'] = {}
|
|
|
|
self.devices[device_id]['availability'] = 'online'
|
|
|
|
self.devices[device_id]['availability'] = 'online'
|
|
|
|
|
|
|
|
self.devices[device_id]['motion'] = 'off'
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
if first_time_through:
|
|
|
|
if first_time_through:
|
|
|
|
self.logger.info(f'Saw device, but not supported yet: "{config["device_name"]}" [amcrest {config["device_type"]}] ({device_id})')
|
|
|
|
self.logger.info(f'Saw device, but not supported yet: "{config["device_name"]}" [amcrest {config["device_type"]}] ({device_id})')
|
|
|
|
@ -357,6 +369,7 @@ class AmcrestMqtt(object):
|
|
|
|
'value_template': '{{ value_json.doorbell }}',
|
|
|
|
'value_template': '{{ value_json.doorbell }}',
|
|
|
|
'unique_id': self.get_slug(device_id, 'doorbell'),
|
|
|
|
'unique_id': self.get_slug(device_id, 'doorbell'),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.mqttc.will_set(self.get_discovery_topic(device_id,'doorbell'), payload=None, qos=self.mqtt_config['qos'], retain=True)
|
|
|
|
|
|
|
|
|
|
|
|
if config['is_ad410']:
|
|
|
|
if config['is_ad410']:
|
|
|
|
components[self.get_slug(device_id, 'human')] = {
|
|
|
|
components[self.get_slug(device_id, 'human')] = {
|
|
|
|
@ -369,6 +382,7 @@ class AmcrestMqtt(object):
|
|
|
|
'value_template': '{{ value_json.human }}',
|
|
|
|
'value_template': '{{ value_json.human }}',
|
|
|
|
'unique_id': self.get_slug(device_id, 'human'),
|
|
|
|
'unique_id': self.get_slug(device_id, 'human'),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.mqttc.will_set(self.get_discovery_topic(device_id,'human'), payload=None, qos=self.mqtt_config['qos'], retain=True)
|
|
|
|
|
|
|
|
|
|
|
|
components[self.get_slug(device_id, 'motion')] = {
|
|
|
|
components[self.get_slug(device_id, 'motion')] = {
|
|
|
|
'name': 'Motion',
|
|
|
|
'name': 'Motion',
|
|
|
|
@ -454,7 +468,6 @@ class AmcrestMqtt(object):
|
|
|
|
'unique_id': self.get_slug(device_id, 'last_update'),
|
|
|
|
'unique_id': self.get_slug(device_id, 'last_update'),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# since we always add at least `motion`, this should always be true
|
|
|
|
# since we always add at least `motion`, this should always be true
|
|
|
|
if len(components) > 0:
|
|
|
|
if len(components) > 0:
|
|
|
|
device['components'] = components
|
|
|
|
device['components'] = components
|
|
|
|
@ -501,7 +514,7 @@ class AmcrestMqtt(object):
|
|
|
|
self.publish_device(device_id)
|
|
|
|
self.publish_device(device_id)
|
|
|
|
|
|
|
|
|
|
|
|
def publish_device(self, device_id):
|
|
|
|
def publish_device(self, device_id):
|
|
|
|
for topic in ['state','availability','storage','motion','human','doorbell','event']:
|
|
|
|
for topic in ['state','availability','storage','motion','human','doorbell','event','recording']:
|
|
|
|
if topic in self.devices[device_id]:
|
|
|
|
if topic in self.devices[device_id]:
|
|
|
|
self.mqttc.publish(
|
|
|
|
self.mqttc.publish(
|
|
|
|
self.get_discovery_topic(device_id,topic),
|
|
|
|
self.get_discovery_topic(device_id,topic),
|
|
|
|
@ -521,22 +534,14 @@ class AmcrestMqtt(object):
|
|
|
|
self.update_service_device()
|
|
|
|
self.update_service_device()
|
|
|
|
|
|
|
|
|
|
|
|
def send_command(self, device_id, data):
|
|
|
|
def send_command(self, device_id, data):
|
|
|
|
caps = self.convert_attributes_to_capabilities(data)
|
|
|
|
device = self.devices[device_id]
|
|
|
|
sku = self.devices[device_id]['device']['model']
|
|
|
|
self.logger.info(f'COMMAND {device_id} = {data}')
|
|
|
|
|
|
|
|
|
|
|
|
self.logger.debug(f'COMMAND {device_id} = {caps}')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
first = True
|
|
|
|
if data == 'PRESS':
|
|
|
|
for key in caps:
|
|
|
|
pass
|
|
|
|
if not first:
|
|
|
|
else:
|
|
|
|
time.sleep(1)
|
|
|
|
self.logger.error(f'We got a command ({data}), but do not know what to do')
|
|
|
|
self.logger.debug(f'CMD DEVICE {self.devices[device_id]['device']['name']} ({device_id}) {key} = {caps[key]}')
|
|
|
|
|
|
|
|
self.amcrestc.send_command(device_id, sku, caps[key]['type'], caps[key]['instance'], caps[key]['value'])
|
|
|
|
|
|
|
|
self.update_service_device()
|
|
|
|
|
|
|
|
first = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if device_id not in self.boosted:
|
|
|
|
|
|
|
|
self.boosted.append(device_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_for_events(self):
|
|
|
|
def check_for_events(self):
|
|
|
|
while device_event := self.amcrestc.get_next_event():
|
|
|
|
while device_event := self.amcrestc.get_next_event():
|
|
|
|
@ -550,7 +555,7 @@ class AmcrestMqtt(object):
|
|
|
|
|
|
|
|
|
|
|
|
self.logger.info(f'Got event for {device_id}: {event} {payload}')
|
|
|
|
self.logger.info(f'Got event for {device_id}: {event} {payload}')
|
|
|
|
# if one of our known sensors
|
|
|
|
# if one of our known sensors
|
|
|
|
if event in ['motion','human','doorbell']:
|
|
|
|
if event in ['motion','human','doorbell','recording']:
|
|
|
|
device[event] = payload
|
|
|
|
device[event] = payload
|
|
|
|
# otherwise, just store generically
|
|
|
|
# otherwise, just store generically
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
|