diff --git a/README.md b/README.md index ecf0919..33582e5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ It supports the following environment variables: - `MQTT_TLS_KEY` (required if using TLS) - path to the private key - `HOME_ASSISTANT` (optional, default = false) - `HOME_ASSISTANT_PREFIX` (optional, default = 'homeassistant') -- `STORAGE_POLL_INTERVAL` (optional, default = 3600) - how often to fetch storage data (in seconds) +- `STORAGE_POLL_INTERVAL` (optional, default = 3600) - how often to fetch storage data (in seconds) (set to 0 to disable functionality) - `DEVICE_NAME` (optional) - override the default device name used in the Amcrest app It exposes events to the following topics: diff --git a/requirements.txt b/requirements.txt index 4a686c3..b825624 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -amcrest==1.9.3 +amcrest==1.9.7 paho-mqtt==1.6.1 -python-slugify==5.0.2 +python-slugify==6.1.1 diff --git a/src/amcrest2mqtt.py b/src/amcrest2mqtt.py index 0fbba41..31054dd 100644 --- a/src/amcrest2mqtt.py +++ b/src/amcrest2mqtt.py @@ -8,6 +8,7 @@ import signal from threading import Timer import ssl +import asyncio is_exiting = False mqtt_client = None @@ -59,8 +60,8 @@ def mqtt_publish(topic, payload, exit_on_error=True, json=False): ) if msg.rc == mqtt.MQTT_ERR_SUCCESS: - msg.wait_for_publish() - return msg + msg.wait_for_publish(2) + return log(f"Error publishing MQTT message: {mqtt.error_string(msg.rc)}", level="ERROR") @@ -79,7 +80,6 @@ def exit_gracefully(rc, skip_mqtt=False): if mqtt_client is not None and mqtt_client.is_connected() and skip_mqtt == False: mqtt_publish(topics["status"], "offline", exit_on_error=False) - mqtt_client.loop_stop(force=True) mqtt_client.disconnect() # Use os._exit instead of sys.exit to ensure an MQTT disconnect event causes the program to exit correctly as they @@ -104,14 +104,6 @@ def refresh_storage_sensors(): def to_gb(total): return str(round(float(total[0]) / 1024 / 1024 / 1024, 2)) -def ping_camera(): - Timer(30, ping_camera).start() - response = os.system(f"ping -c1 -W100 {amcrest_host} >/dev/null 2>&1") - - if response != 0: - log("Ping unsuccessful", level="ERROR") - exit_gracefully(1) - def signal_handler(sig, frame): # exit immediately upon receiving a second SIGINT global is_exiting @@ -162,6 +154,10 @@ def signal_handler(sig, frame): exit_gracefully(1) sw_version = camera.software_information[0].replace("version=", "").strip() + build_version = camera.software_information[1].strip() + + amcrest_version = f"{sw_version} ({build_version})" + if not device_name: device_name = camera.machine_name.replace("name=", "").strip() @@ -172,7 +168,7 @@ def signal_handler(sig, frame): log(f"Device type: {device_type}") log(f"Serial number: {serial_number}") -log(f"Software version: {sw_version}") +log(f"Software version: {amcrest_version}") log(f"Device name: {device_name}") # MQTT topics @@ -256,7 +252,7 @@ def signal_handler(sig, frame): "manufacturer": "Amcrest", "model": device_type, "identifiers": serial_number, - "sw_version": sw_version, + "sw_version": amcrest_version, "via_device": "amcrest2mqtt", }, } @@ -411,7 +407,7 @@ def signal_handler(sig, frame): "version": version, "device_type": device_type, "device_name": device_name, - "sw_version": sw_version, + "sw_version": amcrest_version, "serial_number": serial_number, "host": amcrest_host, }, json=True) @@ -419,25 +415,26 @@ def signal_handler(sig, frame): if storage_poll_interval > 0: refresh_storage_sensors() -ping_camera() - log("Listening for events...") -try: - for code, payload in camera.event_actions("All", retries=5, timeout_cmd=(10.00, 3600)): - if (is_ad110 and code == "ProfileAlarmTransmit") or (code == "VideoMotion" and not is_ad110): - motion_payload = "on" if payload["action"] == "Start" else "off" - mqtt_publish(topics["motion"], motion_payload) - elif code == "CrossRegionDetection" and payload["data"]["ObjectType"] == "Human": - human_payload = "on" if payload["action"] == "Start" else "off" - mqtt_publish(topics["human"], human_payload) - elif code == "_DoTalkAction_": - doorbell_payload = "on" if payload["data"]["Action"] == "Invite" else "off" - mqtt_publish(topics["doorbell"], doorbell_payload) - - mqtt_publish(topics["event"], payload, json=True) - log(str(payload)) +async def main(): + try: + async for code, payload in camera.async_event_actions("All"): + if (is_ad110 and code == "ProfileAlarmTransmit") or (code == "VideoMotion" and not is_ad110): + motion_payload = "on" if payload["action"] == "Start" else "off" + mqtt_publish(topics["motion"], motion_payload) + elif code == "CrossRegionDetection" and payload["data"]["ObjectType"] == "Human": + human_payload = "on" if payload["action"] == "Start" else "off" + mqtt_publish(topics["human"], human_payload) + elif code == "_DoTalkAction_": + doorbell_payload = "on" if payload["data"]["Action"] == "Invite" else "off" + mqtt_publish(topics["doorbell"], doorbell_payload) + + mqtt_publish(topics["event"], payload, json=True) + log(str(payload)) -except AmcrestError as error: - log(f"Amcrest error {error}", level="ERROR") - exit_gracefully(1) + except AmcrestError as error: + log(f"Amcrest error: {error}", level="ERROR") + exit_gracefully(1) + +asyncio.run(main())