Code ESP32:
#include <WiFi.h>
#include <HTTPClient.h>
#include <HardwareSerial.h>
#include "time.h"
#include <Preferences.h>
// ---------------- RS485 ----------------
HardwareSerial rs485(1); // UART1
#define RX_PIN 16
#define TX_PIN 17
uint8_t masterID = 0x3F;
uint8_t slaveID = 0x00;
// WiFi dane
const char* ssid = "";
const char* password = "";
// Serwer API
const char* serverURL = "";
// ---------------- Rekordy ----------------
Preferences prefs;
unsigned long recordNumber = 0;
// ---------------- NTP ----------------
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 2 * 3600; // UTC+2
const int daylightOffset_sec = 0;
// ---------- Komendy ----------
struct Command {
uint8_t cmd1;
uint8_t cmd2;
uint8_t length;
String description;
String lastValue;
};
Command commands[] = {
{0x01, 0x00, 4, "Status procesu X", ""},
{0x02, 0x00, 4, "Pozostały czas (s)", ""},
};
#define IN_COUNT 9
Command inCommands[IN_COUNT];
#define INFO_COUNT 8
Command infoCommands[INFO_COUNT];
// ---------------- Zmienne globalne ----------------
int currentCommand = 0;
bool roundInProgress = false;
unsigned long lastPacketTime = 0;
unsigned long lastFullRoundTime = 0;
const unsigned long packetInterval = 1500;
const unsigned long fullRoundInterval = 56000;
const unsigned long responseTimeout = 5000; // 5 sek. na odpowiedź
const int maxRetries = 3; // max ilość ponowień całej rundy
int retryCount = 0;
unsigned long roundStartTime = 0;
uint8_t Prog_No = 0;
uint8_t Segment_No = 0;
uint32_t remainingTimeStr = 0;
int sentCommands = 0; // liczba wysłanych komend
int receivedResponses = 0; // liczba odebranych odpowiedzi
// ---------------- Funkcje pomocnicze ----------------
void setupCommands() {
for (int i = 0; i < IN_COUNT; i++) {
inCommands[i] = {0x05, uint8_t(i), 7, "IN" + String(i), ""};
}
for (int i = 0; i < INFO_COUNT; i++) {
infoCommands[i] = {0x0C, uint8_t(i), 7, "Infobox" + String(i), ""};
}
}
float decodeFloatBE(uint8_t *buf) {
uint8_t temp[4];
temp[0] = buf[3];
temp[1] = buf[2];
temp[2] = buf[1];
temp[3] = buf[0];
float f;
memcpy(&f, temp, 4);
return f;
}
// HEX debug
void printHex(const char* prefix, uint8_t* data, size_t len) {
Serial.print(prefix);
for (size_t i = 0; i < len; i++) {
Serial.printf("%02X ", data[i]);
}
Serial.println();
}
// Funkcja wysyłania ramki (bez DE/RE)
void sendFrame(uint8_t* frame, size_t len) {
rs485.write(frame, len);
rs485.flush();
// DEBUG HEX
printHex("MASTER -> ", frame, len);
}
// ---------------- Setup ----------------
void setup() {
Serial.begin(115200);
// wg dokumentacji -> 8E1
rs485.begin(115200, SERIAL_8N1, RX_PIN, TX_PIN);
setupCommands();
// Odczyt rekordu
prefs.begin("recstore", false);
recordNumber = prefs.getULong("record", 514081);
Serial.println("Odczytany rekord startowy: " + String(recordNumber));
// WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected!");
// NTP
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
// ---------------- Loop ----------------
void loop() {
unsigned long now = millis();
// Odczyt aktualnego czasu
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
// start nowej rundy na początku każdej minuty
if (timeinfo.tm_sec == 56 && !roundInProgress) {
roundInProgress = true;
currentCommand = 0;
lastPacketTime = 0;
sentCommands = 0;
receivedResponses = 0;
retryCount = 0;
roundStartTime = now;
Serial.printf("Start rundy o %02d:%02d\n", timeinfo.tm_hour, timeinfo.tm_min);
}
}
// Wysyłanie kolejnych pakietów
if (roundInProgress && now - lastPacketTime >= packetInterval) {
sendNextPacket();
lastPacketTime = now;
}
// Timeout i retry całej rundy
if (roundInProgress && (now - roundStartTime > responseTimeout)) {
if (receivedResponses < sentCommands) {
if (retryCount < maxRetries) {
Serial.println("Timeout! Ponawiam rundę...");
currentCommand = 0;
lastPacketTime = 0;
sentCommands = 0;
receivedResponses = 0;
roundStartTime = now;
retryCount++;
} else {
Serial.println("Przekroczono maxRetries, kończę rundę.");
roundInProgress = false;
}
}
}
// Sprawdzenie czy komplet danych
int totalCommands = sizeof(commands) / sizeof(commands[0]) + IN_COUNT + INFO_COUNT;
if (roundInProgress && currentCommand >= totalCommands && receivedResponses >= sentCommands) {
roundInProgress = false;
sendJSON(); // pełna runda OK
}
// Odbiór danych RS485
if (rs485.available()) {
uint8_t buffer[256];
size_t len = rs485.readBytes(buffer, sizeof(buffer));
printHex("SLAVE -> ", buffer, len);
parseResponse(buffer, len);
}
}
// ---------------- Wysyłanie pakietu ----------------
void sendNextPacket() {
int totalCommands = sizeof(commands) / sizeof(commands[0]) + IN_COUNT + INFO_COUNT;
if (currentCommand >= totalCommands) return;
int PACKET_SIZE = 10;
int remaining = totalCommands - currentCommand;
int count = min(remaining, PACKET_SIZE);
uint8_t frame[2 + count * 2 + 1];
int idx = 0;
frame[idx++] = slaveID;
frame[idx++] = masterID;
frame[idx++] = count * 2;
for (int i = 0; i < count; i++) {
Command cmd;
if (currentCommand < sizeof(commands) / sizeof(commands[0]))
cmd = commands[currentCommand];
else if (currentCommand < sizeof(commands) / sizeof(commands[0]) + IN_COUNT)
cmd = inCommands[currentCommand - sizeof(commands) / sizeof(commands[0])];
else
cmd = infoCommands[currentCommand - sizeof(commands) / sizeof(commands[0]) - IN_COUNT];
frame[idx++] = cmd.cmd1;
frame[idx++] = cmd.cmd2;
currentCommand++;
sentCommands++; // liczymy wysłane
}
uint8_t sum = 0;
for (int i = 0; i < idx; i++) sum += frame[i];
frame[idx++] = sum;
sendFrame(frame, idx);
}
// ---------------- Parsowanie odpowiedzi ----------------
void parseResponse(uint8_t* data, size_t len) {
if (len < 4) return;
uint8_t recvMaster = data[0];
uint8_t recvSlave = data[1];
uint8_t dataLen = data[2];
if (recvMaster != masterID || recvSlave != slaveID) return;
if (len < 3 + dataLen + 1) return;
int pos = 3;
while (pos < 3 + dataLen) {
uint8_t cmd1 = data[pos++];
uint8_t cmd2 = data[pos++];
if (cmd1 == 0x00) { // ASCII
String strVal = "";
for (int i = 0; i < 8; i++) strVal += char(data[pos++]);
storeValue(cmd1, cmd2, strVal);
receivedResponses++;
} else if (cmd1 == 0x01) { // Status procesu X
uint8_t byte0 = data[pos++];
uint8_t byte1 = data[pos++];
Prog_No = data[pos++];
Segment_No = data[pos++];
String val = "Prog:" + String(Prog_No) + " Seg:" + String(Segment_No);
storeValue(cmd1, cmd2, val);
receivedResponses++;
} else if (cmd1 == 0x02) { // Pozostały czas
uint32_t t = ((uint32_t)data[pos] << 24) |
((uint32_t)data[pos+1] << 16) |
((uint32_t)data[pos+2] << 8) |
(uint32_t)data[pos+3];
pos+=4;
remainingTimeStr = t;
storeValue(cmd1, cmd2, String(t));
receivedResponses++;
} else if (cmd1 == 0x05 || cmd1 == 0x0C) { // IN / Infobox
float val = decodeFloatBE(&data[pos]);
pos += 4;
uint8_t unit = data[pos++];
uint8_t status = data[pos++];
storeValue(cmd1, cmd2, String(val));
receivedResponses++;
} else if (cmd1 == 0x09) {
storeValue(cmd1, cmd2, String(data[pos++]));
receivedResponses++;
} else {
while (pos < 3 + dataLen) pos++;
receivedResponses++;
}
}
}
// ---------------- Zapis wartości ----------------
void storeValue(uint8_t cmd1, uint8_t cmd2, String val) {
for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++)
if (commands[i].cmd1 == cmd1 && commands[i].cmd2 == cmd2) commands[i].lastValue = val;
for (int i = 0; i < IN_COUNT; i++)
if (inCommands[i].cmd1 == cmd1 && inCommands[i].cmd2 == cmd2) inCommands[i].lastValue = val;
for (int i = 0; i < INFO_COUNT; i++)
if (infoCommands[i].cmd1 == cmd1 && infoCommands[i].cmd2 == cmd2) infoCommands[i].lastValue = val;
}
// ---------------- Czas lokalny ----------------
void getLocalDateTime(String &dateStr, String &timeStr) {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
dateStr = "unknown";
timeStr = "unknown";
return;
}
char dateBuff[20];
char timeBuff[20];
strftime(dateBuff, sizeof(dateBuff), "%d/%m/%Y", &timeinfo);
strftime(timeBuff, sizeof(timeBuff), "%H:%M:%S", &timeinfo);
dateStr = String(dateBuff);
timeStr = String(timeBuff);
}
// ---------------- Wysyłanie JSON ----------------
void sendJSON() {
if (WiFi.status() != WL_CONNECTED) return;
String dateStr, timeStr;
getLocalDateTime(dateStr, timeStr);
String json = "{";
json += "\"record\":\"" + String(recordNumber) + "\",";
json += "\"date\":\"" + dateStr + "\",";
json += "\"loc_time\":\"" + timeStr + "\",";
json += "\"vt00\":\"" + infoCommands[0].lastValue + "\",";
json += "\"sp00\":\"" + infoCommands[1].lastValue + "\",";
json += "\"vt01\":\"" + infoCommands[3].lastValue + "\",";
json += "\"vaccum1\":\"" + infoCommands[7].lastValue + "\",";
json += "\"program\":\"" + String(Prog_No) + "\",";
json += "\"segment\":\"" + String(Segment_No) + "\",";
json += "\"rem_time\":\"" + String(remainingTimeStr) + "\",";
json += "\"bottom_l\":\"" + inCommands[0].lastValue + "\",";
json += "\"bottom_r\":\"" + inCommands[1].lastValue + "\",";
json += "\"top_l\":\"" + inCommands[2].lastValue + "\",";
json += "\"top_r\":\"" + inCommands[3].lastValue + "\",";
json += "\"ai2_1\":\"" + inCommands[4].lastValue + "\",";
json += "\"ai2_2\":\"" + inCommands[5].lastValue + "\",";
json += "\"ai2_3\":\"" + inCommands[6].lastValue + "\",";
json += "\"vaccum2\":\"" + inCommands[7].lastValue + "\",";
json += "\"gas_pres\":\"" + inCommands[8].lastValue + "\"";
json += "}";
HTTPClient http;
http.begin(serverURL);
http.addHeader("Content-Type", "application/json");
int httpResponseCode = http.POST(json);
if (httpResponseCode > 0) {
Serial.println("POST success: " + String(httpResponseCode));
recordNumber++;
prefs.putULong("record", recordNumber);
} else {
Serial.println("POST failed: " + String(httpResponseCode));
}
http.end();
}
Code Esp8266:
#include <SoftwareSerial.h>
SoftwareSerial rs485(D1, D2); // RX, TX
uint8_t slaveID = 0x00;
uint8_t masterID = 0x3F;
const char model[] = "ESP8266";
const char version[] = "v1.0.0";
const char serialNumber[] = "SN123456";
uint8_t processStatus[4] = {0b01010010, 0x01, 0x02, 0x03};
float inValues[16];
uint8_t inUnits[16];
uint8_t inStatus[16];
void setup() {
Serial.begin(115200);
rs485.begin(115200);
Serial.println("SLAVE: Start");
randomSeed(analogRead(A0)); // do losowania wartości
}
void loop() {
if (rs485.available()) {
uint8_t buffer[128];
size_t len = rs485.readBytes(buffer, sizeof(buffer));
Serial.print("SLAVE <- Otrzymano: ");
printHex(buffer, len);
if (len < 4) return;
uint8_t recvSlaveID = buffer[0];
uint8_t recvMasterID = buffer[1];
uint8_t dataLen = buffer[2];
// suma kontrolna
uint8_t sum = 0;
for (int i = 0; i < 3 + dataLen; i++) sum += buffer[i];
if (sum != buffer[3 + dataLen]) {
Serial.println("SLAVE: Błąd sumy kontrolnej!");
return;
}
if (recvSlaveID != slaveID) return;
// losowanie nowych danych
for (int i = 0; i < 16; i++) {
inValues[i] = random(200, 400) / 10.0; // np. 20.0–40.0 °C
inUnits[i] = 0; // 0 = °C
inStatus[i] = 0b01000000; // przykładowy status OK
}
// odpowiedź
uint8_t response[128];
int idx = 0;
response[idx++] = masterID;
response[idx++] = slaveID;
int lenPos = idx++;
int dataStart = idx;
// obsługa komend
for (int i = 3; i < 3 + dataLen; i += 2) {
uint8_t cmd1 = buffer[i];
uint8_t cmd2 = buffer[i + 1];
response[idx++] = cmd1;
response[idx++] = cmd2;
if (cmd1 == 0x00) { // tekstowe dane
if (cmd2 == 0x01) memcpy(&response[idx], model, 8);
else if (cmd2 == 0x02) memcpy(&response[idx], version, 8);
else if (cmd2 == 0x03) memcpy(&response[idx], serialNumber, 8);
else memset(&response[idx], 0, 8);
idx += 8;
}
else if (cmd1 == 0x01 && cmd2 == 0x00) { // status procesu
memcpy(&response[idx], processStatus, 4);
idx += 4;
}
else if (cmd1 == 0x02 && cmd2 == 0x00) { // czas pozostały
uint32_t timeLeft = 3600;
writeUint32BE(&response[idx], timeLeft);
idx += 4;
}
else if ((cmd1 == 0x05 || cmd1 == 0x0C) && cmd2 <= 0x0F) { // INxx / Infobox
int inIdx = cmd2;
writeFloatBE(&response[idx], inValues[inIdx]); // float big endian
idx += 4;
response[idx++] = inUnits[inIdx];
response[idx++] = inStatus[inIdx];
}
else if (cmd1 == 0x09 && cmd2 == 0x00) { // wyjście cyfrowe
response[idx++] = 0x01;
}
else {
response[idx++] = 0x00;
}
}
response[lenPos] = idx - dataStart;
uint8_t sumResp = 0;
for (int i = 0; i < idx; i++) sumResp += response[i];
response[idx++] = sumResp;
rs485.write(response, idx);
rs485.flush();
Serial.print("SLAVE -> Odpowiedź: ");
printHex(response, idx);
}
}
// ---------- Funkcje pomocnicze ----------
void writeFloatBE(uint8_t* buf, float val) {
uint32_t tmp;
memcpy(&tmp, &val, sizeof(float));
buf[0] = (tmp >> 24) & 0xFF;
buf[1] = (tmp >> 16) & 0xFF;
buf[2] = (tmp >> 8) & 0xFF;
buf[3] = tmp & 0xFF;
}
void writeUint32BE(uint8_t* buf, uint32_t val) {
buf[0] = (val >> 24) & 0xFF;
buf[1] = (val >> 16) & 0xFF;
buf[2] = (val >> 8) & 0xFF;
buf[3] = val & 0xFF;
}
void printHex(uint8_t* data, size_t len) {
for (size_t i = 0; i < len; i++) {
Serial.printf("%02X ", data[i]);
}
Serial.println();
}
Drawing:
