Why are RS485 frames corrupted between ESP32 and ESP8266?

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: