Files
Arduino/ESP8266_VICTRON/ESP8266_VICTRON.ino
Jérôme Delacotte 7b30d6e298 first commit
2025-03-06 11:15:32 +01:00

697 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/////////////////////
// Domoticz Classe
/////////////////////
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <ESP8266WebServer.h>
#include <SoftwareSerial.h>
#include <ArduinoJson.h>
void handleRoot(void);
int getValueFromParam();
void setPwm(int pwm);
const char* ssid = "Livebox-37cc";
const char* pass = "8A6060920A8A86896F770F2C47";
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 0, 0);
IPAddress DNS(192, 168, 1, 1);
// DIMMER
ESP8266WebServer server(80);
void ICACHE_RAM_ATTR handleInterrupt();
//// Dimmer
//#include <RBDdimmer.h>//
//
//#define LEDn 15
//#define outputPin D5 // D5
//#define zerocross D6 // D6 for boards with CHANGEBLE input pins
//#define pas 5
//
//dimmerLamp dimmer(outputPin, zerocross); //initialase port for dimmer for ESP8266, ESP32, Arduino due boards
////dimmerLamp dimmer(outputPin); //initialase port for dimmer for MEGA, Leonardo, UNO, Arduino M0, Arduino Zero
//
// ##########################################
// VICTRON
String devicename = "Victron Blue Solar";
#include "config.h"
#define PRINT_EVERY_SECONDS 5
#define rxPin D4 //D7
#define txPin 1 // D8 // TX Not used
SoftwareSerial victronSerial(rxPin, txPin);
// via the USB serial provided by the NodeMCU.
char receivedChars[buffsize]; // an array to store the received data
char tempChars[buffsize]; // an array to manipulate the received data
char recv_label[num_keywords][label_bytes] = {0}; // {0} tells the compiler to initalize it with 0.
char recv_value[num_keywords][value_bytes] = {0}; // That does not mean it is filled with 0's
char value[num_keywords][value_bytes] = {0}; // The array that holds the verified data
static byte blockindex = 0;
bool new_data = false;
bool blockend = false;
int outVal = 0;
String webpage = R"=(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Victron Control</title>
<SCRIPT>
var xmlHttp01=createXmlHttpObject();
function createXmlHttpObject(){
if(window.XMLHttpRequest){
xmlHttp=new XMLHttpRequest();
}else{
xmlHttp=new ActiveXObject('Microsoft.XMLHTTP');
}
return xmlHttp;
}
function process(){
if(xmlHttp01.readyState==0 || xmlHttp01.readyState==4){
xmlHttp01.open('GET','getData',true);
xmlHttp01.onreadystatechange=handleServerResponse;
xmlHttp01.send(null);
}
setTimeout('process()', 5000);
}
function handleServerResponse(){
console.log(xmlHttp01.readyState);
console.log(xmlHttp01.status);
if(xmlHttp01.readyState==4 && xmlHttp01.status==200){
const json_obj = JSON.parse(xmlHttp01.response);
console.log(xmlHttp01.response);
var result_text = "<table>";
for (var i = 0; i < json_obj.length; i++){
var obj = json_obj[i];
result_text = result_text + "<tr><td style=\"text-align: left;\">" + obj.id + "</td><td style=\"text-align: center;\">" + obj.value + "</td></tr>";
}
result_text = result_text + "</table>";
document.getElementById('response_items').innerHTML=result_text;
}
}
</SCRIPT>
<style>
body {
font-family: 'Arial', sans-serif;
background-color: #f2f2f2;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
height: 100vh;
}
.control-container {
background: #C0C0C0;
width: 128px;
height: 64px;
margin: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s ease-in-out; /* Ajout de la transition */
}
h2,button {
margin: 0;
font-size: 1rem;
}
p {
font-size: 1.2rem;
margin-top: 20px;
}
/* Ajout des styles au survol ou au clic */
.control-container:hover,
.control-container:active {
background-color: #a0a0a0;
}
.arcade-button {
display: inline-block;
position: relative;
width: 150px;
height: 150px;
background-color: #3498db;
border: 10px solid #2c3e50;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
transition: background-color 0.3s ease-in-out;
}
.arcade-button:hover {
background-color: #2980b9;
}
.button-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #ecf0f1;
font-size: 1.5rem;
text-align: center;
}
h1 {
font-size: 120%;
color: blue;
margin: 0 0 10px 0;
}
table{
border-collapse: collapse;
}
table, th, td {
border: 0px solid blue;
}
</style>
</head>
<BODY onload="process()">
<div class="control-container" id="LEDn">
<button onclick="alert('on');">ON</button>
</div>
<div class="control-container" id="pwmplus">
<a href="/plus?value=10">Plus</a>
</div>
<div class="control-container" id="pwmminus">
<a href="/minus?value=10">Minus</a>
</div>
<div class="control-container" id="pwmstop">
<a href="/stop">Stop</a>
</div>
<div class="arcade-button">
<div class="button-content">@@pwmCount@@</div>
</div>
<div class="victron_data">
<span style="font-family: Arial, Helvetica, sans-serif;font-size: 20px;font-weight: bold;color: #a5a5a5;"><A id='response_items'></A></span>
</div>
</body>
</html>
)=";
bool led;
int pwm;
int pwmCount;
//
//void ledON(){
// led=1;
// digitalWrite(LEDn, led);
// handleRoot();
//
//}
//
//void ledOFF(){
// led=0;
// digitalWrite(LEDn, led);
// handleRoot();
//}
//
////pwm functions - if pwm is set to 1/-1 it will in-/decrease pwmCounter in loop()
//void exact(){
// int value_to_set = getValueFromParam();
//
// pwm=value_to_set;
// pwmCount = pwm;
// if (pwmCount>100) pwmCount = 100;
// if (pwmCount < 0) {
// pwmCount = 0;
// }
//
// Serial.println(pwmCount);
// //dimmer.setPower(pwmCount);
// setPwm(pwmCount);
//
// handleRoot();
//}
//void plus(){
// int value_to_set = getValueFromParam();
// if (value_to_set <= 0) {
// value_to_set = 5;
// }
// pwm+=value_to_set;
// setPwm(pwm);
// handleRoot();
//}
//
//void minus(){
// int value_to_set = getValueFromParam();
// if (value_to_set <= 0) {
// value_to_set = 5;
// }
// pwm+=-value_to_set;
//
// setPwm(pwm);
// handleRoot();
//}
//
//void stopPWM(){
// pwm=0; //-pwmCount;
// setPwm(pwm);
// handleRoot();
//}
//
//void setPwm(int pwm)
//{
// pwmCount = pwm;
// if (pwmCount>100) pwmCount = 100;
// if (pwmCount <= 1) {
// pwmCount = 0;
// }
//
// Serial.println(pwmCount);
//
// if (pwmCount <= 0) {
// dimmer.setState(ON_OFF_typedef::OFF);
// }
// else {
// dimmer.setState(ON_OFF_typedef::ON);
// }
// dimmer.setPower(pwmCount);
//}
//----------------------------------------------------------------------------------
void handleRoot(void){
String page = webpage;
page.replace("@@pwmCount@@", String(pwmCount));
//Serial.println(page);
server.send(200, "text/html", page);
}
//int getValueFromParam()
//{
// String message = "Number of args received:";
//
// message += server.args();
//
// for (int i = 0; i < server.args(); i++) {
// message = message + ("Arg nº" + String(i) + " > ");
// message = message + (server.argName(i) + ": ");
// message = message + (server.arg(i) + "\n");
// if (server.argName(i) == "value") {
// return String(server.arg(i)).toInt();
// }
// }
// Serial.println(message);
// return pas;
//}
void initWifi()
{
WiFi.mode(WIFI_AP);
WiFi.begin(ssid, pass); //Connect to local Wifi
Serial.println();
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
delay(500);
}
Serial.println("WiFi Connected!");
Serial.print("Connexion au reseau ");
Serial.println(WiFi.localIP());
}
void initWifiStatic(
IPAddress ip,
IPAddress gateway,
IPAddress subnet,
IPAddress DNS)
{
WiFi.config(ip, gateway, subnet, DNS);
delay(100);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(200);
}
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println();
Serial.println("Fail connecting");
delay(5000);
ESP.restart();
}
Serial.print(" static OK ");
Serial.print("Module IP: ");
Serial.println(WiFi.localIP());
}
String generateKey()
{
// WiFi.mode(WIFI_AP);
// Do a little work to get a unique-ish name. Append the
// last two bytes of the MAC (HEX'd) to "Thing-":
uint8_t mac[WL_MAC_ADDR_LENGTH];
WiFi.softAPmacAddress(mac);
String macID = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) + String(mac[WL_MAC_ADDR_LENGTH - 1], HEX);
macID.toUpperCase();
String AP_NameString = "ESP8266 Thing " + macID;
char AP_NameChar[AP_NameString.length() + 1];
memset(AP_NameChar, 0, AP_NameString.length() + 1);
for (int i = 0; i < AP_NameString.length(); i++)
{
AP_NameChar[i] = AP_NameString.charAt(i);
}
// WiFi.softAP(AP_NameChar, WiFiAPPSK);
Serial.println("macID=" + macID);
return macID;
}
IPAddress getIP(String macId)
{
IPAddress ip; //(192, 168, 1, 222);
String fst = macId.substring(0, 2);
String sec = macId.substring(2);
char fstc[fst.length() + 1];
fst.toCharArray(fstc, fst.length() + 1);
char secc[sec.length() + 1];
sec.toCharArray(secc, fst.length() + 1);
return IPAddress(192, 168, strtol(fstc, 0, 16), strtol(secc, 0, 16));
}
// Méthode pour gérer la requête '/getData'
void handleData() {
String JSON = F("[");
for (int i = 0; i < num_keywords; i++){
JSON += "{\"id\":\"" + String(keywords[i]) + "\", \"value\":\"" + String(value[i]) + "\"},";
}
JSON += "{\"id\":\"end\", \"value\":\"end\"}";
JSON += "]";
server.send(200, "application/json", JSON);
// // Créer un objet JSON
// DynamicJsonDocument doc(200);
//
// for (int i = 0; i < num_keywords; i++){
// doc[String(keywords[i])] = String(value[i]);
// }
//
// // Convertir l'objet jsonData en chaîne
// String jsonData;
// serializeJson(doc, jsonData);
//
// // Envoyer la réponse JSON au client
// server.send(200, "application/json", jsonData);
}
void setup()
{
Serial.begin(115200);
victronSerial.begin(19200);
//delay(200);
// String macId = generateKey();
// IPAddress ip = getIP(macId);
//
// // Conversion d'objet en pointeur
// initWifiStatic(ip, gateway, subnet, DNS);
// Connectez-vous au réseau WiFi
WiFi.begin(ssid, pass);
Serial.println("Connexion au WiFi en cours.");
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("Connecté au réseau WiFi");
Serial.println(WiFi.localIP());
//Server________________________________
server.begin();
server.on("/", handleRoot);
// server.onNotFound([](AsyncWebServerRequest *request){
// if (request->method() == HTTP_OPTIONS){
// request->send(204);
// }
// else {
// request->send(404);
// }
// });
// server.on("/LEDon", ledON);
// server.on("/LEDoff", ledOFF);
server.on("/getData", HTTP_GET, handleData);
// server.on("/minus", minus);
// server.on("/plus", plus);
// server.on("/exact", exact);
// server.on("/stop", stopPWM);
//initialize variables__________________
pwm = 0;
pwmCount= 0;
led = 0;
Serial.print("Module IP: ");
Serial.println(WiFi.softAPIP());
// // Dimmer
// Serial.println("Dimmer Program is starting...");
// delay(1000);
// dimmer.begin(NORMAL_MODE, ON); //dimmer initialisation: name.begin(MODE, STATE)
// Serial.println("Set value");
// dimmer.setState(ON_OFF_typedef::OFF);
// dimmer.setPower(pwmCount); // setPower(0-100%);
// send temperature every 30s
// flipper.attach(30, flip);
// Port defaults to 8266
ArduinoOTA.setPort(8266);
// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");
// 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_FS
type = "filesystem";
}
// NOTE: if updating FS this would be the place to unmount FS using FS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
Serial.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
Serial.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
Serial.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
Serial.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
Serial.println("End Failed");
}
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
long temps = 0;
// Serial Handling
// ---
// This block handles the serial reception of the data in a
// non blocking way. It checks the Serial line for characters and
// parses them in fields. If a block of data is send, which always ends
// with "Checksum" field, the whole block is checked and if deemed correct
// copied to the 'value' array.
void recvWithEndMarker() {
static byte ndx = 0;
char endMarker = '\n';
char rc;
while (victronSerial.available() > 0 && new_data == false) {
rc = victronSerial.read();
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= buffsize) {
ndx = buffsize - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
ndx = 0;
new_data = true;
}
yield();
}
}
void parseData() {
char * strtokIndx; // this is used by strtok() as an index
strtokIndx = strtok(tempChars,"\t"); // get the first part - the label
// The last field of a block is always the Checksum
if (strcmp(strtokIndx, "Checksum") == 0) {
blockend = true;
}
strcpy(recv_label[blockindex], strtokIndx); // copy it to label
// Now get the value
strtokIndx = strtok(NULL, "\r"); // This continues where the previous call left off until '/r'.
if (strtokIndx != NULL) { // We need to check here if we don't receive NULL.
strcpy(recv_value[blockindex], strtokIndx);
}
blockindex++;
if (blockend) {
// We got a whole block into the received data.
// Check if the data received is not corrupted.
// Sum off all received bytes should be 0;
byte checksum = 0;
for (int x = 0; x < blockindex; x++) {
// Loop over the labels and value gotten and add them.
// Using a byte so the the % 256 is integrated.
char *v = recv_value[x];
char *l = recv_label[x];
while (*v) {
checksum += *v;
v++;
}
while (*l) {
checksum+= *l;
l++;
}
// Because we strip the new line(10), the carriage return(13) and
// the horizontal tab(9) we add them here again.
checksum += 32;
}
// Checksum should be 0, so if !0 we have correct data.
if (!checksum) {
// Since we are getting blocks that are part of a
// keyword chain, but are not certain where it starts
// we look for the corresponding label. This loop has a trick
// that will start searching for the next label at the start of the last
// hit, which should optimize it.
int start = 0;
for (int i = 0; i < blockindex; i++) {
for (int j = start; (j - start) < num_keywords; j++) {
if (strcmp(recv_label[i], keywords[j % num_keywords]) == 0) {
// found the label, copy it to the value array
strcpy(value[j], recv_value[i]);
start = (j + 1) % num_keywords; // start searching the next one at this hit +1
break;
}
}
}
}
// Reset the block index, and make sure we clear blockend.
blockindex = 0;
blockend = false;
}
}
void printValues() {
for (int i = 0; i < num_keywords; i++){
Serial.print(keywords[i]);
Serial.print(",");
Serial.println(value[i]);
}
}
void printData() {
static unsigned long prev_millis;
if (millis() - prev_millis > PRINT_EVERY_SECONDS * 1000) {
printValues();
prev_millis = millis();
}
}
void handleNewData() {
// We have gotten a field of data
if (new_data == true) {
//Copy it to the temp array because parseData will alter it.
strcpy(tempChars, receivedChars);
parseData();
new_data = false;
}
}
void loop()
{
server.handleClient();
ArduinoOTA.handle();
// Receive information on Serial from MPPT
recvWithEndMarker();
handleNewData();
// Just print the values every second,
// Add your own code here to use the data.
// Make sure to not used delay(X)s of bigger than 50ms,
// so make use of the same principle used in printData()
// or use some sort of Alarm/Timer Library
printData();
}