Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dext0r committed Dec 9, 2023
0 parents commit 68b4741
Show file tree
Hide file tree
Showing 51 changed files with 7,873 additions and 0 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CI

on:
push:
branches:
- '*'
tags-ignore:
- 'v*'
pull_request:

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- uses: actions/setup-python@v4
with:
python-version: '3.11'

- uses: pre-commit/[email protected]

validate:
name: Validate for HACS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3

- name: HACS validation
uses: hacs/action@main
with:
category: integration
29 changes: 29 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Release

on:
release:
types:
- published

permissions:
contents: write

jobs:
release-zip:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Prepare release
run: |
cd ${{ github.workspace }}/custom_components/airmx
zip -r airmx.zip ./
- name: Upload zip
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ github.workspace }}/custom_components/airmx/airmx.zip
asset_name: airmx.zip
tag: ${{ github.ref }}
overwrite: true
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.iml
.mypy_cache
research
tools
15 changes: 15 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.280
hooks:
- id: ruff

- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black

- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Интеграция AIRMX и Tion Iris в Home Assistant
Инструкция в процессе разработки...
12 changes: 12 additions & 0 deletions airmx-addon/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ARG BUILD_FROM
FROM $BUILD_FROM

WORKDIR /

RUN apk add --no-cache python3 py3-pip mosquitto && \
pip3 install flask==3.0.0

COPY rootfs /

VOLUME /data
EXPOSE 80 1883
2 changes: 2 additions & 0 deletions airmx-addon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Home Assistant Add-on: AIRMX
Аддон предоставляет замену китайским серверам i.airmx.cn и awm.airmx.cn, которые необходимы для управления увлажнителями AIRMX и TION Iris.
5 changes: 5 additions & 0 deletions airmx-addon/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build_from:
aarch64: ghcr.io/home-assistant/aarch64-base:3.18
amd64: ghcr.io/home-assistant/amd64-base:3.18
armhf: ghcr.io/home-assistant/armhf-base:3.18
armv7: ghcr.io/home-assistant/armv7-base:3.18
15 changes: 15 additions & 0 deletions airmx-addon/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: AIRMX
version: '0.0.1.dev1'
slug: airmx-addon
description: Local control AIRMX devices
url: https://github.com/dext0r/airmx
arch:
- aarch64
- amd64
- armhf
- armv7
init: false
startup: system
ports:
80/tcp: 25880
1883/tcp: 25883
Binary file added airmx-addon/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added airmx-addon/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions airmx-addon/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
flask==3.0.0
8 changes: 8 additions & 0 deletions airmx-addon/rootfs/etc/mosquitto/mosquitto.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
allow_anonymous true
listener 1883 0.0.0.0
log_dest stdout
log_type error
log_type warning
log_type notice
log_type information
log_timestamp_format %Y-%m-%d %H:%M:%S
12 changes: 12 additions & 0 deletions airmx-addon/rootfs/etc/services.d/i-airmx-cn/finish
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bashio
# ==============================================================================
# Take down the S6 supervision tree when service fails
# s6-overlay docs: https://github.com/just-containers/s6-overlay
# ==============================================================================

if [[ "$1" -ne 0 ]] && [[ "$1" -ne 256 ]]; then
bashio::log.warning "Halt add-on"
exec /run/s6/basedir/bin/halt
fi

bashio::log.info "Service restart after closing"
2 changes: 2 additions & 0 deletions airmx-addon/rootfs/etc/services.d/i-airmx-cn/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/with-contenv bashio
exec /i-airmx-cn.py
12 changes: 12 additions & 0 deletions airmx-addon/rootfs/etc/services.d/mosquitto/finish
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bashio
# ==============================================================================
# Take down the S6 supervision tree when service fails
# s6-overlay docs: https://github.com/just-containers/s6-overlay
# ==============================================================================

if [[ "$1" -ne 0 ]] && [[ "$1" -ne 256 ]]; then
bashio::log.warning "Halt add-on"
exec /run/s6/basedir/bin/halt
fi

bashio::log.info "Service restart after closing"
2 changes: 2 additions & 0 deletions airmx-addon/rootfs/etc/services.d/mosquitto/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/with-contenv bashio
exec mosquitto -c /etc/mosquitto/mosquitto.conf
124 changes: 124 additions & 0 deletions airmx-addon/rootfs/i-airmx-cn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
import argparse
from dataclasses import asdict, dataclass, is_dataclass
from datetime import datetime
import json
from json import JSONDecodeError
import logging
import os.path
from typing import Any

from flask import Flask, request
from werkzeug import exceptions as HTTPException

DEVICE_STORE_PATH = "/data/devices.json"

app = Flask(__name__)
app.logger.setLevel(logging.INFO)


@dataclass
class Device:
id: int
key: str
wifi_mac: str
ble_mac: str
type: int
ts: int


class EnhancedJSONEncoder(json.JSONEncoder):
def default(self, o: Any) -> Any:
if is_dataclass(o):
return asdict(o)

return super().default(o)


def _load_devices(path: str) -> dict[int, Device]:
rv: dict[int, Device] = {}
if os.path.isfile(path):
with open(path, "r") as f:
try:
for item in json.load(f).values():
device = Device(**item)
rv[device.id] = device
except JSONDecodeError:
app.logger.exception("Failed to load devices.json")

return rv


devices: dict[int, Device] = _load_devices(DEVICE_STORE_PATH)


@app.route("/")
def root() -> str:
return "AIRMX addon\n"


@app.route("/aw")
def aw() -> dict[str, Any]:
if (path := request.args.get("path")) != "aw/GET/genId":
app.logger.error(f"Unsupported path {path}")
raise HTTPException.NotImplemented()

params = json.loads(request.args.get("params", ""))
wifi_mac = int.from_bytes(bytearray.fromhex(params["mac"]), byteorder="little")
ble_mac = wifi_mac + 2
device = Device(
id=int.from_bytes(bytearray.fromhex(params["mac"])[:2], byteorder="little"),
wifi_mac=wifi_mac.to_bytes(6, byteorder="big").hex(),
ble_mac=ble_mac.to_bytes(6, byteorder="big").hex(),
key=params["key"],
type=int(params["type"]),
ts=int(datetime.now().timestamp()),
)

match device.type:
case 21: # A3S_V2 / Iris
data = {
"awId": device.id,
"electrolysisLevel4OffTime": 1800,
"electrolysisLevel2OpenTime": 600,
"electrolysisLevel3OpenTime": 1200,
"electrolysisLevel4OpenTime": 1800,
"electrolysisLevel2OffTime": 600,
"electrolysisLevel1OpenTime": 60,
"electrolysisLevel3OffTime": 1200,
"electrolysisLevel1OffTime": 120,
}
case _:
app.logger.error(f"Unsupported device: {device}")
raise HTTPException.NotImplemented()

app.logger.info(f"New device registered: {device}")
devices[device.id] = device

with open(DEVICE_STORE_PATH, "w") as f:
json.dump(devices, f, cls=EnhancedJSONEncoder)

return {"status": 200, "data": data}


@app.route("/gettime")
def gettime() -> dict[str, int]:
return {"time": int(datetime.now().timestamp())}


@app.route("/check/airwater/washCleanNotify")
def wash_clean_notify() -> dict[str, int]:
return {"status": 200}


@app.route("/_devices")
def get_devices() -> list[dict[str, str | int]]:
return [asdict(device) for device in sorted(devices.values(), key=lambda d: d.ts * -1)]


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--port", default=80, type=int)
args = parser.parse_args()

app.run(host="0.0.0.0", port=args.port, debug=False, use_reloader=False)
6 changes: 6 additions & 0 deletions airmx-esp-gate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# AIRMX Gate
Прошивка под ESP8266 для запуска точки доступа, к которой подключаются увлажнители.

Весь трафик от увлажнителя будет перенаправлен в аддон на Home Assistant.

Является необязательным компонентом, увлажнители можно подключать к домашнему роутеру напрямую и заворачивать их трафик через DNAT.
Loading

0 comments on commit 68b4741

Please sign in to comment.