lots of change, config changes, added snapshots and webrtc link capability

pull/106/head
Jeff Culverhouse 11 months ago
parent 69f1368d7a
commit 9ffed93fac

6
.gitignore vendored

@ -11,6 +11,8 @@ dist/
# Environments # Environments
venv/ venv/
# Config testing # Config testing and notes
config
config/
config.yaml config.yaml
NOTES

@ -76,4 +76,6 @@ useful to myself and others, not for any financial gain - but any token of appre
<a href="https://buymeacoffee.com/weirdtangent">Buy Me A Coffee</a> <a href="https://buymeacoffee.com/weirdtangent">Buy Me A Coffee</a>
### How Happy am I?
<img src="https://github.com/weirdtangent/amcrest2mqtt/actions/workflows/deploy.yaml/badge.svg" /> <img src="https://github.com/weirdtangent/amcrest2mqtt/actions/workflows/deploy.yaml/badge.svg" />

@ -1 +1 @@
0.99.19 0.99.20

@ -1,6 +1,7 @@
from amcrest import AmcrestCamera, AmcrestError from amcrest import AmcrestCamera, AmcrestError
import asyncio import asyncio
from asyncio import timeout from asyncio import timeout
import base64
from datetime import datetime from datetime import datetime
import httpx import httpx
import logging import logging
@ -14,6 +15,7 @@ class AmcrestAPI(object):
# we don't want to get the .info HTTP Request logs from Amcrest # we don't want to get the .info HTTP Request logs from Amcrest
logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("amcrest.http").setLevel(logging.ERROR)
self.last_call_date = '' self.last_call_date = ''
self.timezone = config['timezone'] self.timezone = config['timezone']
@ -97,6 +99,8 @@ class AmcrestAPI(object):
}, },
} }
# Storage stats -------------------------------------------------------------------------------
def get_device_storage_stats(self, device_id): def get_device_storage_stats(self, device_id):
try: try:
storage = self.devices[device_id]["camera"].storage_all storage = self.devices[device_id]["camera"].storage_all
@ -104,19 +108,36 @@ class AmcrestAPI(object):
self.logger.error(f'Problem connecting with camera to get storage stats: {err}') self.logger.error(f'Problem connecting with camera to get storage stats: {err}')
return {} return {}
return { return {
'last_update': str(datetime.now(ZoneInfo(self.timezone))), 'last_update': str(datetime.now(ZoneInfo(self.timezone))),
'used_percent': str(storage['used_percent']), 'used_percent': str(storage['used_percent']),
'used': to_gb(storage['used']), 'used': to_gb(storage['used']),
'total': to_gb(storage['total']), 'total': to_gb(storage['total']),
} }
async def collect_all_device_events(self): # Snapshots -----------------------------------------------------------------------------------
async def collect_all_device_snapshots(self):
tasks = [self.get_snapshot_from_device(device_id) for device_id in self.devices]
await asyncio.gather(*tasks)
async def get_snapshot_from_device(self, device_id):
try: try:
tasks = [self.get_events_from_device(device_id) for device_id in self.devices] image = await self.devices[device_id]["camera"].async_snapshot()
await asyncio.gather(*tasks) self.devices[device_id]['snapshot'] = base64.b64encode(image)
self.logger.debug(f'Processed snapshot from ({device_id}) {len(image)} bytes raw, and {len(self.devices[device_id]['snapshot'])} bytes base64')
except Exception as err: except Exception as err:
self.logger.error(f'collect_all_device_events: {err}') self.logger.error(f'Failed to get snapshot from device ({device_id})')
pass
def get_snapshot(self, device_id):
return self.devices[device_id]['snapshot'] if 'snapshot' in self.devices[device_id] else None
# Events --------------------------------------------------------------------------------------
async def collect_all_device_events(self):
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): async def get_events_from_device(self, device_id):
try: try:
@ -128,38 +149,34 @@ class AmcrestAPI(object):
self.reset_connection(device_id) self.reset_connection(device_id)
async def process_device_event(self, device_id, code, payload): async def process_device_event(self, device_id, code, payload):
try: config = self.devices[device_id]['config']
config = self.devices[device_id]['config']
self.logger.debug(f'Event on {config["host"]} - {code}: {payload}')
self.logger.debug(f'Event on {config["host"]} - {code}: {payload}')
if ((code == "ProfileAlarmTransmit" and config["is_ad110"])
if ((code == "ProfileAlarmTransmit" and config["is_ad110"]) or (code == "VideoMotion" and not config["is_ad110"])):
or (code == "VideoMotion" and not config["is_ad110"])): motion_payload = "on" if payload["action"] == "Start" else "off"
motion_payload = "on" if payload["action"] == "Start" else "off" self.events.append({ 'device_id': device_id, 'event': 'motion', 'payload': motion_payload })
self.events.append({ 'device_id': device_id, 'event': 'motion', 'payload': motion_payload }) elif code == "CrossRegionDetection" and payload["data"]["ObjectType"] == "Human":
elif code == "CrossRegionDetection" and payload["data"]["ObjectType"] == "Human": human_payload = "on" if payload["action"] == "Start" else "off"
human_payload = "on" if payload["action"] == "Start" else "off" self.events.append({ 'device_id': device_id, 'event': 'human', 'payload': human_payload })
self.events.append({ 'device_id': device_id, 'event': 'human', 'payload': human_payload }) elif code == "_DoTalkAction_":
elif code == "_DoTalkAction_": doorbell_payload = "on" if payload["data"]["Action"] == "Invite" else "off"
doorbell_payload = "on" if payload["data"]["Action"] == "Invite" else "off" self.events.append({ 'device_id': device_id, 'event': 'doorbell', 'payload': doorbell_payload })
self.events.append({ 'device_id': device_id, 'event': 'doorbell', 'payload': doorbell_payload }) elif code == "NewFile":
elif code == "NewFile": file_payload = { 'file': payload["data"]["File"], 'size': payload["data"]["Size"] }
file_payload = { 'file': payload["data"]["File"], 'size': payload["data"]["Size"] } self.events.append({ 'device_id': device_id, 'event': 'recording', 'payload': file_payload })
self.events.append({ 'device_id': device_id, 'event': 'recording', 'payload': file_payload }) # lets ignore the event codes we don't care about (for now)
# lets ignore the event codes we don't care about (for now) elif code == "VideoMotionInfo":
elif code == "VideoMotionInfo": pass
pass elif code == "TimeChange":
elif code == "TimeChange": pass
pass elif code == "NTPAdjustTime":
elif code == "NTPAdjustTime": pass
pass elif code == "RtspSessionDisconnect":
elif code == "RtspSessionDisconnect": pass
pass else:
else: self.events.append({ 'device_id': device_id, 'event': code , 'payload': payload['action'] })
self.events.append({ 'device_id': device_id, 'event': code , 'payload': payload['action'] })
except Exception as err:
self.logger.error(f'process_device_event: {err}', exc_info=True)
def get_next_event(self): def get_next_event(self):
return self.events.pop(0) if len(self.events) > 0 else None return self.events.pop(0) if len(self.events) > 0 else None

