Wild circus of python

pull/106/head
Jeff Culverhouse 12 months ago
parent b29e5706a9
commit c6f7887498

@ -1 +1 @@
1.0.16 1.0.17

@ -13,6 +13,11 @@ import asyncio
is_exiting = False is_exiting = False
mqtt_client = None mqtt_client = None
config = {}
cameras = {}
camera_configs = {}
camera_topics = {}
# Read env variables # Read env variables
amcrest_hosts = os.getenv("AMCREST_HOSTS") amcrest_hosts = os.getenv("AMCREST_HOSTS")
amcrest_port = int(os.getenv("AMCREST_PORT") or 80) amcrest_port = int(os.getenv("AMCREST_PORT") or 80)
@ -70,15 +75,20 @@ def mqtt_publish(topic, payload, exit_on_error=True, json=False):
def on_mqtt_disconnect(client, userdata, rc): def on_mqtt_disconnect(client, userdata, rc):
if rc != 0: if rc != 0:
log(f"Unexpected MQTT disconnection", level="ERROR") if rc == 5:
log(f"MQTT connection not authorized", level="ERROR")
else:
log(f"Unexpected MQTT disconnection: {rc}", level="ERROR")
exit_gracefully(rc, skip_mqtt=True) exit_gracefully(rc, skip_mqtt=True)
def exit_gracefully(rc, skip_mqtt=False): def exit_gracefully(rc, skip_mqtt=False):
global topics, mqtt_client global hosts, camera_topics, mqtt_client
log("Exiting app...") log("Exiting app...")
if mqtt_client is not None and mqtt_client.is_connected() and skip_mqtt == False: if mqtt_client is not None and mqtt_client.is_connected() and skip_mqtt == False:
for host in hosts:
topics = camera_topics[host]
mqtt_publish(topics["status"], "offline", exit_on_error=False) mqtt_publish(topics["status"], "offline", exit_on_error=False)
mqtt_client.disconnect() mqtt_client.disconnect()
@ -86,14 +96,16 @@ def exit_gracefully(rc, skip_mqtt=False):
# occur on a separate thread # occur on a separate thread
os._exit(rc) os._exit(rc)
def refresh_storage_sensors(): def refresh_storage_sensors(hosts, cameras, camera_topics):
global camera, topics, storage_poll_interval global storage_poll_interval
Timer(storage_poll_interval, refresh_storage_sensors).start() Timer(storage_poll_interval, refresh_storage_sensors).start()
log("Fetching storage sensors...") log("Fetching storage sensors...")
for host in hosts:
topics = camera_topics[host]
try: try:
storage = camera.storage_all storage = cameras[host].storage_all
mqtt_publish(topics["storage_used_percent"], str(storage["used_percent"])) mqtt_publish(topics["storage_used_percent"], str(storage["used_percent"]))
mqtt_publish(topics["storage_used"], to_gb(storage["used"])) mqtt_publish(topics["storage_used"], to_gb(storage["used"]))
@ -115,19 +127,21 @@ def signal_handler(sig, frame):
exit_gracefully(0) exit_gracefully(0)
def get_camera(amcrest_host, amcrest_post, amcrest_username, amcrest_password, device_name): def get_camera(amcrest_host, amcrest_post, amcrest_username, amcrest_password, device_name):
camera |= AmcrestCamera( camera = AmcrestCamera(
amcrest_host, amcrest_port, amcrest_username, amcrest_password amcrest_host, amcrest_port, amcrest_username, amcrest_password
).camera ).camera
# Fetch camera details # Fetch camera details
log("Fetching camera details...") log("Fetching camera details...")
camera_config = {}
camera_config["device_name"] = device_name
camera_config["amcrest_host"] = amcrest_host camera_config["amcrest_host"] = amcrest_host
try: try:
camera_config["device_type"] = device_type = camera.device_type.replace("type=", "").strip() camera_config["device_type"] = device_type = camera.device_type.replace("type=", "").strip()
is_ad110 = camera_config["device_type"] == "AD110" is_ad110 = camera_config["device_type"] == "AD110"
is_ad410 = camera_config["device_type"] == "AD410" camera_config["is_ad410"] = is_ad410 = camera_config["device_type"] == "AD410"
is_doorbell = is_ad110 or is_ad410 camera_config["is_doorbell"] = is_doorbell = is_ad110 or is_ad410
camera_config["serial_number"] = serial_number = camera.serial_number camera_config["serial_number"] = serial_number = camera.serial_number
if not isinstance(serial_number, str): if not isinstance(serial_number, str):
@ -142,7 +156,7 @@ def get_camera(amcrest_host, amcrest_post, amcrest_username, amcrest_password, d
if not device_name: if not device_name:
device_name = camera.machine_name.replace("name=", "").strip() device_name = camera.machine_name.replace("name=", "").strip()
device_slug = slugify(device_name, separator="_") camera_config["device_slug"] = device_slug = slugify(device_name, separator="_")
except AmcrestError as error: except AmcrestError as error:
log(f"Error fetching camera details", level="ERROR") log(f"Error fetching camera details", level="ERROR")
exit_gracefully(1) exit_gracefully(1)
@ -152,8 +166,10 @@ def get_camera(amcrest_host, amcrest_post, amcrest_username, amcrest_password, d
log(f"Software version: {amcrest_version}") log(f"Software version: {amcrest_version}")
log(f"Device name: {device_name}") log(f"Device name: {device_name}")
# MQTT topics setup = {}
topics = { setup["camera"] = camera
setup["camera_config"] = camera_config
setup["camera_topic"] = {
"config": f"amcrest2mqtt/{serial_number}/config", "config": f"amcrest2mqtt/{serial_number}/config",
"status": f"amcrest2mqtt/{serial_number}/status", "status": f"amcrest2mqtt/{serial_number}/status",
"event": f"amcrest2mqtt/{serial_number}/event", "event": f"amcrest2mqtt/{serial_number}/event",
@ -187,9 +203,12 @@ def get_camera(amcrest_host, amcrest_post, amcrest_username, amcrest_password, d
}, },
} }
return camera, camera_config, topics return setup
def config_home_assistant(config, camera_config, topics): def config_home_assistant(config, camera_config, topics):
amcrest_version = config["amcrest_version"]
device_name = camera_config["device_name"]
device_slug = camera_config["device_slug"]
device_type = camera_config["device_type"] device_type = camera_config["device_type"]
serial_number = camera_config["serial_number"] serial_number = camera_config["serial_number"]
@ -206,7 +225,7 @@ def config_home_assistant(config, camera_config, topics):
}, },
} }
if is_doorbell: if camera_config["is_doorbell"]:
doorbell_name = "Doorbell" if device_name == "Doorbell" else f"{device_name} Doorbell" doorbell_name = "Doorbell" if device_name == "Doorbell" else f"{device_name} Doorbell"
mqtt_publish(topics["home_assistant_legacy"]["doorbell"], "") mqtt_publish(topics["home_assistant_legacy"]["doorbell"], "")
@ -224,7 +243,7 @@ def config_home_assistant(config, camera_config, topics):
json=True, json=True,
) )
if is_ad410: if camera_config["is_ad410"]:
mqtt_publish(topics["home_assistant_legacy"]["human"], "") mqtt_publish(topics["home_assistant_legacy"]["human"], "")
mqtt_publish( mqtt_publish(
topics["home_assistant"]["human"], topics["home_assistant"]["human"],
@ -351,14 +370,21 @@ def config_home_assistant(config, camera_config, topics):
) )
def camera_online(config, camera_config, topics): def camera_online(config, camera_config, topics):
amcrest_version = config["amcrest_version"]
host = camera_config["amcrest_host"]
device_name = camera_config["device_name"]
device_slug = camera_config["device_slug"]
device_type = camera_config["device_type"]
serial_number = camera_config["serial_number"]
mqtt_publish(topics["status"], "online") mqtt_publish(topics["status"], "online")
mqtt_publish(topics["config"], { mqtt_publish(topics["config"], {
"version": version, "version": version,
"device_type": camera_config["device_type"], "device_type": device_type,
"device_name": device_name, "device_name": device_name,
"sw_version": config["amcrest_version"], "sw_version": amcrest_version,
"serial_number": camera_config["serial_number"], "serial_number": serial_number,
"host": camera_config["amcrest_host"], "host": host,
}, json=True) }, json=True)
# Exit if any of the required vars are not provided # Exit if any of the required vars are not provided
@ -394,11 +420,14 @@ log(f"App Version: {version}")
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
# Connect to each camera, if not already # Connect to each camera, if not already
for x in range(1..host_count): for host in hosts:
if cameras[x] and camers[x].serial_number: log(f"Working host: {host}", level="INFO")
if host in cameras and camers[host].serial_number:
continue continue
cameras[x], camera_configs[x], camera_topics[x] = get_camera(hosts[x], amcrest_port, amcrest_username, amcrest_password, names[x]) setup = get_camera(host, amcrest_port, amcrest_username, amcrest_password, names.pop())
camera_count = len(cameras) cameras[host] = setup["camera"]
camera_configs[host] = setup["camera_config"]
camera_topics[host] = setup["camera_topic"]
# Connect to MQTT # Connect to MQTT
mqtt_client = mqtt.Client( mqtt_client = mqtt.Client(
@ -406,9 +435,9 @@ mqtt_client = mqtt.Client(
) )
mqtt_client.on_disconnect = on_mqtt_disconnect mqtt_client.on_disconnect = on_mqtt_disconnect
# send "will_set" for each connected camera # send "will_set" for each connected camera
for x in range(1..camera_count): for host in hosts:
if camera_topics[x]["status"]: if camera_topics[host]["status"]:
mqtt_client.will_set(camera_topics[x]["status"], payload="offline", qos=config["mqtt_qos"], retain=True) mqtt_client.will_set(camera_topics[host]["status"], payload="offline", qos=config["mqtt_qos"], retain=True)
if mqtt_tls_enabled: if mqtt_tls_enabled:
log(f"Setting up MQTT for TLS") log(f"Setting up MQTT for TLS")
@ -442,35 +471,35 @@ except ConnectionError as error:
if home_assistant: if home_assistant:
log("Writing Home Assistant discovery config...") log("Writing Home Assistant discovery config...")
for x in range(1..camera_count): for host in hosts:
if camera_topics[x]["status"]: if host in camera_topics:
config_home_assistant(camera_configs[x], camera_topics[x]) config_home_assistant(config, camera_configs[host], camera_topics[host])
# Main loop # Main loop
for x in range(1..camera_count): for host in hosts:
if camera_topics[x]["status"]: if host in camera_topics:
camera_online(config, camera_configs[x], camera_topics[x]) camera_online(config, camera_configs[host], camera_topics[host])
if storage_poll_interval > 0: if storage_poll_interval > 0:
refresh_storage_sensors() refresh_storage_sensors(hosts, cameras, camera_topics)
log("Listening for events...") log("Listening for events...")
async def main(): async def main():
try: try:
for x in range(1..camera_count): for host in hosts:
async for code, payload in cameras[x].async_event_actions("All"): async for code, payload in cameras[host].async_event_actions("All"):
if (camera_config[x].is_ad110 and code == "ProfileAlarmTransmit") or (code == "VideoMotion" and not camera_config[x].is_ad110): if (camera_configs[host].is_ad110 and code == "ProfileAlarmTransmit") or (code == "VideoMotion" and not camera_configs[host].is_ad110):
motion_payload = "on" if payload["action"] == "Start" else "off" motion_payload = "on" if payload["action"] == "Start" else "off"
mqtt_publish(camera_topics[x]["motion"], motion_payload) mqtt_publish(camera_topics[host]["motion"], 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"
mqtt_publish(camera_topics[x]["human"], human_payload) mqtt_publish(camera_topics[host]["human"], 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"
mqtt_publish(camera_topics[x]["doorbell"], doorbell_payload) mqtt_publish(camera_topics[host]["doorbell"], doorbell_payload)
mqtt_publish(camera_topics[x]["event"], payload, json=True) mqtt_publish(camera_topics[host]["event"], payload, json=True)
log(str(payload)) log(str(payload))
except AmcrestError as error: except AmcrestError as error:

Loading…
Cancel
Save