Files
Shinebridge/Growatt ShineLAN-X/firmware/src/main.cpp
T
retr0 942e6b4e4d ShineLAN-X: USB-CDC Modbus vorbereitet, Modbus vorläufig deaktiviert
- 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>
2026-04-17 05:49:39 +02:00

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.");
}