@ -26,7 +26,8 @@ class AmcrestMqtt(object):
self.timezone = config['timezone'] self.timezone = config['timezone']
self.version = config['version'] self.version = config['version']
self.device_update_interval = config['amcrest'].get('device_update_interval', 600) self.storage_update_interval = config['amcrest'].get('storage_update_interval', 900)
self.snapshot_update_interval = config['amcrest'].get('snapshot_update_interval', 300)
self.client_id = self.get_new_client_id() self.client_id = self.get_new_client_id()
self.service_name = self.mqtt_config['prefix'] + ' service' self.service_name = self.mqtt_config['prefix'] + ' service'
@ -87,38 +88,54 @@ class AmcrestMqtt(object):
self.logger.error('Failed to understand MQTT message, ignoring') self.logger.error('Failed to understand MQTT message, ignoring')
return return
self.logger.info(f'Got MQTT message for {topic} - {payload}')
# we might get: # we might get:
# device/component/set # */service/set
# device/component/set/attribute # */service/set/attribute
# homeassistant/device/component/set # */device/component/set
# homeassistant/device/component/set/attribute # */device/component/set/attribute
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:
if components[-1] == 'set':
vendor, device_id = components[-2].split('-')
elif components[-2] == 'set':
vendor, device_id = components[-3].split('-')
else: else:
if components[-1] == 'set': self.logger.error(f'UNKNOWN MQTT MESSAGE STRUCTURE: {topic}')
vendor, device_id = components[-2].split('-') return
elif components[-2] == 'set':
vendor, device_id = components[-3].split('-') # of course, we only care about our 'amcrest-<serial>' messages
else: if vendor != 'amcrest':
self.logger.error(f'UNKNOWN MQTT MESSAGE STRUCTURE: {topic}') return
return
# of course, we only care about our 'amcrest-<serial>' messages # ok, it's for us, lets announce it
if vendor != 'amcrest': self.logger.debug(f'Incoming MQTT message for {topic} - {payload}')
# if we only got back a scalar value, lets turn it into a dict with
# the attribute name after `/set/` in the command topic
if not isinstance(payload, dict) and attribute:
payload = { attribute: payload }
# if we just started, we might get messages immediately, lets
# wait up to 3 min for devices to show up before we ignore the message
checks = 0
while device_id not in self.configs:
checks += 1
# we'll try for 3 min, and then give up
if checks > 36:
self.logger.warn(f"Got MQTT message for a device we don't know: {device_id}")
return return
# ok, lets format the device_id and send to amcrest time.sleep(5)
# for Amcrest devices, we use the string as-is (after the vendor name)
self.send_command(device_id, payload) self.logger.info(f'Got MQTT message for: {self.configs[device_id]["device"]["name"]} - {payload}')
except Exception as err:
self.logger.error(f'Failed to understand MQTT message slug ({topic}): {err}, ignoring') # ok, lets format the device_id (not needed) and send to amcrest
return self.send_command(device_id, payload)
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)
@ -204,24 +221,41 @@ class AmcrestMqtt(object):
return f"{self.mqtt_config['prefix']}/{self.get_component_slug(device_id)}/{topic}" return f"{self.mqtt_config['prefix']}/{self.get_component_slug(device_id)}/{topic}"
return f"{self.mqtt_config['discovery_prefix']}/device/{self.get_component_slug(device_id)}/{topic}" return f"{self.mqtt_config['discovery_prefix']}/device/{self.get_component_slug(device_id)}/{topic}"
def get_discovery_topic(self, device_id, topic):
if 'homeassistant' not in self.mqtt_config or self.mqtt_config['homeassistant'] == False:
return f"{self.mqtt_config['prefix']}/{self.get_component_slug(device_id)}/{topic}"
return f"{self.mqtt_config['discovery_prefix']}/device/{self.get_component_slug(device_id)}/{topic}"
def get_discovery_subtopic(self, device_id, topic, subtopic):
if 'homeassistant' not in self.mqtt_config or self.mqtt_config['homeassistant'] == False:
return f"{self.mqtt_config['prefix']}/{self.get_component_slug(device_id)}/{topic}/{subtopic}"
return f"{self.mqtt_config['discovery_prefix']}/device/{self.get_component_slug(device_id)}/{topic}/{subtopic}"
# Service Device ------------------------------------------------------------------------------ # Service Device ------------------------------------------------------------------------------
def publish_service_state(self): def publish_service_state(self):
self.mqttc.publish( if 'service' not in self.configs:
self.get_discovery_topic('service','availability'), self.configs['service'] = {
'online', 'availability': 'online',
qos=self.mqtt_config['qos'], 'state': { 'state': 'ON' },
retain=True 'intervals': {},
) }
self.mqttc.publish(
self.get_discovery_topic('service','state'), service_states = self.configs['service']
json.dumps({
'status': 'online', # update states
'device_refresh': self.device_update_interval, service_states['state'] = {
}), 'state': 'ON',
qos=self.mqtt_config['qos'], }
retain=True service_states['intervals'] = {
) 'storage_refresh': self.storage_update_interval,
'snapshot_refresh': self.snapshot_update_interval,
}
for topic in ['state','availability','intervals']:
if topic in service_states:
payload = json.dumps(service_states[topic]) if isinstance(service_states[topic], dict) else service_states[topic]
self.mqttc.publish(self.get_discovery_topic('service', topic), payload, qos=self.mqtt_config['qos'], retain=True)
def publish_service_device(self): def publish_service_device(self):
state_topic = self.get_discovery_topic('service', 'state') state_topic = self.get_discovery_topic('service', 'state')
@ -251,26 +285,36 @@ class AmcrestMqtt(object):
'name': 'Service', 'name': 'Service',
'platform': 'binary_sensor', 'platform': 'binary_sensor',
'schema': 'json', 'schema': 'json',
'payload_on': 'online', 'payload_on': 'ON',
'payload_off': 'offline', 'payload_off': 'OFF',
'icon': 'mdi:language-python', 'icon': 'mdi:language-python',
'state_topic': state_topic, 'state_topic': state_topic,
'availability_topic': availability_topic, 'value_template': '{{ value_json.state }}',
'value_template': '{{ value_json.status }}',
'unique_id': 'amcrest_service_status', 'unique_id': 'amcrest_service_status',
}, },
self.service_slug + '_device_refresh': { self.service_slug + '_storage_refresh': {
'name': 'Device Refresh Interval', 'name': 'Storage Refresh Interval',
'platform': 'number', 'platform': 'number',
'schema': 'json', 'schema': 'json',
'icon': 'mdi:numeric', 'icon': 'mdi:numeric',
'min': 10, 'min': 10,
'max': 3600, 'max': 3600,
'state_topic': state_topic, 'state_topic': self.get_discovery_topic('service', 'intervals'),
'command_topic': self.get_command_topic('service', 'device_refresh'), 'command_topic': self.get_command_topic('service', 'storage_refresh'),
'availability_topic': availability_topic, 'value_template': '{{ value_json.storage_refresh }}',
'value_template': '{{ value_json.device_refresh }}', 'unique_id': 'amcrest_service_storage_refresh',
'unique_id': 'amcrest_service_device_refresh', },
self.service_slug + '_snapshot_refresh': {
'name': 'Snapshot Refresh Interval',
'platform': 'number',
'schema': 'json',
'icon': 'mdi:numeric',
'min': 10,
'max': 3600,
'state_topic': self.get_discovery_topic('service', 'intervals'),
'command_topic': self.get_command_topic('service', 'snapshot_refresh'),
'value_template': '{{ value_json.snapshot_refresh }}',
'unique_id': 'amcrest_service_snapshot_refresh',
}, },
}, },
}), }),
@ -324,13 +368,13 @@ class AmcrestMqtt(object):
'support_url': 'https://github.com/weirdtangent/amcrest2mqtt', 'support_url': 'https://github.com/weirdtangent/amcrest2mqtt',
} }
self.add_components_to_device(device_id) self.add_components_to_device(device_id)
if first: if first:
self.logger.info(f'Adding device: "{config['device_name']}" [Amcrest {config["device_type"]}] ({device_id})') self.logger.info(f'Adding device: "{config['device_name']}" [Amcrest {config["device_type"]}] ({device_id})')
self.publish_device_discovery(device_id) self.publish_device_discovery(device_id)
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 # device discovery sent, now it is save to add these to the
# dict (so they aren't included in device discovery object itself) # dict (so they aren't included in device discovery object itself)
self.devices[device_id]['state'] = { self.devices[device_id]['state'] = {
@ -339,7 +383,6 @@ class AmcrestMqtt(object):
'serial_number': config['serial_number'], 'serial_number': config['serial_number'],
'sw_version': config['software_version'], 'sw_version': config['software_version'],
} }
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})')
@ -388,6 +431,23 @@ class AmcrestMqtt(object):
retain=True retain=True
) )
components[self.get_slug(device_id, 'camera')] = {
'name': 'Camera',
'platform': 'camera',
'topic': self.get_discovery_subtopic(device_id, 'camera','snapshot'),
'image_encoding': 'b64',
'state_topic': device['state_topic'],
'value_template': '{{ value_json.state }}',
'unique_id': self.get_slug(device_id, 'camera'),
}
if 'webrtc' in self.amcrest_config:
webrtc_config = self.amcrest_config['webrtc']
rtc_host = webrtc_config['host']
rtc_port = webrtc_config['port'] if 'port' in webrtc_config else 1984
rtc_source = webrtc_config['sources'].pop(0)
rtc_url = f'http://{rtc_host}:{rtc_port}/api/frame.jpeg?src={rtc_source}'
components[self.get_slug(device_id, 'camera')]['entity_picture'] = rtc_url
components[self.get_slug(device_id, 'motion')] = { components[self.get_slug(device_id, 'motion')] = {
'name': 'Motion', 'name': 'Motion',
'platform': 'binary_sensor', 'platform': 'binary_sensor',
@ -476,12 +536,18 @@ class AmcrestMqtt(object):
def publish_device_state(self, device_id): def publish_device_state(self, device_id):
device = self.devices[device_id] device = self.devices[device_id]
config = self.configs[device_id]
for topic in ['state','storage','motion','human','doorbell','event','recording']: for topic in ['state','storage','motion','human','doorbell','event','recording']:
if topic in device: if topic in device:
payload = json.dumps(device[topic]) if isinstance(device[topic], dict) else device[topic] payload = json.dumps(device[topic]) if isinstance(device[topic], dict) else device[topic]
self.mqttc.publish(self.get_discovery_topic(device_id, topic), payload, qos=self.mqtt_config['qos'], retain=True) self.mqttc.publish(self.get_discovery_topic(device_id, topic), payload, qos=self.mqtt_config['qos'], retain=True)
if 'snapshot' in device['camera'] and device['camera']['snapshot'] is not None:
self.logger.info(f'Refreshing snapshot for {config["device_name"]}')
payload = device['camera']['snapshot']
result = self.mqttc.publish(self.get_discovery_subtopic(device_id, 'camera','snapshot'), payload, qos=self.mqtt_config['qos'], retain=True)
def publish_device_discovery(self, device_id): def publish_device_discovery(self, device_id):
device = self.devices[device_id] device = self.devices[device_id]
self.mqttc.publish( self.mqttc.publish(
@ -491,36 +557,43 @@ class AmcrestMqtt(object):
retain=True retain=True
) )
# setup initial states
device['state'] = { 'state': 'ON' } device['state'] = { 'state': 'ON' }
device['motion'] = 'off'
device['camera'] = { 'snapshot': None }
self.publish_device_state(device_id) # refresh * all devices -----------------------------------------------------------------------
# refresh all devices -------------------------------------------------------------------------
def refresh_all_devices(self): def refresh_storage_all_devices(self):
self.logger.info(f'Refreshing storage info for all devices (every {self.device_update_interval} sec)') self.logger.info(f'Refreshing storage info for all devices (every {self.storage_update_interval} sec)')
# refresh devices starting with the device updated the longest time ago
# sorted = sorted(self.devices.items(), key=lambda dt: (dt is None, dt)):
for device_id in self.devices: for device_id in self.devices:
# break loop if we are ending if not self.running: break
if not self.running:
break
self.refresh_device(device_id)
# other helpers ------------------------------------------------------------------------------- device = self.devices[device_id]
config = self.configs[device_id]
def refresh_device(self, device_id): # get the storage info, pull out last_update and save that to the device state
device = self.devices[device_id] storage = self.amcrestc.get_device_storage_stats(device_id)
config = self.configs[device_id] device['state']['last_update'] = storage.pop('last_update', None)
device['storage'] = storage
# get the storage info, pull out last_update and save that to the device state self.publish_service_state()
storage = self.amcrestc.get_device_storage_stats(device_id) self.publish_device_state(device_id)
device['state']['last_update'] = storage.pop('last_update', None)
device['storage'] = storage
self.publish_service_state() def refresh_snapshot_all_devices(self):
self.publish_device_state(device_id) self.logger.info(f'Collecting snapshots for all devices (every {self.snapshot_update_interval} sec)')
for device_id in self.devices:
if not self.running: break
device = self.devices[device_id]
config = self.configs[device_id]
device['camera']['snapshot'] = self.amcrestc.get_snapshot(device_id)
self.publish_service_state()
self.publish_device_state(device_id)
# send command to Amcrest -------------------------------------------------------------------- # send command to Amcrest --------------------------------------------------------------------
@ -537,9 +610,12 @@ class AmcrestMqtt(object):
def handle_service_message(self, attribute, message): def handle_service_message(self, attribute, message):
match attribute: match attribute:
case 'device_refresh': case 'storage_refresh':
self.device_update_interval = message self.storage_update_interval = message
self.logger.info(f'Updated UPDATE_INTERVAL to be {message}') self.logger.info(f'Updated STORAGE_REFRESH_INTERVAL to be {message}')
case 'snapshot_refresh':
self.snapshot_update_interval = message
self.logger.info(f'Updated SNAPSHOT_REFRESH_INTERVAL to be {message}')
case _: case _:
self.logger.info(f'IGNORED UNRECOGNIZED amcrest-service MESSAGE for {attribute}: {message}') self.logger.info(f'IGNORED UNRECOGNIZED amcrest-service MESSAGE for {attribute}: {message}')
return return
@ -563,10 +639,9 @@ class AmcrestMqtt(object):
if event in ['motion','human','doorbell','recording']: if event in ['motion','human','doorbell','recording']:
self.logger.info(f'Got event for {config["device_name"]}: {event}') self.logger.info(f'Got event for {config["device_name"]}: {event}')
device[event] = payload device[event] = payload
# otherwise, just store generically
else: else:
self.logger.info(f'Got generic event for {config["device_name"]}: {event} {payload}') self.logger.debug(f'Got "other" event for {config["device_name"]}: {event} {payload}')
device['event'] = f'{event}: {payload}' device['event'] = event
self.refresh_device(device_id) self.refresh_device(device_id)
@ -579,29 +654,32 @@ class AmcrestMqtt(object):
for task in asyncio.all_tasks(): for task in asyncio.all_tasks():
if not task.done(): task.cancel(f'{signame} received') if not task.done(): task.cancel(f'{signame} received')
async def device_loop(self): async def collect_storage_info(self):
while self.running == True: while self.running == True:
self.refresh_all_devices() self.refresh_storage_all_devices()
await asyncio.sleep(self.device_update_interval) await asyncio.sleep(self.storage_update_interval)
async def collect_events(self): async def collect_events(self):
while self.running == True: while self.running == True:
await self.amcrestc.collect_all_device_events() await self.amcrestc.collect_all_device_events()
async def process_events(self):
while self.running == True:
self.check_for_events() self.check_for_events()
await asyncio.sleep(1) 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)
# main loop # main loop
async def main_loop(self): async def main_loop(self):
await self.setup_devices() await self.setup_devices()
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
tasks = [ tasks = [
asyncio.create_task(self.device_loop()), asyncio.create_task(self.collect_storage_info()),
asyncio.create_task(self.collect_events()), asyncio.create_task(self.collect_events()),
asyncio.create_task(self.process_events()), asyncio.create_task(self.collect_snapshots()),
] ]
# setup signal handling for tasks # setup signal handling for tasks

@ -54,7 +54,8 @@ except:
'port': int(os.getenv("AMCREST_PORT") or 80), 'port': int(os.getenv("AMCREST_PORT") or 80),
'username': os.getenv("AMCREST_USERNAME") or "admin", 'username': os.getenv("AMCREST_USERNAME") or "admin",
'password': os.getenv("AMCREST_PASSWORD"), 'password': os.getenv("AMCREST_PASSWORD"),
'device_update_interval': int(os.getenv("DEVICE_UPDATE_INTERVAL") or 600), 'storage_update_interval': int(os.getenv("STORAGE_UPDATE_INTERVAL") or 900),
'snapshot_update_interval': int(os.getenv("SNAPSHOT_UPDATE_INTERVAL") or 300),
}, },
'debug': True if os.getenv('DEBUG') else False, 'debug': True if os.getenv('DEBUG') else False,
'hide_ts': True if os.getenv('HIDE_TS') else False, 'hide_ts': True if os.getenv('HIDE_TS') else False,
@ -63,6 +64,9 @@ except:
} }
config['version'] = version config['version'] = version
config['configpath'] = os.path.dirname(configpath) config['configpath'] = os.path.dirname(configpath)
if 'username' not in config['mqtt']: config['mqtt']['username'] = ''
if 'password' not in config['mqtt']: config['mqtt']['password'] = ''
if 'qos' not in config['mqtt']: config['mqtt']['qos'] = 0
if 'timezone' not in config: config['timezone'] = 'UTC' if 'timezone' not in config: config['timezone'] = 'UTC'
if 'debug' not in config: config['debug'] = os.getenv('DEBUG') or False if 'debug' not in config: config['debug'] = os.getenv('DEBUG') or False
@ -91,6 +95,19 @@ if config['amcrest']['host_count'] != config['amcrest']['name_count']:
exit(1) exit(1)
logger.info(f'Found {config["amcrest"]["host_count"]} host(s) defined to monitor') logger.info(f'Found {config["amcrest"]["host_count"]} host(s) defined to monitor')
if 'webrtc' in config['amcrest']:
webrtc = config['amcrest']['webrtc']
if 'host' not in webrtc:
logger.error('Missing HOST in webrtc config')
exit(1)
if 'sources' not in webrtc:
logger.error('Missing SOURCES in webrtc config')
exit(1)
config['amcrest']['webrtc_sources_count'] = len(config['amcrest']['webrtc']['sources'])
if config['amcrest']['host_count'] != config['amcrest']['webrtc_sources_count']:
logger.error('The AMCREST_HOSTS and AMCREST_WEBRTC_SOURCES must have the same number of space-delimited hosts/names')
exit(1)
if config['amcrest']['password'] is None: if config['amcrest']['password'] is None:
logger.error('Please set the AMCREST_PASSWORD environment variable') logger.error('Please set the AMCREST_PASSWORD environment variable')
exit(1) exit(1)

@ -22,7 +22,13 @@ amcrest:
port: 80 port: 80
username: admin username: admin
password: password password: password
storage_poll_interval: 60 storage_update_interval: 900
snapshot_update_interval: 60
webrtc:
url: http://webrtc.local.com:1984/api/frame.jpeg?src=
sources:
- FrontYard
- Patio
timezone: America/New_York timezone: America/New_York
hide_ts: False hide_ts: False

Loading…
Cancel
Save