Hi all there,
Is someone out there who tried to connect to the can bus from Mastervolt?
I have a inverter/charger combination, Type is CombiMaster.
You can visualize and Control it via masterbus.
Masterbus is based on can.
I have developed an interface, most of it is already working.
Only a few commands are not working.
One command I need is to switch the charger part off.
But until now I did not found out this command.
Maybe someone can help me.
If someone is interested, please send me a Message.
How is this related to Arduino. If it is Arduino post your schematic and code following forum instructions.
@gilshultz
...yes, you are right, but the sketch was not ready.
Here it is now. If someone has also done investigations in the bus, please tell me.
#include <stdint.h> // Standardisierte Datentypen
// Vorzeichenbehaftete int-Typen
// Typname Bit-Breite Wertebereich
// int8_t 8 −128 ⋯ 127
// int16_t 16 −32768 ⋯ 32767
// int32_t 32 −2147483648 ⋯ 2147483647 int beginnen immer mit kleinem Buchstaben
// int64_t 64 −9223372036854775808 ⋯ 9223372036854775807
// Vorzeichenlose int-Typen
// uint8_t 8 0 ⋯ 255 uint beginnen immer mit großem Buchstaben
// uint16_t 16 0 ⋯ 65535
// uint32_t 32 0 ⋯ 4294967295
// uint64_t 64 0 ⋯ 18446744073709551615
// float 32 auf ca. 6 Kommastellen genau float enden immer mit _f
// double 64 auf ca. 12 Kommastellen genau double enden immer mit _d
// Diverse Librarys --------------------------------------------------------------------------------------------------------------------
#include <Arduino.h>
#include <math.h>
#include <CAN.h>
#include <Preferences.h> // EEprom; Infos: https://unsinnsbasis.de/esp32-preferences/
Preferences prefs;
// WiFi --------------------------------------------------------------------------------------------------------------------
// Das ESP32 arbeiter hier im Station (STA) Mode. Das heißt, das ESP32 verbindet sich mit einem vorhandenen Router und ist selbst ein WiFi Station.
// Das ESP32 erhält seine IP Adresse vom Router. Das ESP32 kann aber auch beim Router eine statische IP Adresse anfragen. (wird weiter unten im SetUp gemacht)
// Auf dem ESP32 läuft ein HTTP Server.
#include <WiFi.h> // is required for doing all WiFi related functionalities such as connection, AP, etc.
// Wenn nur die Daten vom Cortex AIS empfangen werden sollen, ist außer WiFi.h nichts erforderlich.
// Das Cortex AIS sendet die Daten continuirlich über TCP, eine HTTP Anfrage ist nicht erforderlich.
// Das ESP32 ist einfacher Client. Das ESP32 kann entweder direkt an den Access Point des Cortex AIS eingeloggt werden.
// Oder das Cortex AIS wird in das Huawei Netzwerk eingeloggt und kann dann über das Huawei angebunden werden.
// Das reine empfangen wird aber nicht mehr genacht, da die Daten über NMEA2000 empfangen werden.
#include <WebServer.h> // it handles all HTTP protocols
WebServer server(80); // Erforderlich zusammen mit WebSerfer.h
// ********************************************** Adresse und Passwort Netgear
const char* ssid = "sid"; // Wlan SSID
const char* password = "password"; // Wlan Passwort
// Folgende Daten sind erforderlich, um beim Router eine bestimmte IP Adresse anzufragen.
// Die Festlegung geschieht im SetUp, mit der Schleife --- Configures static IP address ---
// Wenn die Gateway, DNS ect. Daten nicht bekannt sind, müssen diese im SetUp erst ermittelt werden.
// Siehe näheres im SetUp.
IPAddress local_IP(192, 168, 178, 98); // Set your Static IP address
IPAddress gateway(192, 168, 178, 1); // Set your Gateway IP address
IPAddress subnet(255, 255, 255, 0); // Set Subnet Maske
IPAddress primaryDNS(192, 168, 178, 1); // optional; Fritzbox gibt dies aus, wenn keine Static IP festgelegt wird;
// Ist in der Fritzbox unter Einstellungen so als lokaler DNS Server eingetragen!!!
IPAddress secondaryDNS(0, 0, 0, 0); // optional; Fritzbox gibt dies aus, wenn keine Static IP festgelegt wird
// OTA ---------------------------------------------------------------------------------------------------------------------------------
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
// Zur Anzeige Filname im Programm:---------------------------------------------------------------------------------------------------------------
#include <string.h>
#define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
// Definition Uhrzeit register -------------------------------------------------------------------------------------------------------------------------------
#include "time.h" // Standard C Library
const char* ntpServer = "pool.ntp.org";
int32_t gmtOffset_sec = 3600; // Offset in Sekunden zu UTC
const int32_t daylightOffset_sec = 3600; // Zusätzlicher Offset in Sekunden zu UTC während Sommerzeit
char TimeDateComplete_char20[21];
String Zeitzone;
// Variablen ---------------------------------------------------------------------------------------------------------
uint32_t zyklusTime, zyklusTimeLast, zyklusTimeHigh, zyklusTimeHighWeb, LEDgruenTime, PreviousCheck, TimeSZ, PollTime, Hardbeat;
uint32_t Length;
uint32_t deviceUniqueId;
uint32_t deviceKindId;
uint32_t Time1s;
uint8_t BlockMon, BlockErr, BlockConfig, BlockHistory;
uint8_t MbDeviceStateIst, MbDeviceStateSoll, MbChargerStateIst;
uint8_t MbInverterOnOffIst, MbInverterPreOffIst, MbChargerOnOffIst, MbChargerPreOffIst;
uint8_t CfLandstromIst, CfACinputSupportModeIst, CfBatterieTypeIst, CfInverterEnergySaveIst, CfInverterEnergySaveSoll,
uint8_t AcFuseSettingIst, CfMaxChargeCurrentIst;
unsigned char RxBuf[8];
int32_t zyklusCount, espRestartCounter, packet, watchDogSchalter;
float inValue_f, wert_f;
String FilenameShort;
String canString[21];
int32_t num;
int32_t numMax = 19;
boolean bFirstCycle, bLEDgruen;
boolean bIRequest;
boolean bDS, bDataOK, bProtocol = true;
#define MonitorDeviceID 0x5506C1A
#define CombiMasterID 0xBC5D
#define PageMonitoring 0x818
#define PageMonitoringRequest 0x1818
#define PageMonRequestID 0x1818BC5D
#define MBDeviceState 0x00
#define MBChargerState 0x01
#define MBAcInputVoltage 0x02
#define MBAcInputCurrent 0x03
#define MBAcInputFrequency 0x04
#define MBAcOutVoltage 0x05
#define MBAcOutFrequency 0x06
#define MBAcOutPowerW 0x07
#define MBAcOutPower2 0x08
#define MBAuslastung 0x09
#define MBInverterOnOff 0x13
#define MBInverterPreOff 0x14
#define MBChargerOnOff 0x15
#define MBChargerPreOff 0x16
#define MBAcNC3 0x0A
#define MBBatterieVoltage 0x0B
#define MBBatterieCurrent 0x0C
#define MBFuseSettingACinput 0x17
float MBDeviceState_f;
float MBChargerState_f;
float MBAcInputVoltage_f;
float MBAcInputCurrent_f;
float MBAcInputFrequency_f;
float MBAcOutVoltage_f;
float MBAcOutFrequency_f;
float MBAcOutPowerW_f;
float MBAcOutPower2_f;
float MBAuslastung_f;
float MBAcNC3_f;
float MBBatterieVoltage_f;
float MBBatterieCurrent_f;
float MBFuseSettingACinput_f;
#define PageAlarm 0x218
#define PageAlarmRequest 0x0328
#define PageAlarmRequestID 0x0318BC5D
#define ALTemperatureHigh 0x00
#define ALFanError 0x01
#define ALDChighVoltage 0x02
#define ALDClowVoltage 0x03
#define ALOverload 0x04
#define ALDeviceError 0x05
#define ALACinError 0x06
float ALTemperatureHigh_f;
float ALFanError_f;
float ALDChighVoltage_f;
float ALDClowVoltage_f;
float ALOverload_f;
float ALDeviceError_f;
float ALACinError_f;
#define PageHistory 0xD98
#define PageHistoryRequest 0x1D98
#define PageHistoryRequestID 0x1D98BC5D
#define HIinverterRunTime 0x00
#define HIchargerRunTime 0x01
float HIinverterRunTime_f;
float HIchargerRunTime_f;
#define PageConfig 0xB98
#define PageConfigRequest 0x1B98
#define PageConfigRequestID 0x1B98BC5D
#define CFInverterEnergySave 0x09
#define CFLandstrom 0x0A
#define CFACinputSupportMode 0x0B
#define CFmaxChargeCurrent 0x0F
#define CFBatterieType 0x10
#define MLI 4
#define ConstVoltage 5
#define CFAValueConstVoltage 0x11
#define CFInverterLowVoltON 0x15
#define CFInverterLowVoltOFF 0x16
#define CFmaxValueAC_IN_Limit 0x33
float CFmaxChargeCurrent_f;
float CFInverterLowVoltON_f;
float CFInverterLowVoltOFF_f;
// Definition E/A -------------------------------------------------------------------------------------------------------------------------------
// Ausgänge:
#define LEDrot 14 // nur auf Testboard
#define LEDgruen 27
// can TX 33
// can RX 32
// Eingänge:
// #define INA_ALE 17 // Board 1, Hauptstrang
// #define LiFePoAlarm 2 // LiFePo Batterie Unterspannung Voralarm
//-----------------------------------------------------------------------------------------------------------------
//--SetUp
//-----------------------------------------------------------------------------------------------------------------
void setup()
{
// Filname bearbeiten ---------------------------------------------------------------------------------------------------------
// Wichtig bei indexOf:
// String beispiel "01234,567890"
// Die 0 steht auf Poition 0
// Das , steht auf Poition 5
// Um einen bestimmten Bereich auszufiltern, zum Beispiel die erste Zifferngruppe, ist die Startposition die entsprechend erste Position, also 0,
// die Endposition aber die entsprechende Position plus 1, also 5!!!!!!
String Filename = __FILENAME__;
int slashPos = Filename.lastIndexOf('/'); // "/" im String von hinten suchen. ...44-073/AnkerAppEmail-V2-44-073.ino
// Nur der Teil nach dem "/" soll genutzt werden. In slashPos wird die Position des "/" abgelegt
FilenameShort = Filename.substring(slashPos+1); // Filname inklusive ".ino"
Length = FilenameShort.length(); // Gesamtlänge String
FilenameShort = FilenameShort.substring(0, (Length-4)); // letzten 4 Zeichen (.ino) entfernen
// Hardware ---------------------------------------------------------------------------------------------------------
// Ausgänge:
pinMode(LEDgruen, OUTPUT);
digitalWrite(LEDgruen, LOW);
// Eingänge:
// pinMode(LiFePoAlarm, INPUT_PULLUP); //
// Serial ---------------------------------------------------------------------------------------------------------
if (1 == 1) {
delay(3000);
Serial.begin(115200); // Kommunikation Serial Terminal
bDS = true;
}
// CAN ---------------------------------------------------------------------------------------------------------
if (bDS == true) Serial.println("CAN Receiver");
CAN.setPins(32, 33); // (rx, tx)
if (!CAN.begin(250E3)) { // start the CAN bus at 250 kbps
if (bDS == true) Serial.println("Starting CAN failed!");
while (1);
}
// EEprom auslesen -------------------------------------------------------------------------------------------------------------------
if (!prefs.begin("Loop")) { // Sehr gute Infos: https://unsinnsbasis.de/esp32-preferences/
// Open Preferences with "Loop" namespace. Each application module, library, etc has to use a
// namespace name to prevent key name collisions. We will open storage in RW-mode
// (second parameter has to be false). Note: Namespace name is limited to 15 chars.
if (bDS == true) Serial.println("Fehler beim Öffnen des NVS-Namespace");
for (;;); // leere Dauerschleife -> Ende
}
if (bDS == true) Serial.print("Anzahl freier Einträge: ");
if (bDS == true) Serial.println (prefs.freeEntries());
// zum erstmaligen laden EEprom --------------------------------------------------------------------------------------------------------------
/*
prefs.clear();
if (bDS == true) Serial.print("Anzahl freier Einträge nach dem Init./Löschen: ");
if (bDS == true) Serial.println (prefs.freeEntries());
if (prefs.putInt("gmtOffset", 3600) == 0)
if (bDS == true) Serial.println("error gmtOffset");
if (bDS == true) Serial.printf("Anzahl freier Einträge nach dem Speichern: %d\n", prefs.freeEntries());
*/
gmtOffset_sec = prefs.getInt("gmtOffset", 0);
// start by connecting to a WiFi network----------------------------------------------------------------------------------------------
if (bDS == true) Serial.println();
if (bDS == true) Serial.print("Connecting to ");
if (bDS == true) Serial.println(ssid);
// Normalerweise wird die IP Adresse durch den Router zugewiesen. (DHCP)
// Da wir aber immer die gleiche IP Adresse nutzen wollen, configurieren wir nachfolgend eine feste IP Adresse innerhalb des privaten Netzwerks.
// Die entsprechenden Daten sind oben in den Definitionen bereits hinterlegt.
// Bei Benutzung eines anderen Routers, müssen die Daten local_IP, gateway, subnet, primaryDNS, secondaryDNS erst ermittelt werden.
// Dazu die folgenden drei Zeilen auskommentieren und die Daten bei aktivem Serial.print anzeigen lassen.
// (Siehe nachfolgende Serial.print Zeilen)
WiFi.mode(WIFI_STA); // Station Mode: ESP32 verbindet sich mit einem Router
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
if (bDS == true) Serial.println("STA Failed to configure");
}
// WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE); // define hostname (Beide Zeilen erforderlich) Hat niccht funktioniert!!!
// WiFi.setHostname(hostname.c_str());
WiFi.begin(ssid, password);
uint32_t TimeWaitMax = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
if (bDS == true) Serial.print(".");
if (millis() - TimeWaitMax > 3000) { // Wenn nach 10 Sekunden keine Verbindung, dann abbrechen
if (bDS == true) Serial.println("Keine Verbindung zum Router möglich, ESP Restart follows");
espRestartCounter = prefs.getInt("espRestartCount", 0);
if (bDS == true) Serial.print("Counter aus EEprom: ");
if (bDS == true) Serial.println(espRestartCounter);
espRestartCounter++;
if (espRestartCounter < 3) {
prefs.putInt("espRestartCount", espRestartCounter);
if (bDS == true) Serial.print("Counter gesichert:**********************************: ");
if (bDS == true) Serial.println(espRestartCounter);
ESP.restart(); // Wenn der ESP noch im Router eingetragn ist, ist eine sofortige, erneute Verbindung
// nicht möglich. Nach dem Restart klappts problemlos.
}
else {
if (bDS == true) Serial.print("Counter 2: ");
if (bDS == true) Serial.println(espRestartCounter);
break; // Nach drittem Versuch break aus der while Schleife und fortfahren ohne Wifi
}
}
}
prefs.putInt("espRestartCount", 0); // Restart Counter ggf. löschen
if (bDS == true) Serial.print("RRSI: ");
if (bDS == true) Serial.println(WiFi.RSSI());
//if (bDS == true) Serial.println("");
//if (bDS == true) Serial.print("Local IP address: "); // Das ist die IP Adresse, welche der Access Point (Huawei Modul) uns als Teilnehmer im
//if (bDS == true) Serial.println(WiFi.localIP()); // Intranet zugeteilt hat.
//if (bDS == true) Serial.print("Subnet Mask: "); // To obtain the subnet mask, we simply need to call the subnetMask method.
//if (bDS == true) Serial.println(WiFi.subnetMask());
//if (bDS == true) Serial.print("Gateway IP: "); // For the gateway IP address, we can call the gatewayIP method.
//if (bDS == true) Serial.println(WiFi.gatewayIP());
//if (bDS == true) Serial.print("DNS 1: "); // To get the main and backup DNS server IP addresses, we can call the dnsIP method, passing as input the values 0 and 1, respectively.
//if (bDS == true) Serial.println(WiFi.dnsIP(0));
//if (bDS == true) Serial.print("DNS 2: ");
//if (bDS == true) Serial.println(WiFi.dnsIP(1));
// Init OTA ------------------------------------------------------------------------------------------------------------------
// Port defaults to 3232
// ArduinoOTA.setPort(3232);
// Hostname defaults to esp3232-[MAC]
ArduinoOTA.setHostname("Esp32-MasterBus");
// No authentication by default
// ArduinoOTA.setPassword("admin");
// Password can be set with it's md5 value as well
// MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
// ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
/*
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
if (bDS == true) Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
if (bDS == true) Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
if (bDS == true) Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
if (bDS == true) Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
if (bDS == true) Serial.println("Auth Failed");
}
else if (error == OTA_BEGIN_ERROR) {
if (bDS == true) Serial.println("Begin Failed");
}
else if (error == OTA_CONNECT_ERROR) {
if (bDS == true) Serial.println("Connect Failed");
}
else if (error == OTA_RECEIVE_ERROR) {
if (bDS == true) Serial.println("Receive Failed");
}
else if (error == OTA_END_ERROR) {
if (bDS == true) Serial.println("End Failed");
}
});
*/
ArduinoOTA.begin();
//if (bDS == true) Serial.println("2. Test");
//if (bDS == true) Serial.println(WiFi.localIP());
// Zeit vom Zeitserver holen -----------------------------------------------------------------------------------------------------------------
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
delay(3000); // 3 Sekunden warten, sonst klappt das noch nicht über WiFi
getTime();
// if (bDS == true) Serial.println(TimeDateComplete_char20);
// Init Webserver -------------------------------------------------------------------------------------------------------------------
// siehe:
// https://lastminuteengineers.com/creating-esp32-web-server-arduino-ide/
// In order to handle incoming HTTP requests, we need to specify which code to execute when a particular URL is hit.
// To do so, we use on method. This method takes two parameters. First one is a URL path and second one is the name of function which
// we want to execute when that URL is hit.
// For example, the first line of below code snippet indicates that when a server receives an HTTP request on the root (/) path,
// it will trigger the handle_OnConnect() function. Note that the URL specified is a relative path.
server.on("/", handle_OnConnect);
server.on("/ref", handle_ref);
server.on("/Charger", handle_Charger);
server.on("/BatteryTypeMLI", handle_BatteryTypeMLI);
server.on("/BatteryTypeConstant", handle_BatteryTypeConstant);
server.on("/MaxChargeCurrent0A", handle_MaxChargeCurrent0A);
server.on("/MaxChargeCurrent5A", handle_MaxChargeCurrent5A);
server.on("/MaxChargeCurrent25A", handle_MaxChargeCurrent25A);
server.on("/MaxChargeCurrent60A", handle_MaxChargeCurrent60A);
server.on("/Inverter", handle_Inverter);
server.on("/InvEnergySave", handle_InverterEnergySaveState);
server.on("/SiLand4A", handle_SiLand4A);
server.on("/SiLand10A", handle_SiLand10A);
server.on("/SiLand16A", handle_SiLand16A);
server.on("/ACinputSupportMode", handle_ACinputSupportMode);
server.on("/UTC1", handle_UTC1);
server.on("/UTC2", handle_UTC2);
// We haven’t specified what the server should do if the client requests any URL other than specified with server.on() .
// It should respond with an HTTP status 404 (Not Found) and a message for the user. We put this in a function as well,
// and use server.onNotFound() to tell it that it should execute it when it receives a request for a URI that wasn’t specified with server.on
server.onNotFound(handle_NotFound);
server.begin(); // Startet den HTTP Server (web Server) im ESP32, so dass andere clients im gleichen Netz
// Init sonstiges -------------------------------------------------------------------------------------------------------------------
bFirstCycle = true;
PreviousCheck = millis();
zyklusTime = millis();
}
//-----------------------------------------------------------------------------------------------------------------
//--Main Program
//-----------------------------------------------------------------------------------------------------------------
void loop() {
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden, z.B. in länger laufenden while Schleifen
server.handleClient(); // To handle the actual incoming HTTP requests, we need to call the handleClient() method on the server object.
ReceiveMasterBus(); // can bus auf neue Daten prüfen
zyklusTime = millis() - zyklusTimeLast;
zyklusTimeLast = millis();
if (zyklusTime > zyklusTimeHigh) {
zyklusTimeHigh = zyklusTime;
}
if (millis() > Time1s == true) {
Time1s += 10000; // 10 Sekunden
//if (bDS == true) Serial.print("zykl Time ms: ");
//if (bDS == true) Serial.println(zyklusTimeHigh);
zyklusTimeHighWeb = zyklusTimeHigh;
zyklusTimeHigh = 0;
}
++zyklusCount;
if (zyklusCount >= 1000) {
digitalWrite(LEDgruen, HIGH); // LED Grün, zur Anzeige, dass Programm läuft
if (bLEDgruen == false) {
bLEDgruen = true;
LEDgruenTime = millis();
}
if (millis() - LEDgruenTime >= 15) {
digitalWrite(LEDgruen, LOW);
bLEDgruen = false;
zyklusCount = 0;
}
}
uint32_t Minuten = millis() / 60000;
if ((Minuten > PreviousCheck) || (bFirstCycle == true)) { // Prüfen, ob Wlan Verbindung noch aktiv
if (WiFi.status() != WL_CONNECTED) {
WiFi.disconnect();
WiFi.reconnect();
// if (bDS == true) Serial.println("WiFi nach Verbindungsabbruch neu gestartet...");
}
else {
// if (bDS == true) Serial.println("WiFi bei Prüfung noch aktiv...");
}
PreviousCheck = Minuten + 10;
}
//------------------------------------------------------------------------------------------------------------------------------------------------
//--Masterbus Daten abfragen
//------------------------------------------------------------------------------------------------------------------------------------------------
if (millis() - PollTime >= 50) {
PollTime = millis();
if (BlockMon <= 17) {
transmitMonitoring(BlockMon);
BlockMon++;
}
else if (BlockErr <= 6) {
transmitError(BlockErr);
BlockErr++;
}
else if (BlockConfig <= 8) {
transmitConfig(BlockConfig);
BlockConfig++;
}
else if (BlockHistory <= 1) {
transmitHistory(BlockHistory);
BlockHistory++;
if (BlockHistory > 1) {
BlockMon = 0;
BlockErr = 0;
BlockConfig = 0;
BlockHistory = 0;
}
}
}
//if(millis() - Hardbeat > 1300)
// {
// transmit6C1B();
// Hardbeat = millis(); // 1300ms Zeitscheibe zurücksetzen
// delay(1);
// }
}
//-----------------------------------------------------------------------------------------------------------------
//--Funktionen für Webserver
//-----------------------------------------------------------------------------------------------------------------
// Next, we need to create a function we attached to root (/) URL with server.on. Remember? At the start of this function,
// we set the status of both the LEDs to LOW (Initial state of LEDs) and print it on serial monitor.
// In order to respond to the HTTP request, we use the send method. Although the method can be called with a different set of arguments,
// its simplest form consists of the HTTP response code, the content type and the content.
// In our case, we are sending the code 200 (one of the HTTP status codes), which corresponds to the OK response.
// Then, we are specifying the content type as “text/html“, and finally we are calling SendHTML() custom function which creates a
// dynamic HTML page containing status of LEDs.
// Likewise, we need to create four functions to handle LED ON/OFF requests and 404 Error page.
void handle_OnConnect() {
server.send(200, "text/html", SendHTML());
}
void handle_ref() {
server.send(200, "text/html", SendHTML());
}
void handle_NotFound(){
server.send(404, "text/plain", "Not found");
}
void handle_Charger() {
bProtocol = true;
num = 0;
if (MbChargerOnOffIst == 0) {
canPageCommand(PageMonRequestID, MBChargerOnOff, 0x3F, 0x80, 0x00);
delay(5);
ReceiveMasterBus();
canPageCommand(PageMonRequestID, MBChargerPreOff, 0x00, 0x00, 0x00);
delay(5);
ReceiveMasterBus();
canPageRequest(PageMonRequestID,MBChargerOnOff, 0x00);
delay(5);
ReceiveMasterBus();
while ((MbChargerOnOffIst != 1) && (MbChargerPreOffIst != 0)) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
canPageRequest(PageMonRequestID,MBChargerPreOff, 0x00);
delay(2);
ReceiveMasterBus(); // MasterBus Daten empfangen
canPageRequest(PageMonRequestID,MBChargerOnOff, 0x00);
delay(2);
ReceiveMasterBus();
if (watchDogSchalter > 1000) { // nach 1000 Zyklen (wenige Sekunden) abbrechen
break;
}
}
}
else if (MbChargerOnOffIst == 1) {
canPageCommand(PageMonRequestID, MBChargerOnOff, 0x00, 0x00, 0x00);
delay(5);
ReceiveMasterBus();
canPageCommand(PageMonRequestID, MBChargerPreOff, 0x3F, 0x80, 0x00);
delay(5);
ReceiveMasterBus();
canPageRequest(PageMonRequestID,MBChargerOnOff, 0x00);
delay(5);
ReceiveMasterBus();
while ((MbChargerOnOffIst != 0) && (MbChargerPreOffIst != 1)) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
canPageRequest(PageMonRequestID,MBChargerOnOff, 0x00);
delay(2);
ReceiveMasterBus(); // MasterBus Daten empfangen
canPageRequest(PageMonRequestID,MBChargerPreOff, 0x00);
delay(2);
ReceiveMasterBus();
if (watchDogSchalter > 1000) { // nach 1000 Zyklen (wenige Sekunden) abbrechen
break;
}
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_BatteryTypeMLI() {
bProtocol = true;
num = 0;
canPageCommand(PageConfigRequestID, CFBatterieType, 0x40, 0x80, 0x00);
while (CfBatterieTypeIst != 4) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_BatteryTypeConstant() {
bProtocol = true;
num = 0;
canPageCommand(PageConfigRequestID, CFBatterieType, 0x40, 0xA0, 0x00);
while (CfBatterieTypeIst != 5) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
//if (bDS == true) Serial.println("Batterie Type Soll = Constant voltage");
server.send(200, "text/html", SendHTML());
}
void handle_MaxChargeCurrent0A() {
bProtocol = true;
num = 0;
canPageCommand(PageConfigRequestID, CFmaxChargeCurrent, 0x00, 0x00, 0x00);
while (CfMaxChargeCurrentIst != 0) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_MaxChargeCurrent5A() {
bProtocol = true;
num = 0;
canPageCommand(PageConfigRequestID, CFmaxChargeCurrent, 0x40, 0xA0, 0x00);
while (CfMaxChargeCurrentIst != 5) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_MaxChargeCurrent25A() {
bProtocol = true;
num = 0;
canPageCommand(PageConfigRequestID, CFmaxChargeCurrent, 0x41, 0xC8, 0x00);
while (CfMaxChargeCurrentIst != 25) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_MaxChargeCurrent60A() {
bProtocol = true;
num = 0;
canPageCommand(PageConfigRequestID, CFmaxChargeCurrent, 0x42, 0x70, 0x00);
while (CfMaxChargeCurrentIst != 60) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_Inverter() {
bProtocol = true;
num = 0;
if (MbInverterOnOffIst == 0) {
canPageCommand(PageMonRequestID, MBInverterOnOff, 0x3F, 0x80, 0x00);
delay(5);
ReceiveMasterBus();
canPageCommand(PageMonRequestID, MBInverterPreOff, 0x00, 0x00, 0x00);
delay(5);
ReceiveMasterBus();
canPageRequest(PageMonRequestID,MBInverterOnOff, 0x00);
delay(5);
ReceiveMasterBus();
while ((MbInverterOnOffIst != 1) && (MbInverterPreOffIst != 0)) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
canPageRequest(PageMonRequestID,MBInverterPreOff, 0x00);
delay(2);
ReceiveMasterBus(); // MasterBus Daten empfangen
canPageRequest(PageMonRequestID,MBInverterOnOff, 0x00);
delay(2);
ReceiveMasterBus();
if (watchDogSchalter > 1000) { // nach 1000 Zyklen (wenige Sekunden) abbrechen
break;
}
}
}
else if (MbInverterOnOffIst == 1) {
canPageCommand(PageMonRequestID, MBInverterOnOff, 0x00, 0x00, 0x00);
delay(5);
ReceiveMasterBus();
canPageCommand(PageMonRequestID, MBInverterPreOff, 0x3F, 0x80, 0x00);
delay(5);
ReceiveMasterBus();
canPageRequest(PageMonRequestID,MBInverterOnOff, 0x00);
delay(5);
ReceiveMasterBus();
while ((MbInverterOnOffIst != 0) && (MbInverterPreOffIst != 1)) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
canPageRequest(PageMonRequestID,MBInverterOnOff, 0x00);
delay(2);
ReceiveMasterBus(); // MasterBus Daten empfangen
canPageRequest(PageMonRequestID,MBInverterPreOff, 0x00);
delay(2);
ReceiveMasterBus();
if (watchDogSchalter > 1000) { // nach 1000 Zyklen (wenige Sekunden) abbrechen
break;
}
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_InverterEnergySaveState() {
bProtocol = true;
num = 0;
if (CfInverterEnergySaveIst == 0) {
CfInverterEnergySaveSoll = 1; // CfInverterEnergySaveSoll zum Ein- oder Ausschalten Energy save mode, je nach aktuellem Status
MbDeviceStateSoll = 4;
}
if (CfInverterEnergySaveIst == 1) {
CfInverterEnergySaveSoll = 0;
MbDeviceStateSoll = 2;
}
if (CfInverterEnergySaveSoll == 1) {
canPageCommand(PageConfigRequestID, CFInverterEnergySave, 0x3F, 0x80, 0x00); // Sende Befehl zum Umschalten Inverter Energy Save Mode
}
if (CfInverterEnergySaveSoll == 0) {
canPageCommand(PageConfigRequestID, CFInverterEnergySave, 0x00, 0x00, 0x00); // Sende Befehl zum Umschalten Inverter Energy Save Mode
}
while (CfInverterEnergySaveIst != CfInverterEnergySaveSoll) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
delay(1);
if (watchDogSchalter > 1000) { // nach 1000 Zyklen (wenige Sekunden) abbrechen
break;
}
}
watchDogSchalter = 0;
delay(1000);
canPageRequest(PageMonRequestID, MBDeviceState, 0x00); // Sende Befehl zum auffrischen Status Inverter
while (MbDeviceStateIst != MbDeviceStateSoll) { // Warten, bis aktualisierter Parameter über MasterBus empfangen wird
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
delay(1);
if (watchDogSchalter > 1000) { // nach 1000 Zyklen (wenige Sekunden) abbrechen
break;
}
}
watchDogSchalter = 0;
//if (bDS == true) Serial.println(CfInverterEnergySaveIst);
server.send(200, "text/html", SendHTML());
}
void handle_ACinputSupportMode() {
bProtocol = true;
num = 0;
if (CfACinputSupportModeIst == 0) {
canPageCommand(PageConfigRequestID, CFACinputSupportMode, 0x3F, 0x80, 0x00); // Sende Befehl zum Umschalten Inverter Energy Save Mode
while (CfACinputSupportModeIst == 0) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
delay(1);
if (watchDogSchalter > 1000) { // nach 1000 Zyklen (wenige Sekunden) abbrechen
break;
}
}
}
else if (CfACinputSupportModeIst == 1) {
canPageCommand(PageConfigRequestID, CFACinputSupportMode, 0x00, 0x00, 0x00); // Sende Befehl zum Umschalten Inverter Energy Save Mode
while (CfACinputSupportModeIst == 1) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
delay(1);
if (watchDogSchalter > 1000) { // nach 1000 Zyklen (wenige Sekunden) abbrechen
break;
}
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_SiLand4A() {
bProtocol = true;
num = 0;
canPageCommand(PageMonRequestID, MBFuseSettingACinput, 0x40, 0x80, 0x00);
while (AcFuseSettingIst != 4) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_SiLand10A() {
bProtocol = true;
num = 0;
canPageCommand(PageMonRequestID, MBFuseSettingACinput, 0x41, 0x20, 0x00);
while (AcFuseSettingIst != 10) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_SiLand16A() {
bProtocol = true;
num = 0;
canPageCommand(PageMonRequestID, MBFuseSettingACinput, 0x41, 0x80, 0x00);
while (AcFuseSettingIst != 16) { // Warten, bis die Umschaltung erfolgt ist und die Antwort
// über MasterBus empfangen wurde
watchDogSchalter++; // Watchdog, zur Absicherung, falls die Umschaltung nicht funtioniert
ArduinoOTA.handle(); // Muss regelmäßig aufgerufen werden!!!!! Kann öfter aufgerufen werden.
ReceiveMasterBus(); // MasterBus Daten empfangen
if (watchDogSchalter > 100) { // nach 100 Zyklen abbrechen
break;
}
}
watchDogSchalter = 0;
server.send(200, "text/html", SendHTML());
}
void handle_UTC1() {
gmtOffset_sec = 3600;
prefs.putInt("gmtOffset", gmtOffset_sec); // UTC Offset im EEprom festhalten
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
getTime();
server.send(200, "text/html", SendHTML());
}
void handle_UTC2() {
gmtOffset_sec = 7200;
prefs.putInt("gmtOffset", gmtOffset_sec); // UTC Offset im EEprom festhalten
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
getTime();
server.send(200, "text/html", SendHTML());
}
// SendHTML() function is responsible for generating a web page whenever the ESP32 web server gets a request from a web client.
// It merely concatenates HTML code into a big string and returns to the server.send() function we discussed earlier.
// The function takes status of LEDs as a parameter to dynamically generate the HTML content.
// The first text you should always send is the <!DOCTYPE> declaration that indicates that we’re sending HTML code.
String SendHTML(){
//Doc****************************************************************************************************************
String ptr = "<!DOCTYPE html>\n";
ptr +="<html lang=\"de\">\n";
//Head***************************************************************************************************************
ptr +="<head>";
ptr +="<meta charset=\"utf-8\">\n";
ptr +="<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n";
ptr +="<title>Libera CombiMaster APP</title>\n";
ptr +="<style>";
ptr +="body {background-color: rgb(230,200,160); font-family: Helvetica; } \n";
ptr +="h1 { color:#000000; font-size:160%; margin-top: 1em; margin-bottom: 0.1em; font-family: Helvetica; } \n";
ptr +="h2 { color:#000000; font-size:120%; margin-top: 1em; margin-bottom: 0.1em; font-family: Helvetica; } \n";
// CSS to style the buttons
ptr +=".button {display: inline-block; background-color: #3498db; border: none; color: white; padding: 10px 15px; text-decoration: none; font-size: 20px; margin: 4px 4px; cursor: pointer; border-radius: 4px;} \n";
ptr +=".button-1 {background-color: #3498db; margin: 6px 6px;}\n"; // hellblau = ON
ptr +=".button-2 {background-color: #2c3e50; margin: 6px 6px;}\n"; // dunkel = OFF
ptr +=".button-3 {background-color: #3498db; margin: 6px 6px; font-size: small;}\n"; // hellblau = ON, kleine Variante
ptr +=".button-4 {background-color: #2c3e50; margin: 6px 6px; font-size: small;}\n"; // grau // dunkel = OFF, kleine Variante
ptr +="</style>\n";
ptr +="</head>\n";
//Body***************************************************************************************************************
ptr +="<body>\n";
// Überschrift---------------------------------------------------------------------------------------------------------
ptr += "<h1>CombiMaster ";
ptr += "<a class=\"button button-4\" href=\"/ref\"> Refresh </a>\n"; // Taste zum Auffrischen der Seite ohne Auslösen von Aktionen
ptr += "</h1>\n";
// CAN Traffic Plotter---------------------------------------------------------------------------------------------------------
// W i c h t i g : Daten werden nur beim Betätigen der Taten einmal mitgeschrieben.
// Um alle mitgeschriebenen Daten anzuzeigen, nach Betätigen der Taste und Empfang der aktualisierten web page nochmals Refresch betätigen.
// ptr += "<h2>CAN Traffic Plotter:</h2>";
// ptr += ("<font face=\"courier\">");
// for (int32_t i = 0; i < numMax; i++) {
// ptr += canString[i];
// ptr += "<br />";
// }
// ptr += ("</font>");
// Tasten Auswahl Batterie Type:---------------------------------------------------------------------------------------------------------------------
ptr += "<h2>Charger, Batterietype, Ladestrom:</h2>";
if (MbChargerOnOffIst == 1) ptr +="<a class=\"button button-3\" href=\"/Charger\">Charger ⏼</a>\n";
else ptr +="<a class=\"button button-4\" href=\"/Charger\">Charger ⏼</a>\n";
if (CfBatterieTypeIst == 4) ptr +="<a class=\"button button-3\" href=\"/BatteryTypeMLI\">LiFePO4</a>\n";
// /BatteryTypeMLI wird an Adresse angefügt
// Li wird als Text auf Taster ausgegeben
else ptr +="<a class=\"button button-4\" href=\"/BatteryTypeMLI\">LiFePO4</a>\n";
if (CfBatterieTypeIst == 5) ptr +="<a class=\"button button-3\" href=\"/BatteryTypeConstant\">13,25V</a>\n";
else ptr +="<a class=\"button button-4\" href=\"/BatteryTypeConstant\">13,25V</a>\n";
ptr += "<br />";
//if (CfMaxChargeCurrentIst == 0) ptr += "<a class=\"button button-3\" href=\"/MaxChargeCurrent0A\">0A</a>\n";
//else ptr += "<a class=\"button button-4\" href=\"/MaxChargeCurrent0A\">0A</a>\n";
if (CfMaxChargeCurrentIst == 5) ptr += "<a class=\"button button-3\" href=\"/MaxChargeCurrent5A\">5A</a>\n";
else ptr += "<a class=\"button button-4\" href=\"/MaxChargeCurrent5A\">5A</a>\n";
if (CfMaxChargeCurrentIst == 25) ptr += "<a class=\"button button-3\" href=\"/MaxChargeCurrent25A\">25A</a>\n";
else ptr += "<a class=\"button button-4\" href=\"/MaxChargeCurrent25A\">25A</a>\n";
if (CfMaxChargeCurrentIst == 60) ptr += "<a class=\"button button-3\" href=\"/MaxChargeCurrent60A\">60A</a>\n";
else ptr += "<a class=\"button button-4\" href=\"/MaxChargeCurrent60A\">60A</a>\n";
// ............................................................................................
ptr += "<h2>Inverter, Supportmode, Landsicherung:</h2>";
if (MbInverterOnOffIst == 1) ptr +="<a class=\"button button-3\" href=\"/Inverter\">Inverter ⏼</a>\n";
else ptr +="<a class=\"button button-4\" href=\"/Inverter\">Inverter ⏼</a>\n";
if (CfInverterEnergySaveIst == 1) ptr += "<a class=\"button button-3\" href=\"/InvEnergySave\">EnergySave</a>\n";
else ptr += "<a class=\"button button-4\" href=\"/InvEnergySave\">EnergySave</a>\n";
if (CfACinputSupportModeIst == 1) ptr += "<a class=\"button button-3\" href=\"/ACinputSupportMode\">Support</a>\n";
else ptr += "<a class=\"button button-4\" href=\"/ACinputSupportMode\">Support</a>\n";
if (AcFuseSettingIst == 4) ptr += "<a class=\"button button-3\" href=\"/SiLand4A\">4A</a>\n";
else ptr += "<a class=\"button button-4\" href=\"/SiLand4A\">4A</a>\n";
if (AcFuseSettingIst == 10) ptr += "<a class=\"button button-3\" href=\"/SiLand10A\">10A</a>\n";
else ptr += "<a class=\"button button-4\" href=\"/SiLand10A\">10A</a>\n";
if (AcFuseSettingIst == 16) ptr += "<a class=\"button button-3\" href=\"/SiLand16A\">16A</a>\n";
else ptr += "<a class=\"button button-4\" href=\"/SiLand16A\">16A</a>\n";
// Werte: ------------------------------------------------------------------------------------------------------------------
ptr += "<h2>Monitoring:</h2>";
// Laut Handbuch: Standby, Charging, Inverting, Supporting, Alarm
ptr += "<table>"; // mit th statt td wirds zentriert
ptr += "<tr>";
ptr += "<td>Status:</td>";
if (MbDeviceStateIst == 0) ptr += "<td>Off</td>";
else if (MbDeviceStateIst == 1) ptr += "<td>Charging</td>";
else if (MbDeviceStateIst == 2) ptr += "<td>Inverter mode</td>";
else if (MbDeviceStateIst == 3) ptr += "<td>Alarm</td>";
else if (MbDeviceStateIst == 4) ptr += "<td>Inverter Energy Save</td>";
else if (MbDeviceStateIst == 5) ptr += "<td>Supporting</td>";
else if (MbDeviceStateIst == 6) ptr += "<td>Landstrom</td>";
else if (MbDeviceStateIst > 6) ptr += "<td>nicht bekannt</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Charger State:</td>";
if (MbChargerStateIst == 0) ptr += "<td>Off</td>";
if (MbChargerStateIst == 1) ptr += "<td>Bulk</td>";
if (MbChargerStateIst == 2) ptr += "<td>Absorption</td>";
if (MbChargerStateIst == 3) ptr += "<td>Float</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Inverter Input voltage:</td>";
ptr += "<td>";
ptr += (MBAcInputVoltage_f);
ptr += "V";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Inverter Input current:</td>";
ptr += "<td>";
ptr += (MBAcInputCurrent_f);
ptr += "A";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Inverter Input frequency:</td>";
ptr += "<td>";
ptr += (MBAcInputFrequency_f);
ptr += "Hz";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Inverter Output voltage:</td>";
ptr += "<td>";
ptr += (MBAcOutVoltage_f);
ptr += "V";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Inverter Output frequency:</td>";
ptr += "<td>";
ptr += (MBAcOutFrequency_f);
ptr += "Hz";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Inverter Output Power:</td>";
ptr += "<td>";
ptr += (MBAcOutPowerW_f);
ptr += "W";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Power 2 (?):</td>";
ptr += "<td>";
ptr += (MBAcOutPower2_f);
ptr += "VA";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Auslastung (?):</td>";
ptr += "<td>";
ptr += (MBAuslastung_f);
ptr += "%";
ptr += "</td>";
ptr += "</tr>";
//ptr += "<tr>";
//ptr += "<td>Inverter MBAcNC3_f:</td>";
//ptr += "<td>";
//ptr += (MBAcNC3bin, BIN);
//ptr += (MBAcNC3_f);
//ptr += "</td>";
//ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Battery voltage:</td>";
ptr += "<td>";
ptr += (MBBatterieVoltage_f);
ptr += "V";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Battery current:</td>";
ptr += "<td>";
ptr += (MBBatterieCurrent_f);
ptr += "A";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Config Page:</td>";
ptr += "<td>";
ptr += " ";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>0x0A (Landstrom vorhanden?):</td>";
if (CfLandstromIst == 0) ptr += "<td>-0-</td>";
if (CfLandstromIst == 1) ptr += "<td>-1-</td>";
if (CfLandstromIst > 1) {
ptr += "<td>-";
ptr += CfLandstromIst;
ptr += "-</td>";
}
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Inverter DC Low ON:</td>";
ptr += "<td>";
ptr += CFInverterLowVoltON_f;
ptr += "h";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Inverter DC Low Off:</td>";
ptr += "<td>";
ptr += CFInverterLowVoltOFF_f;
ptr += "h";
ptr += "</td>";
ptr += "</tr>";
ptr += "</table>";
ptr += "<h2>Fehlermeldungen:</h2>";
if (ALTemperatureHigh_f == 1.00) ptr += "Internal temperature of the CombiMaster is too high<br />";
if (ALFanError_f == 1.00) ptr += "Fan error<br />";
if (ALDChighVoltage_f == 1.00) ptr += "Battery voltage is too high<br />";
if (ALDClowVoltage_f == 1.00) ptr += "Battery voltage is too low<br />";
if (ALOverload_f == 1.00) ptr += "The loads exceed the nominal inverter power<br />";
if (ALDeviceError_f == 1.00) ptr += "Internal CombiMaster error<br />";
if (ALACinError_f == 1.00) ptr += "AC input voltage or frequency out of range<br />";
if (ALTemperatureHigh_f == 0.0 && ALFanError_f == 0.0 && ALDChighVoltage_f == 0.0 && ALDClowVoltage_f == 0.0 && ALOverload_f == 0.0
&& ALDeviceError_f == 0.0 && ALACinError_f == 0.0 && ALTemperatureHigh_f == 0.0) {
ptr += "- kein Fehler -";
}
ptr += "<h2>History:</h2>";
ptr += "<table>"; // mit th statt td werden die Tabellenzeilen zentriert
ptr += "<tr>";
ptr += "<td>Inverter Runtime:</td>";
ptr += "<td>";
ptr += HIinverterRunTime_f;
ptr += "h";
ptr += "</td>";
ptr += "</tr>";
ptr += "<tr>";
ptr += "<td>Charger runtime:</td>";
ptr += "<td>";
ptr += HIchargerRunTime_f;
ptr += "h";
ptr += "</td>";
ptr += "</tr>";
ptr += "</table>";
// Next Message, Version, sonstiges ------------------------------------------------------------------------------------------------------------------
ptr += "<h2>Sonstiges:</h2>";
// getTime();
// ptr += "Zeit: ";
// ptr += TimeDateComplete_char20;
// ptr += "<br />";
ptr += "Vers.: ";
ptr += FilenameShort;
ptr += ", ";
ptr += zyklusTimeHighWeb;
ptr += "ms";
//ptr += "</font>";
ptr += "<br />";
//if (gmtOffset_sec == 3600) ptr +="<a class=\"button button-3\" href=\"/UTC1\">Zeit D/I</a>\n";
// /UTC1 wird an Adresse angefügt
// Zeit D/I wird als Text auf Taster ausgegeben
//else ptr +="<a class=\"button button-4\" href=\"/UTC1\">Zeit D I</a>\n";
//if (gmtOffset_sec == 7200) ptr +="<a class=\"button button-3\" href=\"/UTC2\">Zeit Gr</a>\n";
//else ptr +="<a class=\"button button-4\" href=\"/UTC2\">Zeit Gr</a>\n";
//Foot***************************************************************************************************************
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}
//------------------------------------------------------------------------------------------------------------------------------------------------
//-- Masterbus Daten empfangen
//------------------------------------------------------------------------------------------------------------------------------------------------
void ReceiveMasterBus() {
// if (bDS == true) Serial.print(" and length ");
// if (bDS == true) Serial.println(packet);
packet = CAN.parsePacket(); // try to parse packet
if (packet > 0) { // received a packet
//if (bDS == true) Serial.println (packet);
//if (bDS == true) Serial.print(CAN.packetId(),HEX);
//if (bDS == true) Serial.print(" ");
canString[num] = ("R: ."); // String zum plotten Can Traffic
String part (CAN.packetId(), HEX);
canString[num] += part;
canString[num] += (" ");
canString[num] += packet;
canString[num] += (" ");
deviceUniqueId = (CAN.packetId() & 0x7FFF0000) >> 16;
//if (bDS == true) Serial.print("deviceUniqueId: ");
//if (bDS == true) Serial.print(deviceUniqueId, HEX);
deviceKindId = (CAN.packetId() & 0x0000FFFF); // Geräte ID --> CombiMaster
//if (bDS == true) Serial.print("deviceKindId: ");
//if (bDS == true) Serial.println(deviceKindId, HEX);
bIRequest = (CAN.packetId() & 10000000) != 0;
while (CAN.available()) {
for (int32_t count = 0; count < packet; count++) {
// if (count > 7) break; //maximale Größe array count
RxBuf[count] = CAN.read();
//if (RxBuf[count] < 0x10) {
// if (bDS == true) Serial.print ("0"); // führende 0 bei Werten kleiner 0x10
// }
//if (bDS == true) Serial.print (RxBuf[count], HEX);
//if (bDS == true) Serial.print (" ");
String part (RxBuf[count], HEX);
if (RxBuf[count] < 0x10) canString[num] += ("0"); // führende 0 bei Werten kleiner 0x10
canString[num] += part;
if (count < (packet-1)) canString[num] += (" : ");
}
if (bProtocol == true) num++;
if (num >= numMax) {
num = 20;
bProtocol = false;
}
//if (bDS == true) Serial.print ("\t"); // Tab
inValue_f = decodeIEEE754(&RxBuf[2]); // 00 01 02 03 04 05 Adresse von array RxBuf[2] wird an Funktion übergeben
// if (bDS == true) Serial.print("float: ");
//if (bDS == true) Serial.println(inValue_f);
}
// Datenfeld bei Parameteranfrage:
// 0 - 8 byte möglich
// 0 = Nummrer des angefragten Parameters
// 1 =
// 2 = Wert des angefragten Parameters als IEEE754 Float, byte 0 (niederwertiges
// 3 = byte 1
// 4 = byte 2
// 5 = byte 3 (höherwertiges)
// 6 =
// 7 =
if (deviceKindId == CombiMasterID) { // listen CombiMaster
// Page Monitoring :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
if (deviceUniqueId == PageMonitoring) {
if (RxBuf[0] == MBDeviceState) {
MbDeviceStateIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == MBChargerState) {
MbChargerStateIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == MBAcInputVoltage) {
MBAcInputVoltage_f = inValue_f;
}
if (RxBuf[0] == MBAcInputCurrent) {
MBAcInputCurrent_f = inValue_f;
}
if (RxBuf[0] == MBAcInputFrequency) {
MBAcInputFrequency_f = inValue_f;
}
if (RxBuf[0] == MBAcOutVoltage) {
MBAcOutVoltage_f = inValue_f;
}
if (RxBuf[0] == MBAcOutFrequency) {
MBAcOutFrequency_f = inValue_f;
}
if (RxBuf[0] == MBAcOutPowerW) {
MBAcOutPowerW_f = inValue_f;
}
if (RxBuf[0] == MBAcOutPower2) {
MBAcOutPower2_f = inValue_f;
}
if (RxBuf[0] == MBAuslastung) {
MBAuslastung_f = inValue_f;
}
if (RxBuf[0] == MBAcNC3) {
MBAcNC3_f = inValue_f;
}
if (RxBuf[0] == MBBatterieVoltage) {
MBBatterieVoltage_f = inValue_f;
}
if (RxBuf[0] == MBBatterieCurrent) {
MBBatterieCurrent_f = inValue_f;
}
if (RxBuf[0] == MBFuseSettingACinput) {
MBFuseSettingACinput_f = inValue_f;
AcFuseSettingIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == MBInverterOnOff) {
MbInverterOnOffIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == MBInverterPreOff) {
MbInverterPreOffIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == MBChargerOnOff) {
MbChargerOnOffIst = (uint8_t) inValue_f;
wert_f = inValue_f;
}
if (RxBuf[0] == MBChargerPreOff) {
MbChargerPreOffIst = (uint8_t) inValue_f;
wert_f = inValue_f;
}
}
// Page Alarme :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
if (deviceUniqueId == PageAlarm) {
if (RxBuf[0] == ALTemperatureHigh) {
ALTemperatureHigh_f = inValue_f;
}
if (RxBuf[0] == ALFanError) {
ALFanError_f = inValue_f;
}
if (RxBuf[0] == ALDChighVoltage) {
ALDChighVoltage_f = inValue_f;
}
if (RxBuf[0] == ALDClowVoltage) {
ALDClowVoltage_f = inValue_f;
}
if (RxBuf[0] == ALOverload) {
ALOverload_f = inValue_f;
}
if (RxBuf[0] == ALDeviceError) {
ALDeviceError_f = inValue_f;
}
if (RxBuf[0] == ALACinError) {
ALACinError_f = inValue_f;
}
}
// Page History :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
if (deviceUniqueId == PageHistory) {
if (RxBuf[0] == HIinverterRunTime) {
HIinverterRunTime_f = inValue_f;
}
if (RxBuf[0] == HIchargerRunTime) {
HIchargerRunTime_f = inValue_f;
}
}
// Page Config :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
if (deviceUniqueId == PageConfig) {
if (RxBuf[0] == CFBatterieType) {
CfBatterieTypeIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == CFInverterEnergySave) {
CfInverterEnergySaveIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == CFACinputSupportMode) {
CfACinputSupportModeIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == CFmaxChargeCurrent) {
CfMaxChargeCurrentIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == CFLandstrom) {
CfLandstromIst = (uint8_t) inValue_f;
}
if (RxBuf[0] == CFInverterLowVoltON) {
CFInverterLowVoltON_f = inValue_f;
}
if (RxBuf[0] == CFInverterLowVoltOFF) {
CFInverterLowVoltOFF_f = inValue_f;
}
}
}
}
}
void canPageCommand(uint32_t PageRequestID, uint8_t Parameter, uint8_t byte5, uint8_t byte4, uint8_t byte2) {
// byte 5 und byte 4 sind die höherwertgen bytes des
// über MasterBus übertragenen float Wertes
canString[num] = ("S: "); // String zum plotten Can Traffic
String part (PageRequestID, HEX);
canString[num] += part;
canString[num] += (" 6 ");
String part2 (Parameter, HEX);
if (Parameter < 0x10) canString[num] += ("0"); // führende 0 bei Werten kleiner 0x10
canString[num] += part2;
canString[num] += (" : 00 : ");
String part3 (byte2, HEX);
if (byte2 < 0x10) canString[num] += ("0"); // führende 0 bei Werten kleiner 0x10
canString[num] += part3;
canString[num] += (" : 00 : ");
String part4 (byte4, HEX);
if (byte4 < 0x10) canString[num] += ("0"); // führende 0 bei Werten kleiner 0x10
canString[num] += part4;
canString[num] += (" : ");
String part5 (byte5, HEX);
if (byte5 < 0x10) canString[num] += ("0"); // führende 0 bei Werten kleiner 0x10
canString[num] += part5;
if (bProtocol == true) num++;
if (num >= numMax) {
num = 20;
bProtocol = false;
}
CAN.beginExtendedPacket(PageRequestID,6);
CAN.write(Parameter); // Adresse Parameter
CAN.write(0x00);
CAN.write(byte2);
CAN.write(0x00);
CAN.write(byte4); // Value
CAN.write(byte5);
CAN.endPacket();
delay(1);
}
void transmit6C1B() {
CAN.beginExtendedPacket(MonitorDeviceID,0);
CAN.endPacket();
delay(1);
}
void transmitMonitoring(uint8_t MsgBlock) {
unsigned char stmp;
switch (MsgBlock)
{
case 0:
stmp = MBDeviceState;
break;
case 1:
stmp = MBChargerState;
break;
case 2:
stmp = MBAcInputVoltage;
break;
case 3:
stmp = MBAcInputCurrent;
break;
case 4:
stmp = MBAcInputFrequency;
break;
case 5:
stmp = MBAcOutVoltage;
break;
case 6:
stmp = MBAcOutFrequency;
break;
case 7:
stmp = MBAcOutPowerW;
break;
case 8:
stmp = MBAcOutPower2;
break;
case 9:
stmp = MBAuslastung;
break;
case 10:
stmp = MBAcNC3;
break;
case 11:
stmp = MBBatterieVoltage;
break;
case 12:
stmp = MBBatterieCurrent;
break;
case 13:
stmp = MBFuseSettingACinput;
break;
case 14:
stmp = MBInverterOnOff;
break;
case 15:
stmp = MBInverterPreOff;
break;
case 16:
stmp = MBChargerOnOff;
break;
case 17:
stmp = MBChargerPreOff;
break;
default:
break;
}
canPageRequest(PageMonRequestID, stmp, 0x00);
}
void transmitError(uint8_t MsgBlock) {
unsigned char stmp;
stmp = MsgBlock;
canPageRequest(PageAlarmRequestID, stmp, 0x00);
}
void transmitHistory(uint8_t MsgBlock) {
unsigned char stmp;
stmp = MsgBlock;
canPageRequest(PageHistoryRequestID, stmp, 0x00);
}
void transmitConfig(uint8_t MsgBlock) {
unsigned char stmp;
switch (MsgBlock)
{
case 0:
stmp = CFInverterEnergySave;
break;
case 1:
stmp = CFACinputSupportMode;
break;
case 2:
stmp = CFmaxChargeCurrent;
break;
case 3:
stmp = CFBatterieType;
break;
case 4:
stmp = CFAValueConstVoltage;
break;
case 5:
stmp = CFmaxValueAC_IN_Limit;
break;
case 6:
stmp = CFLandstrom;
break;
case 7:
stmp = CFInverterLowVoltON;
break;
case 8:
stmp = CFInverterLowVoltOFF;
break;
default:
break;
}
canPageRequest(PageConfigRequestID, stmp, 0x00);
}
void canPageRequest(uint32_t PageRequestID, uint8_t Byte0, uint8_t Byte1) {
if (PageRequestID == PageAlarmRequestID) { // Erstellen String zum plotten Can Traffic
canString[num] = ("S: 0"); // bei Alarm Page führende 0 einfügen, wegen besserer Lesbarkeit beim mitschreiben
}
else canString[num] = ("S: ");
String part (PageRequestID, HEX);
canString[num] += part;
canString[num] += (" 2 ");
String part2 (Byte0, HEX);
if (Byte0 < 0x10) canString[num] += ("0"); // führende 0 bei Werten kleiner 0x10
canString[num] += part2;
canString[num] += (" : 00 ");
if (bProtocol == true) num++;
if (num >= numMax) {
num = 20;
bProtocol = false;
}
CAN.beginExtendedPacket(PageRequestID,2); // 0x1 steht für eine Anfrage,
// 0xB98 = PageConfigRequest
// 0xBC5D Geräte ID CombiMaster
CAN.write(Byte0);
CAN.write(Byte1);
CAN.endPacket();
delay(1);
}
float decodeIEEE754(uint8_t *data) {
uint32_t temp = ((uint32_t)data[3]<< 24 | (uint32_t)data[2]<< 16 | (uint32_t)data[1]<< 8 | (uint32_t)data[0]);
//float inValue_f = *(float*)&temp;
return *(float*)&temp;
}
void encodeIEEE754(float f, byte* hex) {
byte* f_byte = reinterpret_cast<byte*>(&f);
memcpy(hex, f_byte, 4);
}
//-----------------------------------------------------------------------------------------------------------------
//--Funktion Real Time Clock --
//-----------------------------------------------------------------------------------------------------------------
// aus https://randomnerdtutorials.com/esp32-date-time-ntp-client-server-arduino/
// aus https://www.proggen.org/doku.php?id=c:lib:time:struct_tm
// struct tm ist definiert in der time, die in C über #include "time.h" eingebunden wird.
// struct tm speichert einen Zeitstempel und zusätzliche Metainformationen in einer Struktur.
// Dabei werden die einzelnen Informationen des Zeitstempels nicht wie bei time_t in komprimierter Form gehalten,
// sondern in einzelne Komponenten (Stunde, Minute, Sekunde) unterteilt gespeichert.
// struct tm enthält neun Einträge vom Typ int, wobei die Reihenfolge innerhalb der Struktur nicht festgelegt ist.:
// struct tm
// {
// int tm_sec;
// int tm_min;
// int tm_hour;
// int tm_mday;
// int tm_mon;
// int tm_year;
// int tm_wday;
// int tm_yday;
// int tm_isdst; // Sommerzeit Flag; Das Sommerzeit-Flag (tm_isdst) ist größer als 0, wenn es zurzeit Sommerzeit ist. Im Falle von Winterzeit ist es genau 0.
// Ist es kleiner als 0, so ist die Information nicht verfügbar.
// };
void getTime(){
time_t now; // time_t speichert einen Zeitstempel, welcher in ganzen Sekunden beginnend mit dem 1. Januar 1970,
// 00:00:00 UTC gezählt wird. time_t entspricht in der Regel dem Datentyp int, ist also 32 Bit breit.
// (ESP32 64 bit!!!11)
// time_t entspricht somit dem Unix-Timestamp.
// Mit time_t now; wird eine Variable "now" zum Abspeichern der Zeit erstellt. (64 bit)
struct tm *myTm; // erstelle zeiger auf struct tm
time(&now); // Die Funktion time() gibt die aktuelle Systemzeit als time_t-Objekt zurück.
// Wenn das Argument nicht NULL ist, dann wird der Wert auch in das angegebene time_t-Objekt geschrieben.
// now enthält ohne Verbindung zum Internet jetzt die aktuelle Systemzeit, 0 Sekunden ab 1. Jan. 1970 00:00:00,
// + / - die in der Konfiguration angegebene Sekunden für die Zeitzone.
// Besteht eine Verbindung zum Internet holt time jetzt die aktuelle Zeit vom NTP Server nach "now"
// Besteht später keine Verbindung mehr, so bleibt die Systemzeit trotzdem korrekt, ohne Synchronisierung.
myTm = localtime(&now); // Übersetzt einen Zeitstempel (time_t) in eine tm-Struktur in die lokale Zeitzone.
// und füllt struct tm mit dem Zeitstempel aus "now"
// if (now == ((time_t)-1)) {
// Debug_println("Zeit konnte nicht geholt werden");
// }
strftime(TimeDateComplete_char20,20, "%d.%m.%Y %X",myTm); // 01.02.2022 00:00:00
// strftime ist definiert in der time, die in C über #include "time.h" eingebunden wird.
// strftime Erzeugt einem char String aus einzelnen char. Die einzelnen Zeitmodule werden aus der Structur tm entnommen.
// Für jedes Zeitmodul gibt es ein Zeichen welches mit % beginnt.
// Der Formatstring darf beliebige Zeichen enthalten. Zeichen, die mit einem % beginnen sind Sonderzeichen und werden entsprechend
// der Tabelle --> siehe https://www.proggen.org/doku.php?id=c:lib:time:strftime
// Debug_print("Zeit aus Funktion: ");
// Debug_println(TimeDateComplete_char20);
//TimeStunde = myTm->tm_hour; // Stunde als int extrahieren
TimeSZ = myTm->tm_isdst; // Sommer oder Winterzeit (siehe oben)
if ((TimeSZ == 0) && (gmtOffset_sec == 3600)) Zeitzone = "UTC+1";
if ((TimeSZ == 0) && (gmtOffset_sec == 7200)) Zeitzone = "UTC+2";
if ((TimeSZ == 1) && (gmtOffset_sec == 3600)) Zeitzone = "UTC+2";
if ((TimeSZ == 1) && (gmtOffset_sec == 7200)) Zeitzone = "UTC+3";
}
Hello @libera I would be interested to hear more about your project, I am looking to integrate an ESP32 with a Mastervolt Battery Charger. Is there a GitHub link to your project or any other material you found that might be useful?
Thanks
Hello,
if you send me a real email address I will send you all information I have.
greetings,
Manfred
Entschuldigung, ich spreche kein Deutsch.That code looks great and well written. It appears you have done this many times before. I will try to translate this weekend and see what I can make of it. Can you give the make and model of your Mastervolt device. I am use to Cory J Fowler's library. It is similar to something I am working on but all the targets are my design. More as I get into it.
If you could send it to tristan@metrixadvancedtechnologies.com, that would be amazing, thanks.
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.