diff --git a/Growatt ShineLAN-X/firmware/include/config.h b/Growatt ShineLAN-X/firmware/include/config.h index a4e0dc5..9a9c2ff 100644 --- a/Growatt ShineLAN-X/firmware/include/config.h +++ b/Growatt ShineLAN-X/firmware/include/config.h @@ -1,22 +1,35 @@ #pragma once // ============================================================ -// NETZWERK — ENC28J60 Ethernet (Bitbang-SPI auf Port C) +// Pin-Belegung — Quelle: https://github.com/mwalle/shinelanx-modbus +// Gleiche Platine, verifizierte Pins // STM32F103RBT6, LQFP-64 -// Hardware-SPI nicht nutzbar — alle SPI-Pins liegen auf Port C // ============================================================ -// Bitbang-SPI Pins (alle gemessen) -#define ETH_CS_PIN PC7 // ENC28J60 Pin 7 /CS → STM32 Pin 36 (gemessen) -#define ETH_SCK_PIN PC6 // ENC28J60 Pin 6 SCK → STM32 Pin 35 (gemessen) -#define ETH_MISO_PIN PC8 // ENC28J60 Pin 4 SO → STM32 Pin 37 (gemessen) -#define ETH_MOSI_PIN PC9 // ENC28J60 Pin 5 SI → STM32 Pin ?? (noch unbekannt, Scan läuft) +// ENC28J60 — SPI2 (Hardware-SPI) +// LQFP-64: PB12=33, PB13=34, PB14=35, PB15=36, PC6=37, PC8=39 +#define ETH_CS_PIN PB12 // ENC28J60 /CS (SPI2 NSS) +#define ETH_SCK_PIN PB13 // ENC28J60 SCK (SPI2 SCK) +#define ETH_MISO_PIN PB14 // ENC28J60 SO (SPI2 MISO) +#define ETH_MOSI_PIN PB15 // ENC28J60 SI (SPI2 MOSI) +#define ETH_RST_PIN PC8 // ENC28J60 /RESET +#define ETH_INT_PIN PC6 // ENC28J60 INT# (optional, polling reicht) -// ENC28J60 Reset -#define ETH_RST_PIN PB13 // ENC28J60 Pin 18 /RESET → STM32 Pin 32, 500Ω +// LEDs +#define LED_DEBUG PC7 // Debug-LED (grün o.ä.) +#define LED_RED PB1 // RGB Rot +#define LED_GREEN PB0 // RGB Grün +#define LED_BLUE PC5 // RGB Blau + +// Taster +#define BTN_USER PA3 // User-Taster (low-aktiv) + +// ============================================================ +// NETZWERK +// ============================================================ // 0 = DHCP, 1 = Statische IP -#define USE_DHCP 0 +#define USE_DHCP 1 // Nur relevant wenn USE_DHCP = 0 #define STATIC_IP 192,168,2,15 @@ -30,19 +43,23 @@ // ============================================================ // MQTT // ============================================================ -#define MQTT_BROKER "192.168.2.84" +#define MQTT_BROKER "192.168.1.1" #define MQTT_PORT 1883 -#define MQTT_USER "" // Leer lassen wenn kein Auth -#define MQTT_PASSWORD "" +#define MQTT_USER "mqtt" +#define MQTT_PASSWORD "HIER_MQTT_PASSWORT_EINTRAGEN" #define MQTT_CLIENT "growatt-shinelan" // ============================================================ -// RS485 / MODBUS -// RS485 DE/RE → STM32 Pin 25 = PB1 (LQFP-64 neu gemessen) -// USART1: TX=PA9 (Pin 40), RX=PA10 (Pin 41) +// MODBUS / WECHSELRICHTER-UART +// Growatt kommuniziert über USB-CDC (virtueller COM-Port) bei 115200 Baud — +// kein klassisches RS485, kein DE/RE-Pin nötig. +// Bestätigt durch ESPHome-Configs (tx=1, rx=3, baud=115200, kein flow_control_pin). +// Kandidat für STM32: USART3 (PB10=TX, PB11=RX) — auf Platine nachmessen! +// PA3 = Taster → USART2 (PA2/PA3) scheidet aus. // ============================================================ -#define RS485_DE_PIN PB1 // RE/DE Steuerpin → STM32 Pin 25 -#define MODBUS_BAUD 9600 // Growatt Standard-Baudrate +#define MODBUS_TX_PIN PB10 // USART3 TX — TODO: auf Platine bestätigen +#define MODBUS_RX_PIN PB11 // USART3 RX — TODO: auf Platine bestätigen +#define MODBUS_BAUD 115200 // Growatt USB-CDC Baudrate (nicht 9600 RS485!) #define MODBUS_ADDR 1 // Modbus Slave-Adresse des Wechselrichters // ============================================================ diff --git a/Growatt ShineLAN-X/firmware/src/main.cpp b/Growatt ShineLAN-X/firmware/src/main.cpp index 433a2fb..b83e3cd 100644 --- a/Growatt ShineLAN-X/firmware/src/main.cpp +++ b/Growatt ShineLAN-X/firmware/src/main.cpp @@ -5,11 +5,12 @@ #include #include "config.h" -// Debug-UART: USART1, TX=PA9, RX=PA10 (Testpunkt auf Platine) -// HINWEIS: Modbus ist temporär deaktiviert — erst Ethernet/MQTT bestätigen, -// dann Debug entfernen und Modbus wieder aktivieren. +// Debug-UART: USART1 TX=PA9, RX=PA10 HardwareSerial DebugSerial(PA10, PA9); +// Modbus-UART: USART3 TX=PB10, RX=PB11 (Growatt USB-CDC bei 115200 Baud) +HardwareSerial ModbusSerial(MODBUS_RX_PIN, MODBUS_TX_PIN); + // ============================================================ // Sensor-Definition // ============================================================ @@ -17,7 +18,7 @@ struct Sensor { const char* id; const char* name; uint16_t address; - bool isDword; // true = 2 Register (32 bit), false = 1 Register (16 bit) + bool isDword; float scale; const char* unit; const char* deviceClass; @@ -25,65 +26,58 @@ struct Sensor { const char* icon; }; -// Sensor-Liste — entspricht den Modbus-Registern des SPH 5000 TL3-BH-UP -// Für andere Modelle nicht zutreffende Sensoren auskommentieren. const Sensor SENSORS[] = { // --- PV Eingang --- - {"pv1_voltage", "PV1 Voltage", 3, false, 0.1f, "V", "voltage", "measurement", "mdi:solar-panel"}, - {"pv1_current", "PV1 Current", 4, false, 0.1f, "A", "current", "measurement", "mdi:solar-panel"}, - {"pv1_power", "PV1 Power", 5, true, 0.1f, "W", "power", "measurement", "mdi:solar-panel"}, - {"pv2_voltage", "PV2 Voltage", 7, false, 0.1f, "V", "voltage", "measurement", "mdi:solar-panel"}, - {"pv2_current", "PV2 Current", 8, false, 0.1f, "A", "current", "measurement", "mdi:solar-panel"}, - {"pv2_power", "PV2 Power", 9, true, 0.1f, "W", "power", "measurement", "mdi:solar-panel"}, + {"pv1_voltage", "PV1 Voltage", 3, false, 0.1f, "V", "voltage", "measurement", "mdi:solar-panel"}, + {"pv1_current", "PV1 Current", 4, false, 0.1f, "A", "current", "measurement", "mdi:solar-panel"}, + {"pv1_power", "PV1 Power", 5, true, 0.1f, "W", "power", "measurement", "mdi:solar-panel"}, + {"pv2_voltage", "PV2 Voltage", 7, false, 0.1f, "V", "voltage", "measurement", "mdi:solar-panel"}, + {"pv2_current", "PV2 Current", 8, false, 0.1f, "A", "current", "measurement", "mdi:solar-panel"}, + {"pv2_power", "PV2 Power", 9, true, 0.1f, "W", "power", "measurement", "mdi:solar-panel"}, // --- AC Ausgang / Netz --- - {"ac_power_total", "AC Output Power Total", 35, true, 0.1f, "W", "power", "measurement", "mdi:flash"}, - {"grid_frequency", "Grid Frequency", 37, false, 0.01f, "Hz", "frequency", "measurement", "mdi:sine-wave"}, - {"grid_voltage_l1", "Grid Voltage L1", 38, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"}, - {"grid_current_l1", "Grid Current L1", 39, false, 0.1f, "A", "current", "measurement", "mdi:flash"}, - {"grid_voltage_l2", "Grid Voltage L2", 42, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"}, - {"grid_current_l2", "Grid Current L2", 43, false, 0.1f, "A", "current", "measurement", "mdi:flash"}, - {"grid_voltage_l3", "Grid Voltage L3", 46, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"}, - {"grid_current_l3", "Grid Current L3", 47, false, 0.1f, "A", "current", "measurement", "mdi:flash"}, + {"ac_power_total", "AC Output Power Total", 35, true, 0.1f, "W", "power", "measurement", "mdi:flash"}, + {"grid_frequency", "Grid Frequency", 37, false, 0.01f, "Hz", "frequency", "measurement", "mdi:sine-wave"}, + {"grid_voltage_l1", "Grid Voltage L1", 38, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"}, + {"grid_current_l1", "Grid Current L1", 39, false, 0.1f, "A", "current", "measurement", "mdi:flash"}, + {"grid_voltage_l2", "Grid Voltage L2", 42, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"}, + {"grid_current_l2", "Grid Current L2", 43, false, 0.1f, "A", "current", "measurement", "mdi:flash"}, + {"grid_voltage_l3", "Grid Voltage L3", 46, false, 0.1f, "V", "voltage", "measurement", "mdi:flash"}, + {"grid_current_l3", "Grid Current L3", 47, false, 0.1f, "A", "current", "measurement", "mdi:flash"}, // --- Energie PV --- - {"energy_today", "Energy Today", 53, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:solar-power"}, - {"energy_total", "Energy Total", 55, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:solar-power"}, + {"energy_today", "Energy Today", 53, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:solar-power"}, + {"energy_total", "Energy Total", 55, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:solar-power"}, // --- Temperatur --- - {"inverter_temp", "Inverter Temperature", 93, false, 0.1f, "°C", "temperature", "measurement", "mdi:thermometer"}, + {"inverter_temp", "Inverter Temperature", 93, false, 0.1f, "°C", "temperature", "measurement", "mdi:thermometer"}, // --- Batterie --- - {"bat_discharge_power", "Battery Discharge Power", 1009, true, 0.1f, "W", "power", "measurement", "mdi:battery-minus"}, - {"bat_charge_power", "Battery Charge Power", 1011, true, 0.1f, "W", "power", "measurement", "mdi:battery-plus"}, - {"bat_voltage", "Battery Voltage", 1013, false, 0.1f, "V", "voltage", "measurement", "mdi:battery"}, - {"bat_soc", "Battery State of Charge", 1014, false, 1.0f, "%", "battery", "measurement", "mdi:battery"}, - {"bat_temperature", "Battery Temperature", 1040, false, 0.1f, "°C", "temperature", "measurement", "mdi:thermometer"}, - // --- Netz- und Batterie-Energiezähler (für Energie-Dashboard) --- - {"energy_import_total", "Energy Import Total", 1046, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:transmission-tower-import"}, - {"energy_export_total", "Energy Export Total", 1050, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:transmission-tower-export"}, - {"bat_discharge_total", "Battery Discharge Total", 1054, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:battery-minus"}, - {"bat_charge_total", "Battery Charge Total", 1058, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:battery-plus"}, + {"bat_discharge_power", "Battery Discharge Power", 1009, true, 0.1f, "W", "power", "measurement", "mdi:battery-minus"}, + {"bat_charge_power", "Battery Charge Power", 1011, true, 0.1f, "W", "power", "measurement", "mdi:battery-plus"}, + {"bat_voltage", "Battery Voltage", 1013, false, 0.1f, "V", "voltage", "measurement", "mdi:battery"}, + {"bat_soc", "Battery State of Charge", 1014, false, 1.0f, "%", "battery", "measurement", "mdi:battery"}, + {"bat_temperature", "Battery Temperature", 1040, false, 0.1f, "°C", "temperature", "measurement", "mdi:thermometer"}, + // --- Energiezähler --- + {"energy_import_total", "Energy Import Total", 1046, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:transmission-tower-import"}, + {"energy_export_total", "Energy Export Total", 1050, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:transmission-tower-export"}, + {"bat_discharge_total", "Battery Discharge Total", 1054, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:battery-minus"}, + {"bat_charge_total", "Battery Charge Total", 1058, true, 0.1f, "kWh", "energy", "total_increasing", "mdi:battery-plus"}, }; const uint8_t SENSOR_COUNT = sizeof(SENSORS) / sizeof(SENSORS[0]); // ============================================================ // Globale Objekte // ============================================================ -// HardwareSerial modbusSerial(PA10, PA9); // USART1 — temporär deaktiviert (teilt sich UART mit DebugSerial) - byte mac[] = {MAC_ADDRESS}; EthernetClient ethClient; PubSubClient mqtt(ethClient); -// ModbusMaster modbus; // temporär deaktiviert +ModbusMaster modbus; // ============================================================ -// RS485 Richtungssteuerung (Callbacks für ModbusMaster) +// LED-Hilfsfunktionen // ============================================================ -void preTransmission() { digitalWrite(RS485_DE_PIN, HIGH); } -void postTransmission() { digitalWrite(RS485_DE_PIN, LOW); } +void ledSet(uint8_t pin, bool on) { digitalWrite(pin, on ? LOW : HIGH); } // aktiv LOW // ============================================================ -// MQTT Hilfsfunktionen +// MQTT // ============================================================ - -// Veröffentlicht alle Sensor-Discovery-Pakete für Home Assistant void publishDiscovery() { char topic[128]; char payload[640]; @@ -113,27 +107,23 @@ void publishDiscovery() { s.name, DEVICE_ID, s.id, s.id, - s.unit, - s.deviceClass, - s.stateClass, - s.icon, + s.unit, s.deviceClass, s.stateClass, s.icon, DEVICE_ID, DEVICE_NAME, DEVICE_MODEL, DEVICE_MFR); - mqtt.publish(topic, payload, true); // retained = true + mqtt.publish(topic, payload, true); } - } bool mqttReconnect() { DebugSerial.print("MQTT connecting... "); - bool ok; - if (strlen(MQTT_USER) > 0) { - ok = mqtt.connect(MQTT_CLIENT, MQTT_USER, MQTT_PASSWORD); - } else { - ok = mqtt.connect(MQTT_CLIENT); - } + ledSet(LED_RED, true); + bool ok = (strlen(MQTT_USER) > 0) + ? mqtt.connect(MQTT_CLIENT, MQTT_USER, MQTT_PASSWORD) + : mqtt.connect(MQTT_CLIENT); if (ok) { DebugSerial.println("OK"); + ledSet(LED_RED, false); + ledSet(LED_GREEN, true); publishDiscovery(); } else { DebugSerial.print("FAIL rc="); @@ -146,122 +136,62 @@ bool mqttReconnect() { // Setup // ============================================================ void setup() { - // Debug-UART zuerst starten (USART1: TX=PA9 = Testpunkt auf Platine) DebugSerial.begin(115200); delay(10); DebugSerial.println("\r\n=== Growatt ShineLAN-X ==="); DebugSerial.println("Build: " __DATE__ " " __TIME__); - // RS485 DE/RE Pin - pinMode(RS485_DE_PIN, OUTPUT); - digitalWrite(RS485_DE_PIN, LOW); // Empfangsmodus + // LEDs initialisieren (aktiv LOW laut Referenz) + pinMode(LED_DEBUG, OUTPUT); ledSet(LED_DEBUG, false); + pinMode(LED_RED, OUTPUT); ledSet(LED_RED, false); + pinMode(LED_GREEN, OUTPUT); ledSet(LED_GREEN, false); + pinMode(LED_BLUE, OUTPUT); ledSet(LED_BLUE, false); - // ENC28J60 Reset (Hardware-Reset vor init) + // Startup-Blink: alle LEDs kurz an + ledSet(LED_RED, true); ledSet(LED_GREEN, true); ledSet(LED_BLUE, true); + delay(300); + ledSet(LED_RED, false); ledSet(LED_GREEN, false); ledSet(LED_BLUE, false); + + // ENC28J60 Reset DebugSerial.println("ETH: reset..."); pinMode(ETH_RST_PIN, OUTPUT); - digitalWrite(ETH_RST_PIN, LOW); - delay(50); - digitalWrite(ETH_RST_PIN, HIGH); - delay(200); - - // SO-Aktivitätstest: Ist der ENC28J60 überhaupt am Leben? - // PC8 mit Pull-Down → wenn ENC28J60 SO aktiv HIGH treibt = Chip antwortet - // Wenn PC8 bleibt LOW = SO ist High-Z = Chip tot/kein Takt/keine Power - pinMode(ETH_CS_PIN, OUTPUT); digitalWrite(ETH_CS_PIN, HIGH); - pinMode(ETH_SCK_PIN, OUTPUT); digitalWrite(ETH_SCK_PIN, LOW); - pinMode(PC8, INPUT_PULLDOWN); - - DebugSerial.println("SO-Aktivitaetstest (PC8 = INPUT_PULLDOWN):"); - - // Test 1: CS hoch (SO sollte High-Z sein → LOW erwartet) - uint8_t so_cs_high = digitalRead(PC8); - DebugSerial.print(" CS=H SO="); DebugSerial.print(so_cs_high); - DebugSerial.println(so_cs_high == 0 ? " (High-Z, erwartet)" : " (getrieben! unerwartet)"); - - // Test 2: CS runter (ENC28J60 soll SO aktivieren) - digitalWrite(ETH_CS_PIN, LOW); - delayMicroseconds(50); - uint8_t so_cs_low = digitalRead(PC8); - digitalWrite(ETH_CS_PIN, HIGH); - DebugSerial.print(" CS=L SO="); DebugSerial.print(so_cs_low); - if (so_cs_low == 1) DebugSerial.println(" --> Chip treibt SO! Chip ist am Leben."); - else DebugSerial.println(" --> SO bleibt LOW = Chip antwortet nicht (kein Takt? kein Strom?)"); - - // Test 3: Reset-Zyklus, dann CS low - digitalWrite(ETH_RST_PIN, LOW); delay(10); - digitalWrite(ETH_RST_PIN, HIGH); delay(100); - digitalWrite(ETH_CS_PIN, LOW); - delayMicroseconds(50); - uint8_t so_after_reset = digitalRead(PC8); - digitalWrite(ETH_CS_PIN, HIGH); - DebugSerial.print(" nach Reset, CS=L SO="); DebugSerial.print(so_after_reset); - if (so_after_reset == 1) DebugSerial.println(" --> Chip lebt!"); - else DebugSerial.println(" --> immer noch kein Leben"); - - // ============================================================ - // Roher SPI-Test: ESTAT-Register lesen (kein EthernetENC) - // ENC28J60 ESTAT = Bank0, Addr 0x1D - // Read Control Register: opcode 000 | addr 11101 = 0x1D - // Erwarteter Wert nach Reset: 0x01 (CLKRDY-Bit gesetzt) - // ============================================================ - - // Langer Reset-Zyklus und Wartezeit für CLKRDY - pinMode(ETH_RST_PIN, OUTPUT); digitalWrite(ETH_RST_PIN, LOW); delay(20); - digitalWrite(ETH_RST_PIN, HIGH); delay(800); // 25 MHz Quarz braucht <1 ms + digitalWrite(ETH_RST_PIN, HIGH); delay(200); - pinMode(ETH_CS_PIN, OUTPUT); digitalWrite(ETH_CS_PIN, HIGH); - pinMode(ETH_SCK_PIN, OUTPUT); digitalWrite(ETH_SCK_PIN, LOW); - pinMode(ETH_MISO_PIN, INPUT); // kein Pull — Chip treibt SO direkt + Ethernet.init(ETH_CS_PIN); + DebugSerial.println("ETH: begin..."); - // Inline-Hilfsfunktion: 1 Byte über SPI senden und empfangen - // MOSI-Pin wird als Parameter übergeben (Kandidaten-Scan) - auto spiXfer = [](uint8_t mosiPin, uint8_t out) -> uint8_t { - uint8_t in = 0; - for (int8_t i = 7; i >= 0; i--) { - digitalWrite(mosiPin, (out >> i) & 1); - digitalWrite(ETH_SCK_PIN, HIGH); - in = (in << 1) | digitalRead(ETH_MISO_PIN); - digitalWrite(ETH_SCK_PIN, LOW); - } - return in; - }; - - auto readESTAT = [&](uint8_t mosiPin) -> uint8_t { - digitalWrite(ETH_CS_PIN, LOW); - spiXfer(mosiPin, 0x1D); // RCR ESTAT - uint8_t val = spiXfer(mosiPin, 0x00); - digitalWrite(ETH_CS_PIN, HIGH); - return val; - }; - - // MOSI-Kandidaten - const uint8_t MOSI_CANDIDATES[] = { PC9, PB14, PB15, PB10, PB11, PA5, PA7 }; - const char* MOSI_NAMES[] = {"PC9","PB14","PB15","PB10","PB11","PA5","PA7"}; - const uint8_t NUM_CAND = sizeof(MOSI_CANDIDATES) / sizeof(MOSI_CANDIDATES[0]); - - DebugSerial.println("SPI ESTAT-Scan (Erwartung: 0x01):"); - for (uint8_t c = 0; c < NUM_CAND; c++) { - uint8_t pin = MOSI_CANDIDATES[c]; - pinMode(pin, OUTPUT); digitalWrite(pin, LOW); - - // Chip-Reset zwischen Kandidaten - digitalWrite(ETH_RST_PIN, LOW); delay(5); - digitalWrite(ETH_RST_PIN, HIGH); delay(50); - - uint8_t estat = readESTAT(pin); - - DebugSerial.print(" MOSI="); DebugSerial.print(MOSI_NAMES[c]); - DebugSerial.print(" -> ESTAT=0x"); DebugSerial.print(estat, HEX); - if (estat == 0x01) DebugSerial.println(" <-- TREFFER! SPI OK"); - else if (estat == 0xFF) DebugSerial.println(" (kein Kontakt)"); - else DebugSerial.println(" (unbekannt)"); - - pinMode(pin, INPUT); // danach wieder floating lassen +#if USE_DHCP + if (Ethernet.begin(mac) == 0) { + DebugSerial.println("ETH: DHCP failed, reboot"); + ledSet(LED_RED, true); + delay(3000); + NVIC_SystemReset(); } +#else + IPAddress ip(STATIC_IP); + IPAddress gw(STATIC_GW); + IPAddress sn(STATIC_SUBNET); + IPAddress dns(STATIC_DNS); + Ethernet.begin(mac, ip, dns, gw, sn); +#endif - DebugSerial.println("Scan fertig."); - DebugSerial.println("Setup done (Ethernet deaktiviert bis MOSI gefunden)."); + DebugSerial.print("ETH: IP="); + DebugSerial.println(Ethernet.localIP()); + DebugSerial.print("ETH: link="); + DebugSerial.println(Ethernet.linkStatus() == LinkON ? "UP" : "DOWN"); + + if (Ethernet.linkStatus() == LinkON) ledSet(LED_DEBUG, true); + + // MQTT + mqtt.setServer(MQTT_BROKER, MQTT_PORT); + mqtt.setBufferSize(768); + + // Modbus UART + ModbusSerial.begin(MODBUS_BAUD); + modbus.begin(MODBUS_ADDR, ModbusSerial); + + DebugSerial.println("Setup done."); } // ============================================================ @@ -270,7 +200,6 @@ void setup() { unsigned long lastUpdate = 0; void loop() { - // MQTT Verbindung halten if (!mqtt.connected()) { if (!mqttReconnect()) { delay(5000); @@ -282,11 +211,43 @@ void loop() { if (millis() - lastUpdate < UPDATE_INTERVAL) return; lastUpdate = millis(); + ledSet(LED_BLUE, true); + char stateTopic[64]; char valueStr[16]; - // Modbus temporär deaktiviert — Sensor-Loop übersprungen - DebugSerial.println("loop: MQTT ok, Modbus disabled"); - (void)stateTopic; - (void)valueStr; + for (uint8_t i = 0; i < SENSOR_COUNT; i++) { + const Sensor& s = SENSORS[i]; + + uint8_t result; + uint32_t raw = 0; + + if (s.isDword) { + result = modbus.readInputRegisters(s.address - 1, 2); + if (result == ModbusMaster::ku8MBSuccess) + raw = ((uint32_t)modbus.getResponseBuffer(0) << 16) + | modbus.getResponseBuffer(1); + } else { + result = modbus.readInputRegisters(s.address - 1, 1); + if (result == ModbusMaster::ku8MBSuccess) + raw = modbus.getResponseBuffer(0); + } + + if (result != ModbusMaster::ku8MBSuccess) { + DebugSerial.print("Modbus ERR "); + DebugSerial.print(s.id); + DebugSerial.print(" rc="); + DebugSerial.println(result, HEX); + continue; + } + + float value = raw * s.scale; + dtostrf(value, 1, (s.scale < 0.1f) ? 2 : 1, valueStr); + + snprintf(stateTopic, sizeof(stateTopic), "growatt/shinelan/%s", s.id); + mqtt.publish(stateTopic, valueStr); + } + + ledSet(LED_BLUE, false); + DebugSerial.println("Update done."); }