diff --git a/VERSION b/VERSION index 1c21be8..2306249 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.99.24 +0.99.25 diff --git a/amcrest_api.py b/amcrest_api.py index 0b4b6d9..6d82b2d 100644 --- a/amcrest_api.py +++ b/amcrest_api.py @@ -120,6 +120,8 @@ class AmcrestAPI(object): storage = self.devices[device_id]["camera"].storage_all except CommError as err: self.logger.error(f'Failed to communicate with device ({device_id}) for storage stats') + except LoginError as err: + self.logger.error(f'Failed to authenticate with device ({device_id}) for storage stats') return { 'used_percent': str(storage['used_percent']), @@ -140,6 +142,9 @@ class AmcrestAPI(object): return privacy_mode except CommError as err: self.logger.error(f'Failed to communicate with device ({device_id}) to get privacy mode') + except LoginError as err: + self.logger.error(f'Failed to authenticate with device ({device_id}) to get privacy mode') + def set_privacy_mode(self, device_id, switch): device = self.devices[device_id] @@ -148,6 +153,8 @@ class AmcrestAPI(object): return device["camera"].set_privacy(switch).strip() except CommError as err: self.logger.error(f'Failed to communicate with device ({device_id}) to set privacy mode') + except LoginError as err: + self.logger.error(f'Failed to authenticate with device ({device_id}) to set privacy mode') # Motion detection config --------------------------------------------------------------------- @@ -160,6 +167,8 @@ class AmcrestAPI(object): return motion_detection except CommError as err: self.logger.error(f'Failed to communicate with device ({device_id}) to get motion detection') + except LoginError as err: + self.logger.error(f'Failed to authenticate with device ({device_id}) to get motion detection') def set_motion_detection(self, device_id, switch): device = self.devices[device_id] @@ -168,6 +177,8 @@ class AmcrestAPI(object): return device["camera"].set_motion_detection(switch) except CommError as err: self.logger.error(f'Failed to communicate with device ({device_id}) to set motion detections') + except LoginError as err: + self.logger.error(f'Failed to authenticate with device ({device_id}) to set motion detections') # Snapshots ----------------------------------------------------------------------------------- @@ -187,6 +198,8 @@ class AmcrestAPI(object): self.logger.info(f'Skipped snapshot from ({device_id}) because "privacy mode" is ON') except CommError as err: self.logger.error(f'Failed to communicate with device ({device_id}) to get snapshot') + except LoginError as err: + self.logger.error(f'Failed to authenticate with device ({device_id}) to get snapshot') def get_snapshot(self, device_id): return self.devices[device_id]['snapshot'] if 'snapshot' in self.devices[device_id] else None @@ -204,21 +217,17 @@ class AmcrestAPI(object): return data_base64 else: self.logger.error(f'Processed recording is too large') - return None - - return None + return except CommError as err: - self.logger.error(f'Failed to download recorded file for device ({device_id}): {err}') - return None + self.logger.error(f'Failed to download recorded file for device ({device_id})') + except LoginError as err: + self.logger.error(f'Failed to authenticate for recorded file for device ({device_id})') # Events -------------------------------------------------------------------------------------- async def collect_all_device_events(self): - try: - tasks = [self.get_events_from_device(device_id) for device_id in self.devices] - await asyncio.gather(*tasks) - except Exception as err: - self.logger.error(err, exc_info=True) + tasks = [self.get_events_from_device(device_id) for device_id in self.devices] + await asyncio.gather(*tasks) async def get_events_from_device(self, device_id): device = self.devices[device_id] @@ -227,9 +236,9 @@ class AmcrestAPI(object): async for code, payload in device["camera"].async_event_actions("All"): await self.process_device_event(device_id, code, payload) except CommError as err: - self.logger.error(f'Failed to cmmunicate with device ({device_id})') - except Exception as err: - self.logger.error(f'generic Failed to get events from device({device_id}: {err}', exc_info=True) + self.logger.error(f'Failed to communicate for events for device ({device_id})') + except LoginError as err: + self.logger.error(f'Failed to authenticate for events for device ({device_id})') async def process_device_event(self, device_id, code, payload): try: @@ -279,9 +288,8 @@ class AmcrestAPI(object): else: self.logger.info(f'Event on {device_id} - {code}: {payload}') self.events.append({ 'device_id': device_id, 'event': code , 'payload': payload }) - except Exception as err: - self.logger.error(err, exc_info=True) + self.logger.error(f'Failed to process event from {device_id}: {err}', exc_info=True) def get_next_event(self): return self.events.pop(0) if len(self.events) > 0 else None \ No newline at end of file diff --git a/amcrest_mqtt.py b/amcrest_mqtt.py index 663f655..088acd0 100644 --- a/amcrest_mqtt.py +++ b/amcrest_mqtt.py @@ -114,12 +114,9 @@ class AmcrestMqtt(object): elif components[-2] == 'set': vendor, device_id = components[-3].split('-') attribute = components[-1] - else: - self.logger.error(f'UNKNOWN MQTT MESSAGE STRUCTURE: {topic}') - return # of course, we only care about our 'amcrest-' messages - if vendor != 'amcrest': + if not vendor or vendor != 'amcrest': return # ok, it's for us, lets announce it @@ -463,7 +460,8 @@ class AmcrestMqtt(object): components[self.get_slug(device_id, 'event_camera')] = \ components[self.get_slug(device_id, 'snapshot_camera')] | { 'name': 'Motion snapshot', - 'topic': self.get_discovery_subtopic(device_id, 'camera','eventshot'), + 'platform': 'image', + 'image_topic': self.get_discovery_subtopic(device_id, 'camera','eventshot'), 'unique_id': self.get_slug(device_id, 'eventshot_camera'), } device_states['camera'] = {'snapshot': None, 'eventshot': None} @@ -599,13 +597,15 @@ class AmcrestMqtt(object): for topic in ['state','storage','motion','human','doorbell','event','recording','privacy_mode','motion_detection']: if topic in device_states: + publish_topic = self.get_discovery_topic(device_id, topic) payload = json.dumps(device_states[topic]) if isinstance(device_states[topic], dict) else device_states[topic] - self.mqttc.publish(self.get_discovery_topic(device_id, topic), payload, qos=self.mqtt_config['qos'], retain=True) + self.mqttc.publish(publish_topic, payload, qos=self.mqtt_config['qos'], retain=True) for shot_type in ['snapshot','eventshot']: if shot_type in device_states['camera'] and device_states['camera'][shot_type] is not None: + publish_topic = self.get_discovery_subtopic(device_id, 'camera',shot_type) payload = device_states['camera'][shot_type] - result = self.mqttc.publish(self.get_discovery_subtopic(device_id, 'camera',shot_type), payload, qos=self.mqtt_config['qos'], retain=True) + result = self.mqttc.publish(publish_topic, payload, qos=self.mqtt_config['qos'], retain=True) def publish_device_discovery(self, device_id): device_config = self.configs[device_id] @@ -763,8 +763,8 @@ class AmcrestMqtt(object): if event in ['motion','human','doorbell'] and device_states['privacy_mode'] == 'on': device_states['privacy_mode'] = 'off' else: - self.logger.info(f'Got "other" event for "{self.get_device_name(device_id)}": {event} - {payload}') - device_states['event'] = f'{event} - {payload}' + self.logger.info(f'Got {{{event}: {payload}}} for "{self.get_device_name(device_id)}"') + device_states['event'] = f'{event}: {payload}' self.publish_device_state(device_id) except Exception as err: @@ -782,23 +782,23 @@ class AmcrestMqtt(object): async def collect_storage_info(self): while self.running == True: self.refresh_storage_all_devices() - await asyncio.sleep(self.storage_update_interval) + if self.running: await asyncio.sleep(self.storage_update_interval) async def collect_events(self): while self.running == True: await self.collect_all_device_events() - await asyncio.sleep(1) + if self.running: await asyncio.sleep(1) async def check_event_queue(self): while self.running == True: self.check_for_events() - await asyncio.sleep(1) + if self.running: await asyncio.sleep(1) async def collect_snapshots(self): while self.running == True: await self.amcrestc.collect_all_device_snapshots() self.refresh_snapshot_all_devices() - await asyncio.sleep(self.snapshot_update_interval) + if self.running: await asyncio.sleep(self.snapshot_update_interval) # main loop async def main_loop(self): @@ -819,7 +819,11 @@ class AmcrestMqtt(object): ) try: - await asyncio.gather(*tasks, return_exceptions=True) + results = await asyncio.gather(*tasks, return_exceptions=True) + for result in results: + if isinstance(result, Exception): + self.running = False + self.logger.error(f'Caught exception: {err}', exc_info=True) except asyncio.CancelledError: exit(1) except Exception as err: