From 1f89e7080856afc3ea29884061b9536e1013988f Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Mon, 15 Jun 2026 00:29:08 +0200 Subject: [PATCH] Documentation moved to the root directory --- README.md | 224 +++++++++++++++++++++++++++++++++++++++ documentation/README.md | 226 ---------------------------------------- 2 files changed, 224 insertions(+), 226 deletions(-) delete mode 100644 documentation/README.md diff --git a/README.md b/README.md index 129fcac..049e681 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,226 @@ # 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`. diff --git a/documentation/README.md b/documentation/README.md deleted file mode 100644 index 049e681..0000000 --- a/documentation/README.md +++ /dev/null @@ -1,226 +0,0 @@ -# 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`.