From 2e3e3afc4d24c37a20d2d539ec72e1c3982752ad Mon Sep 17 00:00:00 2001 From: Daniel Chesterton Date: Thu, 27 May 2021 23:18:48 +0100 Subject: [PATCH] Add support for human detection on AD410 and add versioning --- .github/workflows/publish.yml | 59 ++++++++++++++++++++--------------- README.md | 55 +++++++++++++++++--------------- VERSION | 1 + src/amcrest2mqtt.py | 54 +++++++++++++++++++++++--------- 4 files changed, 103 insertions(+), 66 deletions(-) create mode 100644 VERSION diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4372f0e..94f1b80 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,30 +1,39 @@ name: Publish on: - push: - branches: - - main + push: + branches: + - main jobs: - docker-publish: - name: Publish to Docker Hub - if: "!contains(github.event.head_commit.message, '[ci skip]')" - runs-on: ubuntu-20.04 - steps: - - name: Checkout code - uses: actions/checkout@v2.3.4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v1.2.0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1.3.0 - - name: Login to DockerHub - uses: docker/login-action@v1.9.0 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build and push - uses: docker/build-push-action@v2.5.0 - with: - push: true - platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x - tags: dchesterton/amcrest2mqtt:latest + docker-publish: + name: Publish to Docker Hub + if: "!contains(github.event.head_commit.message, '[ci skip]')" + runs-on: ubuntu-20.04 + steps: + - name: Bump version + uses: remorses/bump-version@js + id: version + with: + version_file: ./VERSION + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout code + uses: actions/checkout@v2.3.4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1.2.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1.3.0 + - name: Login to DockerHub + uses: docker/login-action@v1.9.0 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Build and push + uses: docker/build-push-action@v2.5.0 + with: + push: true + platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x + tags: | + dchesterton/amcrest2mqtt:latest + dchesterton/amcrest2mqtt:${{ steps.version.outputs.version }} + labels: "version=${{ steps.version.outputs.version }}" diff --git a/README.md b/README.md index ae79da3..335a4a8 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,24 @@ A simple app to expose all events generated by an Amcrest device to MQTT using t It supports the following environment variables: -- `AMCREST_HOST` (required) -- `AMCREST_PORT` (optional, default = 80) -- `AMCREST_USERNAME` (optional, default = admin) -- `AMCREST_PASSWORD` (required) -- `MQTT_USERNAME` (required) -- `MQTT_PASSWORD` (optional, default = empty password) -- `MQTT_HOST` (optional, default = 'localhost') -- `MQTT_QOS` (optional, default = 0) -- `MQTT_PORT` (optional, default = 1883) -- `HOME_ASSISTANT` (optional, default = false) -- `HOME_ASSISTANT_PREFIX` (optional, default = 'homeassistant') - -It exposes events to the `amcrest2mqtt/[SERIAL_NUMBER]/event` MQTT topic. If the device is an AD110 or AD410 doorbell it will expose -the doorbell status to `amcrest2mqtt/[SERIAL_NUMBER]/doorbell`. If the device supports motion events it will expose motion events -to `amcrest2mqtt/[SERIAL_NUMBER]/motion`. +- `AMCREST_HOST` (required) +- `AMCREST_PORT` (optional, default = 80) +- `AMCREST_USERNAME` (optional, default = admin) +- `AMCREST_PASSWORD` (required) +- `MQTT_USERNAME` (required) +- `MQTT_PASSWORD` (optional, default = empty password) +- `MQTT_HOST` (optional, default = 'localhost') +- `MQTT_QOS` (optional, default = 0) +- `MQTT_PORT` (optional, default = 1883) +- `HOME_ASSISTANT` (optional, default = false) +- `HOME_ASSISTANT_PREFIX` (optional, default = 'homeassistant') + +It exposes events to the following topics: + +- `amcrest2mqtt/[SERIAL_NUMBER]/event` - all events +- `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) ## Device Support @@ -37,17 +40,17 @@ The easiest way to run the app is via Docker Compose, e.g. ```yaml version: "3" services: - amcrest2mqtt: - container_name: amcrest2mqtt - image: dchesterton/amcrest2mqtt:latest - restart: unless-stopped - environment: - AMCREST_HOST: 192.168.0.1 - AMCREST_PASSWORD: password - MQTT_HOST: 192.168.0.2 - MQTT_USERNAME: admin - MQTT_PASSWORD: password - HOME_ASSISTANT: "true" + amcrest2mqtt: + container_name: amcrest2mqtt + image: dchesterton/amcrest2mqtt:latest + restart: unless-stopped + environment: + AMCREST_HOST: 192.168.0.1 + AMCREST_PASSWORD: password + MQTT_HOST: 192.168.0.2 + MQTT_USERNAME: admin + MQTT_PASSWORD: password + HOME_ASSISTANT: "true" ``` ## Out of Scope diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/src/amcrest2mqtt.py b/src/amcrest2mqtt.py index 96a7cde..3fc2c7b 100644 --- a/src/amcrest2mqtt.py +++ b/src/amcrest2mqtt.py @@ -27,19 +27,6 @@ home_assistant = os.getenv("HOME_ASSISTANT") == "true" home_assistant_prefix = os.getenv("HOME_ASSISTANT_PREFIX") or "homeassistant" -# Exit if any of the required vars are not provided -if amcrest_host is None: - log("Please set the AMCREST_HOST environment variable", level="ERROR") - sys.exit(1) - -if amcrest_password is None: - log("Please set the AMCREST_PASSWORD environment variable", level="ERROR") - sys.exit(1) - -if mqtt_username is None: - log("Please set the MQTT_USERNAME environment variable", level="ERROR") - sys.exit(1) - # Helper functions and callbacks def log(msg, level="INFO"): ts = datetime.now(timezone.utc).strftime("%d/%m/%Y %H:%M:%S") @@ -102,6 +89,20 @@ def signal_handler(sig, frame): is_exiting = True exit_gracefully(0) +# Exit if any of the required vars are not provided +if amcrest_host is None: + log("Please set the AMCREST_HOST environment variable", level="ERROR") + sys.exit(1) + +if amcrest_password is None: + log("Please set the AMCREST_PASSWORD environment variable", level="ERROR") + sys.exit(1) + +if mqtt_username is None: + log("Please set the MQTT_USERNAME environment variable", level="ERROR") + sys.exit(1) + +# Handle interruptions signal.signal(signal.SIGINT, signal_handler) # Connect to camera @@ -109,10 +110,13 @@ def signal_handler(sig, frame): amcrest_host, amcrest_port, amcrest_username, amcrest_password ).camera +# Fetch camera details log("Fetching camera details...") device_type = camera.device_type.replace("type=", "").strip() -is_doorbell = device_type in ["AD110", "AD410"] +is_ad110 = device_type == "AD110" +is_ad410 = device_type == "AD410" +is_doorbell = is_ad110 or is_ad410 serial_number = camera.serial_number.strip() sw_version = camera.software_information[0].replace("version=", "").strip() device_name = camera.machine_name.replace("name=", "").strip() @@ -129,11 +133,13 @@ def signal_handler(sig, frame): "event": f"amcrest2mqtt/{serial_number}/event", "motion": f"amcrest2mqtt/{serial_number}/motion", "doorbell": f"amcrest2mqtt/{serial_number}/doorbell", + "human": f"amcrest2mqtt/{serial_number}/human", "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": { "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", @@ -187,6 +193,21 @@ def signal_handler(sig, frame): json=True, ) + if is_ad410: + mqtt_publish( + topics["home_assistant"]["human"], + base_config + | { + "state_topic": topics["human"], + "payload_on": "on", + "payload_off": "off", + "device_class": "motion", + "name": f"{device_name} Human", + "unique_id": f"{serial_number}.human", + }, + json=True, + ) + mqtt_publish( topics["home_assistant"]["motion"], base_config @@ -248,9 +269,12 @@ def signal_handler(sig, frame): try: for code, payload in camera.event_actions("All", retries=5): - if (device_type == "AD110" and code == "ProfileAlarmTransmit") or (code == "VideoMotion" and device_type != "AD110"): + 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)