Einen schönen guten Abend.
Ich und mein Freund ChatGPT haben es geschafft. Ich kann jetzt meine Wärmepumpe auslesen und steuern. Und das bequem von unterwegs mit der APP von openhab
Ich habe mir die ElsterListe schlussendlich selber zusammen kopiert und durch ausprobieren herausgefunden:
Und hier der Code, der die Werte liest und über MQTT versendet und auch empfängt und anschließend auf den Can sendet.
Code: Alles auswählen
#include <AA_MCP2515.h>
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoOTA.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ElsterListe.h>
const CANBitrate::Config CAN_BITRATE = CANBitrate::Config_16MHz_20kbps;
const uint8_t CAN_PIN_CS = 44;
const int8_t CAN_PIN_INT = 43;
bool otaActive = false;
// WiFi
const char* ssid = "change";
const char* password = "change";
// MQTT
const char* hostname = "change";
const char* mqtt_server = "192.168.1.40";
const int mqtt_port = 1883;
const char* mqtt_user = "change";
const char* mqtt_password = "change";
String AckMeldung = "";
bool AckSend = false;
size_t listPos = 36; //Startposition in der ElsterListe.h für periodischen Aufruf
unsigned long lastQueryMillis = 0;
WiFiClient espClient;
PubSubClient client(espClient); // MQTT
// MCP_CAN vorbereiten
const int FRAME_QUEUE_SIZE = 32;
volatile int frameWriteIdx = 0;
volatile int frameReadIdx = 0;
CANFrame frameQueue[FRAME_QUEUE_SIZE];
CANConfig config(CAN_BITRATE, CAN_PIN_CS, CAN_PIN_INT);
CANController CAN(config);
// Index suchen
const ElsterIndex* GetElsterIndex(uint16_t index) {
for(auto &ei : ElsterListe) {
if(ei.Index == index) return &ei;
}
return nullptr;
}
// ======================== ElsterIndex nach Name suchen ========================
const ElsterIndex* GetElsterIndexByName(const char* name) {
for (size_t i = 0; i < elsterListeSize; ++i) {
if (strcasecmp(ElsterListe[i].Name, name) == 0) {
return &ElsterListe[i];
}
}
return nullptr;
}
// ======================== CAN-Frame zum Senden vorbereiten ========================
bool prepareCanFrame(const ElsterIndex* ei, double value, CANFrame &frameOut, uint32_t can_id = 1664) {
if (!ei) return false;
uint16_t index = ei->Index;
uint8_t data[8] = {0};
data[0] = 0x30;
data[1] = 0x00;
bool extended = (index >= 0xFA);
size_t frameLen = 0;
int16_t raw_value = 0;
// === Skalierung gemäß ElsterType ===
switch (ei->Type) {
case et_uint16_val: raw_value = (int16_t)round(value); break;
case et_dec_signed_10: raw_value = (int16_t)round(value * 10.0); break;
case et_dec_signed_100: raw_value = (int16_t)round(value * 100.0); break;
case et_status: raw_value = (int16_t)round(value); break;
}
// === Telegrammaufbau ===
if (ei->Type == et_status) {
if (!extended) {
// kurzer Index, 1 Byte Wert
data[2] = (uint8_t)(index & 0xFF);
data[3] = (uint8_t)(raw_value & 0xFF);
frameLen = 4;
} else {
// erweiterter Index, 1 Byte Wert
data[2] = 0xFA;
data[3] = (uint8_t)((index >> 8) & 0xFF);
data[4] = (uint8_t)(index & 0xFF);
data[5] = (uint8_t)(raw_value & 0xFF);
frameLen = 6;
}
} else {
uint16_t uval = (uint16_t)raw_value;
if (!extended) {
// kurzer Index, 2-Byte Wert
data[2] = (uint8_t)(index & 0xFF);
data[3] = (uint8_t)((uval >> 8) & 0xFF);
data[4] = (uint8_t)(uval & 0xFF);
frameLen = 5;
} else {
// erweiterter Index, 2-Byte Wert
data[2] = 0xFA;
data[3] = (uint8_t)((index >> 8) & 0xFF);
data[4] = (uint8_t)(index & 0xFF);
data[5] = (uint8_t)((uval >> 8) & 0xFF);
data[6] = (uint8_t)(uval & 0xFF);
frameLen = 7;
}
}
frameOut = CANFrame(can_id, data, (uint8_t)frameLen);
return true;
}
// ======================== CAN Callback ========================
void onReceive(CANController&, CANFrame frame) {
int next = (frameWriteIdx + 1) % FRAME_QUEUE_SIZE;
if(next != frameReadIdx) {
frameQueue[frameWriteIdx] = frame;
frameWriteIdx = next;
}
}
void onWakeup(CANController& controller) {
controller.setMode(CANController::Mode::Normal);
}
// ======================== MQTT ========================
void connectMQTT() {
while (!client.connected()) {
client.setServer(mqtt_server, mqtt_port);
char lwtTopic[64];
snprintf(lwtTopic, sizeof(lwtTopic), "%s/statusESP", hostname);
if(client.connect("CAN_MQTT_Client", mqtt_user, mqtt_password, lwtTopic, 0, true, "offline")) {
client.setCallback(callback);
// LWT / Online-Status setzen
client.publish(lwtTopic, "online", true);
// CAN-Daten Topic abonnieren
char canSendTopic[64];
snprintf(canSendTopic, sizeof(canSendTopic), "%s/canDataSend", hostname);
client.subscribe(canSendTopic);
// RSSI senden
int32_t rssi = WiFi.RSSI();
char rssiTopic[64];
snprintf(rssiTopic, sizeof(rssiTopic), "%s/espRSSI", hostname);
char rssiStr[8];
snprintf(rssiStr, sizeof(rssiStr), "%ld", rssi);
client.publish(rssiTopic, rssiStr, true);
}
}
}
// ======================== MQTT Callback ========================
void callback(char* topic, byte* payload, unsigned int length) {
String message;
message.reserve(length + 1);
for (unsigned int i = 0; i < length; i++) message += (char)payload[i];
StaticJsonDocument<384> doc;
DeserializationError err = deserializeJson(doc, message);
if (err) return;
// Erwartetes Format: { "name": "ECOSOLLTEMP", "value": 20.6 }
if (!doc.containsKey("name") || !doc.containsKey("value")) return;
const char* name = doc["name"];
double value = doc["value"].as<double>();
const ElsterIndex* ei = GetElsterIndexByName(name);
if (!ei) {
AckMeldung = "unknown name";
AckSend = true;
return;
}
CANFrame frame;
if (!prepareCanFrame(ei, value, frame)) {
AckMeldung = "frame build failed";
AckSend = true;
return;
}
CANController::IOResult result = CAN.write(frame);
AckMeldung = (result == CANController::IOResult::OK) ? "write OK" : "write Failed";
AckSend = true;
}
// ======================== CAN-Frame auswerten ========================
void processCanFrame(CANFrame &frame) {
if(frame.getDlc() < 3) return; // mindestens 3 Bytes notwendig
const uint8_t* data = frame.getData();
uint16_t index = 0;
uint16_t raw_value = 0;
uint8_t pos = 0;
// Index bestimmen
if(data[2] == 0xFA) { // erweiterter Index
if(frame.getDlc() < 5) return;
index = ((uint16_t)data[3] << 8) | data[4];
pos = 5; // Nutzdaten beginnen hier
} else { // normaler Index
index = data[2];
pos = 3; // Nutzdaten beginnen hier
}
const ElsterIndex* ei = GetElsterIndex(index);
// Raw Value auslesen (für 2-Byte Werte), sonst nur 1 Byte
if (ei && ei->Type == et_status) {
if (frame.getDlc() > pos) raw_value = data[pos];
else return;
} else {
if (frame.getDlc() > pos + 1) raw_value = (data[pos] << 8) | data[pos + 1];
else return;
}
// JSON vorbereiten
StaticJsonDocument<512> doc;
doc["can_id"] = String(frame.getId(), HEX);
char indexStr[7]; sprintf(indexStr, "0x%04x", index);
doc["index"] = indexStr;
doc["raw_value"] = raw_value;
String hexStr;
for(int i=0;i<frame.getDlc();i++){
if(i>0) hexStr+=" ";
if(data[i]<0x10) hexStr+="0";
hexStr += String(data[i], HEX);
}
doc["hex"] = hexStr;
char buffer[256];
char topic[64];
if(ei) {
double value = 0.0;
switch(ei->Type){
case et_uint16_val: value = raw_value; break;
case et_dec_signed_10: value = ((int16_t)raw_value)/10.0; break;
case et_dec_signed_100: value = ((int16_t)raw_value)/100.0; break;
case et_status: value = raw_value; break; // 1 Byte direkt
default: value = raw_value; break;
}
doc["name"] = ei->Name;
doc["value"] = value;
if(ei->Unit) doc["unit"] = ei->Unit;
serializeJson(doc, buffer);
snprintf(topic, sizeof(topic), "%s/canData/%lX", hostname, frame.getId());
client.publish(topic, buffer, false);
} else {
// unbekannter Index
serializeJson(doc, buffer);
snprintf(topic, sizeof(topic), "%s/canUnbekannt", hostname);
client.publish(topic, buffer, false);
}
}
// ======================== Abfragen ========================
void sendElsterListe(uint16_t idx) {
uint8_t data[6]; // max 5 Bytes Index + 1 optionales Statusbyte
uint8_t dlc = 0;
if(idx <= 0xFF){
data[0] = 0x31;
data[1] = 0x00;
data[2] = (uint8_t)(idx & 0xFF);
dlc = 3;
} else {
data[0] = 0x31;
data[1] = 0x00;
data[2] = 0xFA;
data[3] = (uint8_t)(idx >> 8); // IndexHigh
data[4] = (uint8_t)(idx & 0xFF); // IndexLow
dlc = 5;
}
CANFrame frame(0x680, data, dlc); // CAN-ID 0x680 für Abfrage
CAN.write(frame);
}
// ======================== Setup ========================
void setup() {
Serial.begin(115200);
delay(500);
// WiFi-Verbindung
WiFi.setHostname(hostname);
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
delay(500);
}
// MQTT-Verbindung aufbauen
connectMQTT();
// CAN-Controller initialisieren
while(CAN.begin(CANController::Mode::Config) != CANController::OK) {
delay(1000);
}
// CAN-Filter setzen
CAN.setFiltersRxb0(0x180, 0x180, 0x07ff, false);
CAN.setFiltersRxb1(0x180, 0x180, 0x180, 0x180, 0x07ff, false);
CAN.setFilters(true);
// CAN-Modus und Interrupts
CAN.setMode(CANController::Mode::Normal);
CAN.setInterruptCallbacks(&onReceive, &onWakeup);
// OTA-Setup
ArduinoOTA.setHostname(hostname);
ArduinoOTA.setPassword(mqtt_password);
ArduinoOTA.onStart([]() {
otaActive = true; // OTA läuft
});
ArduinoOTA.onEnd([]() {
otaActive = false; // OTA beendet
});
ArduinoOTA.onError([](ota_error_t error) {
otaActive = false;
});
ArduinoOTA.begin();
}
// ======================== Loop ========================
void loop() {
// MQTT-Verbindung prüfen und Loop aufrufen
connectMQTT();
client.loop();
// OTA prüfen
ArduinoOTA.handle();
if (!otaActive) {
// CAN-Frames verarbeiten
while(frameReadIdx != frameWriteIdx){
CANFrame frame = frameQueue[frameReadIdx];
frameReadIdx = (frameReadIdx + 1) % FRAME_QUEUE_SIZE;
processCanFrame(frame);
}
// Gestaffelte Abfrage alle 735ms
if (millis() - lastQueryMillis >= 735) {
sendElsterListe(ElsterListe[listPos].Index);
listPos++;
if (listPos >= elsterListeSize) {
listPos = 36;
}
lastQueryMillis = millis();
}
// MQTT Ack senden
if(AckSend){
char ackTopic[64];
snprintf(ackTopic,sizeof(ackTopic),"%s/canACK",hostname);
client.publish(ackTopic, AckMeldung.c_str(), false);
AckSend = false;
}
} else {
// OTA läuft
static unsigned long lastOTAPrint = 0;
if(millis() - lastOTAPrint > 2000) { // alle 2 Sekunden
lastOTAPrint = millis();
}
}
}