942e6b4e4d
- Wechselrichter-Kommunikation läuft über USB-CDC (PA11=D-, PA12=D+, PA8=Pullup) - Modbus-Platzhalter bis USB-Port wieder eingelötet und getestet - USB-CDC build flags entfernt (blockieren ohne Host), werden separat aktiviert - Ethernet + MQTT läuft stabil (DHCP, HA Discovery) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
239 lines
9.6 KiB
C++
239 lines
9.6 KiB
C++
#include <Arduino.h>
|
|
#include <SPI.h>
|
|
#include <EthernetENC.h>
|
|
#include <PubSubClient.h>
|
|
#include <ModbusMaster.h>
|
|
#include "config.h"
|
|
|
|
// Debug-UART: USART1 TX=PA9, RX=PA10
|
|
HardwareSerial DebugSerial(PA10, PA9);
|
|
|
|
// Modbus über USB-CDC (SerialUSB = PA11/PA12, Wechselrichter ist USB-Host)
|
|
|
|
// ============================================================
|
|
// Sensor-Definition
|
|
// ============================================================
|
|
struct Sensor {
|
|
const char* id;
|
|
const char* name;
|
|
uint16_t address;
|
|
bool isDword;
|
|
float scale;
|
|
const char* unit;
|
|
const char* deviceClass;
|
|
const char* stateClass;
|
|
const char* icon;
|
|
};
|
|
|
|
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"},
|
|
// --- 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"},
|
|
// --- 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"},
|
|
// --- Temperatur ---
|
|
{"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"},
|
|
// --- 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
|
|
// ============================================================
|
|
byte mac[] = {MAC_ADDRESS};
|
|
EthernetClient ethClient;
|
|
PubSubClient mqtt(ethClient);
|
|
ModbusMaster modbus;
|
|
|
|
// ============================================================
|
|
// LED-Hilfsfunktionen
|
|
// ============================================================
|
|
void ledSet(uint8_t pin, bool on) { digitalWrite(pin, on ? LOW : HIGH); } // aktiv LOW
|
|
|
|
// ============================================================
|
|
// MQTT
|
|
// ============================================================
|
|
void publishDiscovery() {
|
|
char topic[128];
|
|
char payload[640];
|
|
|
|
for (uint8_t i = 0; i < SENSOR_COUNT; i++) {
|
|
const Sensor& s = SENSORS[i];
|
|
|
|
snprintf(topic, sizeof(topic),
|
|
"homeassistant/sensor/%s_%s/config",
|
|
DEVICE_ID, s.id);
|
|
|
|
snprintf(payload, sizeof(payload),
|
|
"{"
|
|
"\"name\":\"%s\","
|
|
"\"unique_id\":\"%s_%s\","
|
|
"\"state_topic\":\"growatt/shinelan/%s\","
|
|
"\"unit_of_measurement\":\"%s\","
|
|
"\"device_class\":\"%s\","
|
|
"\"state_class\":\"%s\","
|
|
"\"icon\":\"%s\","
|
|
"\"device\":{"
|
|
"\"identifiers\":[\"%s\"],"
|
|
"\"name\":\"%s\","
|
|
"\"model\":\"%s\","
|
|
"\"manufacturer\":\"%s\""
|
|
"}}",
|
|
s.name,
|
|
DEVICE_ID, s.id,
|
|
s.id,
|
|
s.unit, s.deviceClass, s.stateClass, s.icon,
|
|
DEVICE_ID, DEVICE_NAME, DEVICE_MODEL, DEVICE_MFR);
|
|
|
|
mqtt.publish(topic, payload, true);
|
|
}
|
|
}
|
|
|
|
bool mqttReconnect() {
|
|
DebugSerial.print("MQTT connecting... ");
|
|
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=");
|
|
DebugSerial.println(mqtt.state());
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// ============================================================
|
|
// Setup
|
|
// ============================================================
|
|
void setup() {
|
|
DebugSerial.begin(115200);
|
|
delay(10);
|
|
DebugSerial.println("\r\n=== Growatt ShineLAN-X ===");
|
|
DebugSerial.println("Build: " __DATE__ " " __TIME__);
|
|
|
|
// 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);
|
|
|
|
// 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(20);
|
|
digitalWrite(ETH_RST_PIN, HIGH); delay(200);
|
|
|
|
Ethernet.init(ETH_CS_PIN);
|
|
DebugSerial.println("ETH: begin...");
|
|
|
|
#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.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: USB-CDC (PA11/PA12) — wird aktiviert sobald Wechselrichter angeschlossen
|
|
// SerialUSB.begin(MODBUS_BAUD);
|
|
// modbus.begin(MODBUS_ADDR, SerialUSB);
|
|
|
|
DebugSerial.println("Setup done.");
|
|
}
|
|
|
|
// ============================================================
|
|
// Loop
|
|
// ============================================================
|
|
unsigned long lastUpdate = 0;
|
|
|
|
void loop() {
|
|
if (!mqtt.connected()) {
|
|
if (!mqttReconnect()) {
|
|
delay(5000);
|
|
return;
|
|
}
|
|
}
|
|
mqtt.loop();
|
|
|
|
if (millis() - lastUpdate < UPDATE_INTERVAL) return;
|
|
lastUpdate = millis();
|
|
|
|
ledSet(LED_BLUE, true);
|
|
|
|
char stateTopic[64];
|
|
char valueStr[16];
|
|
|
|
for (uint8_t i = 0; i < SENSOR_COUNT; i++) {
|
|
const Sensor& s = SENSORS[i];
|
|
|
|
uint8_t result;
|
|
uint32_t raw = 0;
|
|
|
|
// TODO: Modbus aktivieren sobald USB-CDC (Wechselrichter) angeschlossen
|
|
result = ModbusMaster::ku8MBSuccess; // Platzhalter
|
|
raw = 0;
|
|
(void)result;
|
|
|
|
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.");
|
|
}
|