Skip to content

Instantly share code, notes, and snippets.

@e1ectr0cut1e
Created February 21, 2026 18:45
Show Gist options
  • Select an option

  • Save e1ectr0cut1e/2b809fe3d3fcf786d4061218032797cf to your computer and use it in GitHub Desktop.

Select an option

Save e1ectr0cut1e/2b809fe3d3fcf786d4061218032797cf to your computer and use it in GitHub Desktop.
#include <Arduino.h>
#include <ESP8266WiFi.h>
const char* ssid = "SSID";
const char* password = "12345678";
const char* loggerIP = "192.0.0.2";
const uint32_t loggerSerial = 3110000000;
const uint16_t loggerPort = 8899;
WiFiClient client;
uint8_t initSerial = 0x37;
static uint16_t crc16(const uint8_t *buf, uint16_t len) {
uint16_t crc = 0xFFFF;
while (len--) {
crc ^= *buf++;
for (uint8_t i = 0; i < 8; ++i) {
if (crc & 0x0001) { crc >>= 1; crc ^= 0xA001; }
else { crc >>= 1; }
}
}
return crc;
}
void hexDump(const uint8_t *buf, size_t len) {
for (size_t i = 0; i < len; ++i) {
if (i) Serial.print(' ');
if (buf[i] < 0x10) Serial.print('0');
Serial.print(buf[i], HEX);
}
Serial.println();
}
void sendReadRequest(uint16_t startReg, uint16_t regCount) {
uint8_t frame[80];
uint16_t idx = 0;
initSerial++;
// ----- Fixed header -------------------------------------------------
frame[idx++] = 0xA5; // start byte
frame[idx++] = 0x00; // length LSB (placeholder)
frame[idx++] = 0x00; // length MSB (placeholder)
frame[idx++] = 0x10; // control code
frame[idx++] = 0x45; // control code
frame[idx++] = initSerial; // our request command serial
frame[idx++] = 0x00; // logger response serial
// Logger serial
frame[idx++] = loggerSerial & 0xFF;
frame[idx++] = (loggerSerial >> 8) & 0xFF;
frame[idx++] = (loggerSerial >> 16) & 0xFF;
frame[idx++] = (loggerSerial >> 24) & 0xFF;
frame[idx++] = 0x02; // frame type (request)
frame[idx++] = 0x00; // sensor type
frame[idx++] = 0x00; // sensor type
frame[idx++] = 0x00; // total working time
frame[idx++] = 0x00; // total working time
frame[idx++] = 0x00; // total working time
frame[idx++] = 0x00; // total working time
frame[idx++] = 0x00; // power on time
frame[idx++] = 0x00; // power on time
frame[idx++] = 0x00; // power on time
frame[idx++] = 0x00; // power on time
frame[idx++] = 0x00; // offset time
frame[idx++] = 0x00; // offset time
frame[idx++] = 0x00; // offset time
frame[idx++] = 0x00; // offset time
const uint8_t modbus_idx = idx;
const uint8_t slaveId = 0x01;
const uint8_t function = 0x03; // read holding registers
frame[idx++] = slaveId;
frame[idx++] = function;
frame[idx++] = (startReg >> 8) & 0xFF; // high byte of start register
frame[idx++] = startReg & 0xFF; // low byte
frame[idx++] = (regCount >> 8) & 0xFF; // high byte of quantity
frame[idx++] = regCount & 0xFF; // low byte
uint16_t crc = crc16(&frame[modbus_idx], 6);
frame[idx++] = crc & 0xFF; // CRC LSB
frame[idx++] = crc >> 8; // CRC MSB
frame[idx++] = 0x00; // checksum
frame[idx++] = 0x15;
uint16_t payloadLen = idx - 13;
frame[1] = payloadLen & 0xFF;
frame[2] = payloadLen >> 8;
uint8_t checksum = 0;
for (uint16_t i = 1; i < idx - 2; ++i) checksum += frame[i] & 0xFF;
frame[idx - 2] = checksum & 0xFF;
Serial.print(F("\n>>> Sending frame ("));
Serial.print(idx);
Serial.println(F(" bytes):"));
hexDump(frame, idx);
client.write(frame, idx);
}
bool readSoc(uint16_t &soc) {
const uint16_t minSize = 19;
unsigned long start = millis();
while (client.available() < minSize && millis() - start < 2000) {
delay(5);
}
if (client.available() < minSize) {
Serial.println(F("<<< No reply (timeout)"));
return false;
}
uint8_t buf[128];
int len = client.read(buf, sizeof(buf));
if (len <= 0) {
Serial.println(F("<<< Read error"));
return false;
}
Serial.print(F("\n<<< Received "));
Serial.print(len);
Serial.println(F(" bytes:"));
hexDump(buf, len);
if (buf[0] != 0xA5 || buf[len - 1] != 0x15) {
Serial.println(F("<<< Bad start/end bytes"));
return false;
}
const uint16_t modbusStart = 25;
const uint8_t slaveId = buf[modbusStart];
const uint8_t function = buf[modbusStart + 1];
const uint8_t byteCount = buf[modbusStart + 2];
if (slaveId != 0x01 || function != 0x03 || byteCount != 2) {
Serial.println(F("<<< Unexpected Modbus header"));
return false;
}
uint16_t raw = (buf[modbusStart + 3] << 8) | buf[modbusStart + 4];
soc = raw; // register 588 already holds % (0‑100)
return true;
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.print(F("\nConnecting to WiFi"));
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print('.');
}
Serial.println(F("\nWiFi connected"));
if (!client.connect(loggerIP, loggerPort)) {
Serial.println(F("Cannot connect to logger"));
while (true) delay(1000);
}
Serial.println(F("Connected to Solarman logger"));
}
void loop() {
if (!client.connected()) {
Serial.println(F("Reconnecting…"));
client.stop();
client.connect(loggerIP, loggerPort);
delay(500);
}
while (client.available()) client.read();
// Register 588 = SoC
sendReadRequest(603, 1);
delay(300);
uint16_t soc = 0;
if (readSoc(soc)) {
Serial.printf("Battery SoC: %u %%\n", soc);
} else {
Serial.println(F("No valid response"));
}
delay(10000);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment