Created
February 21, 2026 18:45
-
-
Save e1ectr0cut1e/2b809fe3d3fcf786d4061218032797cf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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