# M1730-ESP32 ESP32 firmware that drives one or more **M1730 analog panel meters** (moving-coil ammeters) via PWM, with a web configuration UI and Home Assistant integration over MQTT. ## Table of contents - [How it works](#how-it-works) - [Hardware](#hardware) - [Building and flashing](#building-and-flashing) - [First-time Wi-Fi setup](#first-time-wi-fi-setup) - [Web UI](#web-ui) - [MQTT / Home Assistant](#mqtt--home-assistant) - [HTTP API](#http-api) - [Configuration reference](#configuration-reference) --- ## How it works Each meter needle is controlled by a PWM signal on a GPIO pin. The firmware maps a **0–100 %** value to a PWM duty cycle, scaled by a per-meter **Max Duty** percentage that you calibrate for full-scale deflection. All settings are stored in LittleFS (`/config.json`) so they survive reboots. ``` needle position = (currentValue / 100) × (maxDuty / 100) × 1023 ticks ``` PWM runs at 5 kHz with 10-bit resolution (0–1023). ### Physical range mapping Each meter has configurable **Min** / **Max** values that define its physical range (e.g. 0–50 A). The firmware normalises to 0–100 % internally, but MQTT publishes and receives in physical units — Home Assistant never has to deal with percentages. --- ## Hardware | Item | Details | |------|---------| | MCU | ESP32-S3 DevKitC-1 | | Meter | M1730 moving-coil panel ammeter (or any PWM-driveable analog meter) | | Max meters | 8 simultaneous (ESP32 LEDC channels 0–7) | | PWM freq | 5 kHz | | PWM resolution | 10-bit | Connect the meter coil (via a current-limiting resistor sized for full-scale) between a GPIO pin and GND. Find the correct resistor value by raising **Max Duty** slowly until the needle reaches full scale. 660 --- ## Building and flashing The project uses [PlatformIO](https://platformio.org/). ```bash # Build pio run # Flash pio run --target upload # Open serial monitor (115200 baud) pio device monitor ``` Board target: `esp32-s3-devkitc-1` --- ## First-time Wi-Fi setup On first boot (or when stored Wi-Fi credentials are missing), the device starts an access point named **M1730**. Connect to it with any phone or laptop — a captive portal will appear automatically. 1. Enter your Wi-Fi SSID and password. 2. Optionally change the **Device hostname** (default: `m1730`). 3. Click **Save**. The device connects to your network and restarts. After connecting, the device is reachable at: - `http://m1730.local` (mDNS, works on most local networks) - `http://` (shown in the serial monitor on boot) --- ## Web UI Browse to the device address to open the configuration page. ### Info panel Shows the current hostname and IP address. ### Hostname Sets the mDNS name (`.local`) and the MQTT device name. Saved across reboots. ### Meters Use the **Meters** dropdown to add or remove meters (1–10). Each meter has: | Field | Description | |-------|-------------| | Pin | GPIO pin number connected to the meter coil via current limiting resistor | | Name | Label shown in Home Assistant and the web UI | | Unit | Optional unit string shown in Home Assistant (e.g. `W`, `A`, `°C`) | | Min / Max | Physical range of the meter (e.g. 0–50 A, 0–3000 W). MQTT publishes and receives values in this range; HA discovery uses these as the number entity min/max | | Max Duty | PWM duty at full scale, as a percentage (0–100). Calibrate this so the needle just reaches full deflection | | Output slider | Moves the needle live (0–100 % of the configured range). Also sent to MQTT | Click **Save** to persist all settings. Changing the meter count also triggers an immediate save and meter re-attach. --- ## MQTT / Home Assistant ### Enabling MQTT In the **MQTT** section of the web UI: | Field | Description | |-------|-------------| | Enable | Toggle MQTT on/off | | Broker | Hostname or IP of your MQTT broker | | Port | Default `1883` | | User / Pass | Broker credentials | | Prefix | Topic prefix (default `m1730`) | ### Home Assistant auto-discovery On connect, the device publishes discovery payloads to `homeassistant/number/…/config`. Each meter appears in HA as a **Number** entity with `min`/`max` taken from the meter's configured physical range and a step of 0.1. The entities are grouped under a single HA device named after the hostname. ### Value mapping Internally the firmware works with a 0–100 % duty value. MQTT publishes and receives **physical values** — the percentage is transparently mapped to the meter's Min–Max range: ``` physicalValue = percentage / 100 × (rangeMax - rangeMin) + rangeMin ``` For example, with Min=0 and Max=50, the slider at 50 % publishes `25.0` to MQTT, and a command of `25.0` on the `/set` topic moves the slider to 50 %. ### Topics | Direction | Topic | Description | |-----------|-------|-------------| | Published | `/meter//current` | Current meter value in physical units (retained) | | Subscribed | `/meter//current/set` | Set meter value in physical units | | Published | `/status` | `online: true` on connect, `online: false` as LWT | `` is the zero-based meter index. ### Reconnection The firmware probes the broker TCP port before attempting a full MQTT connect. If the broker is unreachable, it retries every 30 seconds without blocking the web server. ### Example HA automation ```yaml automation: - alias: Show solar power on meter trigger: platform: state entity_id: sensor.solar_power_w action: service: number.set_value target: entity_id: number.m1730_solar data: value: "{{ trigger.to_state.state | float | round(1) }}" # Values are in physical units — if the meter Min=0, Max=3000, the HA # number entity directly accepts watts, no conversion needed. ``` --- ## HTTP API ### `GET /set?i=&v=` Immediately moves meter `` to `` (0–100), updates PWM, persists the value to flash, and publishes to MQTT. Used by the live slider on the web UI. | Parameter | Type | Description | |-----------|------|-------------| | `i` | integer | Meter index (0-based) | | `v` | float | Value 0–100 | Returns `200 OK` on success, `400` on bad input. ### `GET /` Returns the full HTML configuration page. ### `POST /config` Saves all configuration from the HTML form and re-applies PWM to all meters. Responds with the updated configuration page. --- ## Configuration reference Config is stored as JSON in LittleFS at `/config.json`. ```json { "hostname": "m1730", "mqtt": { "enabled": true, "host": "192.168.1.10", "port": 1883, "user": "ha", "pass": "secret", "prefix": "m1730" }, "meters": [ { "pin": 4, "maxD": 72.5, "current": 45.0, "name": "Solar", "unit": "W", "rangeMin": 0.0, "rangeMax": 3000.0 } ] } ``` The file is written by the web UI and should not need manual editing. To reset to factory defaults, delete the file or erase flash with `pio run --target erase`.