diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3cd7640..429a6c7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,17 +12,16 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 - name: Bump version uses: remorses/bump-version@js id: version with: version_file: ./VERSION - github_token: ${{ secrets.GITHUB_TOKEN }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Checkout code - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 with: ref: main - name: Set up QEMU @@ -30,14 +29,14 @@ jobs: with: platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1.5.1 + uses: docker/setup-buildx-action@v1.6.0 - name: Login to DockerHub - uses: docker/login-action@v1.10.0 + uses: docker/login-action@v1.12.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push - uses: docker/build-push-action@v2.6.1 + uses: docker/build-push-action@v2.7.0 with: context: . push: true diff --git a/Dockerfile b/Dockerfile index 2ff3e33..bebaff0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ COPY requirements.txt / RUN pip install --no-warn-script-location --prefix=/install -r /requirements.txt FROM base +STOPSIGNAL SIGINT COPY --from=builder /install /usr/local COPY src /app COPY VERSION /app diff --git a/README.md b/README.md index dd72d97..bd6686e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ It supports the following environment variables: - `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) +- `DEVICE_NAME` (optional) - override the default device name used in the Amcrest app It exposes events to the following topics: @@ -29,6 +30,7 @@ It exposes events to the following topics: - `amcrest2mqtt/[SERIAL_NUMBER]/doorbell` - doorbell status (if AD110 or AD410) - `amcrest2mqtt/[SERIAL_NUMBER]/human` - human detection (if AD410) - `amcrest2mqtt/[SERIAL_NUMBER]/motion` - motion events (if supported) +- `amcrest2mqtt/[SERIAL_NUMBER]/config` - device configuration information ## Device Support @@ -63,7 +65,7 @@ services: ### Multiple Devices -The app will not support multiple devices. You can run multiple instances of the app if you need to expose events for multiple devies. +The app will not support multiple devices. You can run multiple instances of the app if you need to expose events for multiple devices. ### Non-Docker Environments diff --git a/VERSION b/VERSION index e5a4a5e..758a46e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.9 \ No newline at end of file +1.0.15 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 58b0c33..4a686c3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -amcrest==1.8.0 -paho-mqtt==1.5.1 +amcrest==1.9.3 +paho-mqtt==1.6.1 python-slugify==5.0.2 diff --git a/src/amcrest2mqtt.py b/src/amcrest2mqtt.py index c5b8e6e..d7abb3c 100644 --- a/src/amcrest2mqtt.py +++ b/src/amcrest2mqtt.py @@ -17,8 +17,10 @@ amcrest_host = os.getenv("AMCREST_HOST") amcrest_port = int(os.getenv("AMCREST_PORT") or 80) amcrest_username = os.getenv("AMCREST_USERNAME") or "admin" amcrest_password = os.getenv("AMCREST_PASSWORD") + storage_poll_interval = int(os.getenv("STORAGE_POLL_INTERVAL") or 3600) camera_ping_enabled = os.getenv("CAMERA_PING_ENABLED") == "true" +device_name = os.getenv("DEVICE_NAME") mqtt_host = os.getenv("MQTT_HOST") or "localhost" mqtt_qos = int(os.getenv("MQTT_QOS") or 0) @@ -93,12 +95,16 @@ def refresh_storage_sensors(): try: storage = camera.storage_all + mqtt_publish(topics["storage_used_percent"], str(storage["used_percent"])) - mqtt_publish(topics["storage_used"], str(storage["used"][0])) - mqtt_publish(topics["storage_total"], str(storage["total"][0])) + mqtt_publish(topics["storage_used"], to_gb(storage["used"])) + mqtt_publish(topics["storage_total"], to_gb(storage["total"])) except AmcrestError as error: log(f"Error fetching storage information {error}", level="WARNING") +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") @@ -150,9 +156,16 @@ try: is_ad110 = device_type == "AD110" is_ad410 = device_type == "AD410" is_doorbell = is_ad110 or is_ad410 - serial_number = camera.serial_number.strip() + serial_number = camera.serial_number + + if not isinstance(serial_number, str): + log(f"Error fetching serial number", level="ERROR") + exit_gracefully(1) + sw_version = camera.software_information[0].replace("version=", "").strip() - device_name = camera.machine_name.replace("name=", "").strip() + if not device_name: + device_name = camera.machine_name.replace("name=", "").strip() + device_slug = slugify(device_name, separator="_") except AmcrestError as error: log(f"Error fetching camera details", level="ERROR") @@ -174,13 +187,27 @@ topics = { "storage_used": f"amcrest2mqtt/{serial_number}/storage/used", "storage_used_percent": f"amcrest2mqtt/{serial_number}/storage/used_percent", "storage_total": f"amcrest2mqtt/{serial_number}/storage/total", - "home_assistant": { + "home_assistant_legacy": { "doorbell": f"{home_assistant_prefix}/binary_sensor/amcrest2mqtt-{serial_number}/{device_slug}_doorbell/config", "human": f"{home_assistant_prefix}/binary_sensor/amcrest2mqtt-{serial_number}/{device_slug}_human/config", "motion": f"{home_assistant_prefix}/binary_sensor/amcrest2mqtt-{serial_number}/{device_slug}_motion/config", "storage_used": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/{device_slug}_storage_used/config", "storage_used_percent": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/{device_slug}_storage_used_percent/config", "storage_total": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/{device_slug}_storage_total/config", + "version": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/{device_slug}_version/config", + "host": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/{device_slug}_host/config", + "serial_number": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/{device_slug}_serial_number/config", + }, + "home_assistant": { + "doorbell": f"{home_assistant_prefix}/binary_sensor/amcrest2mqtt-{serial_number}/doorbell/config", + "human": f"{home_assistant_prefix}/binary_sensor/amcrest2mqtt-{serial_number}/human/config", + "motion": f"{home_assistant_prefix}/binary_sensor/amcrest2mqtt-{serial_number}/motion/config", + "storage_used": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/storage_used/config", + "storage_used_percent": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/storage_used_percent/config", + "storage_total": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/storage_total/config", + "version": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/version/config", + "host": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/host/config", + "serial_number": f"{home_assistant_prefix}/sensor/amcrest2mqtt-{serial_number}/serial_number/config", }, } @@ -236,6 +263,9 @@ if home_assistant: } if is_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"]["doorbell"], base_config @@ -243,13 +273,15 @@ if home_assistant: "state_topic": topics["doorbell"], "payload_on": "on", "payload_off": "off", - "name": f"{device_name} Doorbell", + "icon": "mdi:doorbell", + "name": doorbell_name, "unique_id": f"{serial_number}.doorbell", }, json=True, ) if is_ad410: + mqtt_publish(topics["home_assistant_legacy"]["human"], "") mqtt_publish( topics["home_assistant"]["human"], base_config @@ -264,6 +296,7 @@ if home_assistant: json=True, ) + mqtt_publish(topics["home_assistant_legacy"]["motion"], "") mqtt_publish( topics["home_assistant"]["motion"], base_config @@ -278,7 +311,56 @@ if home_assistant: json=True, ) + mqtt_publish(topics["home_assistant_legacy"]["version"], "") + mqtt_publish( + topics["home_assistant"]["version"], + base_config + | { + "state_topic": topics["config"], + "value_template": "{{ value_json.sw_version }}", + "icon": "mdi:package-up", + "name": f"{device_name} Version", + "unique_id": f"{serial_number}.version", + "entity_category": "diagnostic", + "enabled_by_default": False + }, + json=True, + ) + + mqtt_publish(topics["home_assistant_legacy"]["serial_number"], "") + mqtt_publish( + topics["home_assistant"]["serial_number"], + base_config + | { + "state_topic": topics["config"], + "value_template": "{{ value_json.serial_number }}", + "icon": "mdi:alphabetical-variant", + "name": f"{device_name} Serial Number", + "unique_id": f"{serial_number}.serial_number", + "entity_category": "diagnostic", + "enabled_by_default": False + }, + json=True, + ) + + mqtt_publish(topics["home_assistant_legacy"]["host"], "") + mqtt_publish( + topics["home_assistant"]["host"], + base_config + | { + "state_topic": topics["config"], + "value_template": "{{ value_json.host }}", + "icon": "mdi:ip-network", + "name": f"{device_name} Host", + "unique_id": f"{serial_number}.host", + "entity_category": "diagnostic", + "enabled_by_default": False + }, + json=True, + ) + if storage_poll_interval > 0: + mqtt_publish(topics["home_assistant_legacy"]["storage_used_percent"], "") mqtt_publish( topics["home_assistant"]["storage_used_percent"], base_config @@ -287,11 +369,14 @@ if home_assistant: "unit_of_measurement": "%", "icon": "mdi:micro-sd", "name": f"{device_name} Storage Used %", + "object_id": f"{device_slug}_storage_used_percent", "unique_id": f"{serial_number}.storage_used_percent", + "entity_category": "diagnostic", }, json=True, ) + mqtt_publish(topics["home_assistant_legacy"]["storage_used"], "") mqtt_publish( topics["home_assistant"]["storage_used"], base_config @@ -301,10 +386,12 @@ if home_assistant: "icon": "mdi:micro-sd", "name": f"{device_name} Storage Used", "unique_id": f"{serial_number}.storage_used", + "entity_category": "diagnostic", }, json=True, ) + mqtt_publish(topics["home_assistant_legacy"]["storage_total"], "") mqtt_publish( topics["home_assistant"]["storage_total"], base_config @@ -314,6 +401,7 @@ if home_assistant: "icon": "mdi:micro-sd", "name": f"{device_name} Storage Total", "unique_id": f"{serial_number}.storage_total", + "entity_category": "diagnostic", }, json=True, ) @@ -326,6 +414,7 @@ mqtt_publish(topics["config"], { "device_name": device_name, "sw_version": sw_version, "serial_number": serial_number, + "host": amcrest_host, }, json=True) if storage_poll_interval > 0: