diff --git a/README.md b/README.md index e69de29..c76e293 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,107 @@ +# Selsyn Gauge MQTT Controller + +MQTT-based gauge controller for ESP32 / MicroPython. Drives a VID28/BKA30D/X25 stepper gauge and integrates with Home Assistant via MQTT. + +## Files + +| File | Description | +|------|-------------| +| `boot.py` | WiFi connect + OTA trigger | +| `ota.py` | Gitea API poller for OTA updates | +| `gaugemqtt.py` | Main MQTT controller | +| `gauge.py` | Stepper driver for VID28/BKA30D/X25 gauges | +| `config.json` | Configuration (create from `config.example.json`) | +| `config.example.json` | Template with all options | + +## Deployment + +```bash +# First time: upload via USB +ampy --port /dev/ttyUSB0 put boot.py +ampy --port /dev/ttyUSB0 put ota.py +ampy --port /dev/ttyUSB0 put gaugemqtt.py +ampy --port /dev/ttyUSB0 put gauge.py +ampy --port /dev/ttyUSB0 put config.json +ampy --port /dev/ttyUSB0 put lib/gauge.py /lib/gauge.py + +# Monitor serial output +minicom -D /dev/ttyUSB0 -b 115200 +``` + +## MQTT Topics + +All topics are prefixed with `mqtt_prefix`: + +| Topic | Direction | Description | +|-------|-----------|-------------| +| `/set` | ← | Target value from HA | +| `/state` | → | Current value | +| `/status` | → | `online`/`offline` | +| `/zero` | ← | Trigger zero calibration | +| `/led/red/set` | ← | `ON`/`OFF` | +| `/led/green/set` | ← | `ON`/`OFF` | +| `/led/backlight/set` | ← | Brightness 0-100 | + +## Configuration + +Copy `config.example.json` to `config.json` and edit: + +### WiFi + +| Key | Default | Description | +|-----|---------|-------------| +| `wifi_ssid` | — | WiFi network name | +| `wifi_password` | — | WiFi password | + +### MQTT + +| Key | Default | Description | +|-----|---------|-------------| +| `mqtt_broker` | — | Broker IP/hostname | +| `mqtt_port` | 1883 | Broker port | +| `mqtt_user` | — | MQTT username | +| `mqtt_password` | — | MQTT password | +| `mqtt_client_id` | — | Unique client ID | +| `mqtt_prefix` | — | Topic prefix for all MQTT messages | + +### Gauge + +| Key | Default | Description | +|-----|---------|-------------| +| `gauge_pins` | `[12, 13, 26, 27]` | GPIO pins (IN1, IN2, IN3, IN4) | +| `gauge_min` | 0 | Minimum value | +| `gauge_max` | 7300 | Maximum value | +| `gauge_pulse` | 4 | Pulse width in milliseconds | +| `gauge_half_step` | false | Use 8-phase half-step mode (1200 steps, smoother) | +| `smooth_step_ms` | 50 | Smooth movement interval | +| `idle_release_ms` | 3000 | Release coils after idle (ms) | +| `rezero_interval_ms` | 3600000 | Auto-rezero interval (ms, default 1hr) | + +### LEDs + +| Key | Default | Description | +|-----|---------|-------------| +| `led_red_pin` | 33 | Red LED GPIO pin | +| `led_green_pin` | 32 | Green LED GPIO pin | +| `led_bl_pin` | 23 | Backlight PWM GPIO pin | + +### Home Assistant Discovery + +| Key | Default | Description | +|-----|---------|-------------| +| `device_name` | "Selsyn 1" | Device name in HA | +| `device_model` | "Chernobyl Selsyn-inspired gauge" | Device model | +| `device_manufacturer` | "AdeBaumann" | Device manufacturer | +| `device_area` | "Control Panels" | Suggested area in HA | +| `gauge_entity_name` | "Selsyn 1 Power" | Gauge entity name | +| `gauge_unit` | "W" | Unit of measurement for gauge | +| `red_led_entity_name` | "Selsyn 1 Red LED" | Red LED entity name | +| `green_led_entity_name` | "Selsyn 1 Green LED" | Green LED entity name | +| `backlight_entity_name` | "Selsyn 1 Backlight" | Backlight entity name | +| `backlight_unit` | "%" | Unit for backlight | + +### Timing + +| Key | Default | Description | +|-----|---------|-------------| +| `heartbeat_ms` | 10000 | State publish interval (ms) | diff --git a/ota.py b/ota.py index 7a36313..46fde72 100644 --- a/ota.py +++ b/ota.py @@ -87,6 +87,16 @@ def info(msg): _log("INFO", msg) def warn(msg): _log("WARN", msg) def log_err(msg): _log("ERROR", msg) +# --------------------------------------------------------------------------- +# HTTP helpers +# --------------------------------------------------------------------------- + +def _headers(): + h = {"Accept": "application/json"} + if API_TOKEN: + h["Authorization"] = f"token {API_TOKEN}" + return h + # --------------------------------------------------------------------------- # Config loader # ---------------------------------------------------------------------------