Skip to content
View in the app

A better way to browse. Learn more.

Power Forum - Renewable Energy Discussion

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

SunSynk WiFi Dongle Hacking.

Featured Replies

@frankie, would it be a big ask to explain to a non-IT person how to use the Sunsynk API to extract the current battery charge via POST? I can't figure out how to get the API token. (I want to see if I can use the battery SOC in a Hubotat-dashboard.) Thank you.

  • Replies 152
  • Views 48.9k
  • Created
  • Last Reply

Top Posters In This Topic

Most Popular Posts

  • Have a look at the card I created for HA. I'm sure you can pull all the necessary sensor data through the API.   This is what my dashboard currently looks like

  • Is an ESP32 MCU, would be cool to reflash with ESPhome and pull all the data from the inverter that way.   

  • To confirm do you want to pull the firmware from the nodemcu, or just utilise their API?   It might be easier to just write some HA integration which directly talks to the SunSynk cloud APIs

Posted Images

  • 2 weeks later...

@you1@RoganDawesdo your captures have a TCP packet every 5 minutes whose payload is 292 bytes and starts with a5 06? (total packet length is 346 bytes). If so then we probably have compatible dongles. I'm making a fair amount of progress in decoding it, and running a process on my router to extract the samples and put them into Influxdb. I'll share a repo once it's in a state where someone other than me has a hope of making it work.

On 2022/10/04 at 3:40 AM, Madek said:

Got a surprise the other day. Previously sunsylk issued a magneto data logger with their inverters (looks like it) . We could not understand why we can not find the client on the sunsylk app. Then saw they gave us a magneto data logger that can only work on solarman. I also ordered an add data logger 6mnths ago. When we wanted to replace the magneto data logger that was issued 2yrs ago with the installation we found out they gave us again a magneto data logger. Still gotto contact the supplier. I want to know how could they sell a magneto data logger to us if the order stupilated  a sunsylk dongle. Anyone else with the same problem? 

Yep, I ordered a 5K Sunsynk earlier this year and specifically requested the Sunsynk dongle, but it still came with Solarman dongle. The wholesaler refused to swop it out. I have managed to get another Sunsynk logger in the mean time, but I am still stuck with an unwanted Solarman dongle, which I advertised in the classifieds. It still leaves a bad taste in my mouth.  

16 hours ago, bmerry said:

@you1@RoganDawesdo your captures have a TCP packet every 5 minutes whose payload is 292 bytes and starts with a5 06? (total packet length is 346 bytes). If so then we probably have compatible dongles. I'm making a fair amount of progress in decoding it, and running a process on my router to extract the samples and put them into Influxdb. I'll share a repo once it's in a state where someone other than me has a hope of making it work.

Yeah, my packets also start like that, but the length is variable. First packet is 0x2B long, response is 0x13 long, then 0x0B, response 0x0B, 0xA4, 0x17, etc.

9 minutes ago, RoganDawes said:

Yeah, my packets also start like that, but the length is variable. First packet is 0x2B long, response is 0x13 long, then 0x0B, response 0x0B, 0xA4, 0x17, etc.

I do also have short packets, although not such a variety of lengths. I don't know what they are, but the packets with the sensor data are much longer (as I say, in my case 292 bytes of payload), and have the inverter serial number (in ASCII) pretty close to the start.

  • 2 weeks later...

Hi all, don't know if this thread is still alive but here is what I have deciphered so far, in the 292 bytes payload of the dongle. If anyone else can maybe confirm that this corresponds to their inverter data as well, I have the SUNSYNK-5K-SG01LP1

 

 

payload.png

@PLCguyit's a bit tricky to read the offsets from that view since they don't start at zero, so at a glance I'm not able to say if it all lines up, but it seems promising! I've pushed my work-in-progress to Github, and you can see the fields I've decoded here. I'll take a look this weekend to see if they match yours and add any you have that I'm missing (I don't think I have any of the static fields like discharge limit). Note that some fields seem to be duplicated so for example I've noted battery temperature in a different place to you.

Have you spotted anything that might be a time zone? The embedded timestamp is annoyingly in local time, which is not ideal for a time series database. I could just subtract a known time zone offset, which would be fine for South Africa, but in areas with daylight savings it gets more complicated and ambiguous. For now I'm just using the packet timestamps, but the dongle buffers data if the connection drops so the packet timestamps can be significantly later than the data they contain.

2 hours ago, PLCguy said:

Hi all, don't know if this thread is still alive but here is what I have deciphered so far, in the 292 bytes payload of the dongle. If anyone else can maybe confirm that this corresponds to their inverter data as well, I have the SUNSYNK-5K-SG01LP1

 

 

payload.png

Neat! Nice work!

3 hours ago, PLCguy said:

I have the SUNSYNK-5K-SG01LP1

That's the same model I have. Is your dongle also the unbranded one with red and green LEDs in it, or the Sunsynk-branded one?

32 minutes ago, bmerry said:

That's the same model I have. Is your dongle also the unbranded one with red and green LEDs in it, or the Sunsynk-branded one?

I guess it's the branded one as it has "Sunsynk" printed on it, but it also does have the red and green LED's. The same one posted earlier by @valienté with the picture. Below is the UI served from it.

image.thumb.png.f775e24f6559ed1e9aed11a6a617a844.png

 

I have also checked and it's only port 80 open on the unit, the web server is mongoose6.7 but don't know if there are any exploits to try and get root on the unit. cgi-scripts that can be executed on the unit are, and there are others:

http://<device-ip>/config?command=status

http://<device-ip>/config?command=devinfo

but the interesting one is this: 

image.png.f26218c43e22c6ecc1e649a5a7db7b0d.png

 

just going to keep digging...

 

Edited by PLCguy

12 minutes ago, PLCguy said:

I guess it's the branded one as it has "Sunsynk" printed on it, but it also does have the red and green LED's.

 

Yeah, seems to be the same as mine (identical hardware revision); I guess they just decided to slap their logo on it.

For what you identified as battery capacity: is that in Ah? That does seem to fit with my battery.

44 minutes ago, PLCguy said:

I have also checked and it's only port 80 open on the unit, the web server is mongoose6.7 but don't know if there are any exploits to try and get root on the unit. cgi-scripts that can be executed on the unit are, and there are others:

The whole setup seems to be a typical IoT security disaster. I haven't yet seen a way to change the Wifi password used to access that interface, which means anyone in my area could connect and start uploading firmware (maybe it has to be signed but given the rest of the security I wouldn't count on it) or routing the telemetry through their connection.

Some of my fields agree with yours, but there do seem to be differences. For example, where you've indicated INV AC output load, it looks like I have battery voltage (V-Bat in the data logger app). Your battery voltage is my V-BMS (similar to but not quite the same as V-Bat). Your INV power I can't seem to match up to any of the custom graphs shown by the data logger.

I'm wondering if the logger sends any metadata when it first connects to indicate which field is which. At some point I want to try deliberately break its TCP connection while still running tcpdump on the router so that I can see what the initial exchange looks like.

 

1 hour ago, PLCguy said:

I have also checked and it's only port 80 open on the unit, the web server is mongoose6.7 but don't know if there are any exploits to try and get root on the unit.

It’s an ESP32 running an RTOS, not Linux, so there’s no question of rooting it. I have pulled the firmware off mine using esptool, and there are a bunch of undocumented endpoints that may be worth poking at. Some are more complicated than a simple GET, though, and probably require specific parameters to be POSTed to them. One day I’ll get Ghidra going and take a look.

2 minutes ago, bmerry said:

I'm wondering if the logger sends any metadata when it first connects to indicate which field is which. At some point I want to try deliberately break its TCP connection while still running tcpdump on the router so that I can see what the initial exchange looks like.

🙂 I think I DOS'ed mine this morning when I ran gobuster against it, so I added delays of .2s between single queries, unfortunately no fruitful results. I'm still capturing packets atm and dont' want to stop the capture till at least after 00:00, I suspect some bytes will reset when it is the next day but want to confirm this with the current capture that has been running since this morning. With those "magic" bytes that start in the payload byte 7/8 (starting from 0) is an incremental counter that gets incremented by both the dongle and the server (hosted in China, and owned by Alibaba data center) each time one of them transmit a packet in the stream, since the capture this morning this counter is still going... the dongle represent the even values and responses from the server are odd values, but they are sequential.

bytes 42/42 (from 0) is also some form of a counter that increments every 4 or 5 transmits, this is not consistent, but it keeps increasing (this may be a totalizing value), will confirm.

2 minutes ago, RoganDawes said:

It’s an ESP32 running an RTOS, not Linux, so there’s no question of rooting it. I have pulled the firmware off mine using esptool, and there are a bunch of undocumented endpoints that may be worth poking at. Some are more complicated than a simple GET, though, and probably require specific parameters to be POSTed to them. One day I’ll get Ghidra going and take a look.

@RoganDawes ahh makes sense 🙈 saw the image earlier in the thread but didn't register 😄

On 2022/11/04 at 10:00 PM, PLCguy said:

bytes 42/42 (from 0) is also some form of a counter that increments every 4 or 5 transmits, this is not consistent, but it keeps increasing (this may be a totalizing value), will confirm.

Bytes 41 and 42 are always in the range 0-59 so I'm fairly sure they're the minute and second of the timestamp.

3 hours ago, bmerry said:

Bytes 41 and 42 are always in the range 0-59 so I'm fairly sure they're the minute and second of the timestamp.

Bytes 0 - 10 -> payload header

Byte 2 = A1 when packet from server, = 01 when packet from wifi dongle

Bytes 9-10 -> Actual data byte count from byte 11

Byte 11-20 -> Inverter ID

Byte 37 -> Year

Byte 38 -> Month

Byte 39 -> Day

Byte 40 -> Hour

Byte 41 -> Minute

Byte 66-67 -> Daily battery charge totalizer (resets to zero @ 00:00) | kWh = raw value / 10

Byte 68-69 -> Daily battery discharge totalizer (resets to zero @ 00:00) | kWh = raw value / 10

Byte 74-75 -> Total battery discharge since beginning of time | kWh = raw value / 10

Byte 82-83 -> Total grid import since beginning of time | kWh = raw value / 10

Byte 84-85 -> Grid instantaneous frequency | Hz = raw value / 100

Byte 96-97 -> Load usage totalizer since beginning of time | kWh = raw value / 10

Byte 140-141 -> Battery capacity (Battery setup screen parameter) | Ah = raw value

Byte 182-183 -> V-grid-L1 | V = raw value / 10

Byte 186-187 -> Inverter AC output

Byte 190-191 -> Unidentified voltage

Byte 192-193 -> Unidentified voltage

Byte 212-213 -> Grid power (Instantaneous) | watt = raw value

Byte 216-217 -> Unidentified power

Byte 218-219 -> Unidentified power

Byte 220-221 -> Inverter power

Byte 228-229 -> Inverter load output | watt = raw value / 10

Byte 242-243 -> Battery watts (instantaneous)

Byte 244-245 -> Battery current (instantaneous)

Byte 246-247 -> Unidentified frequency

Byte 248-249 -> Unidentified frequency

Byte 280-281 -> Battery capacity (0 when grid present, capacity value when grid off)

Byte 282-283 -> Discharge limit

Byte 285 -> SOC (State of charge) | % = raw value

Byte 286-287 -> Battery voltage from BMS

Byte 288-289 -> Battery current from BMS

Byte 290-291 -> Real time battery temperature (range -99.9 degrees to +99.9 degrees scaled between 0x01 AND 0x7CF, where 0 degrees = 0x3E8

 

 

7 minutes ago, PLCguy said:

Bytes 0 - 10 -> payload header

Byte 2 = A1 when packet from server, = 01 when packet from wifi dongle

Bytes 9-10 -> Actual data byte count from byte 11

Byte 11-20 -> Inverter ID

Byte 37 -> Year

Byte 38 -> Month

Byte 39 -> Day

Byte 40 -> Hour

Byte 41 -> Minute

Byte 66-67 -> Daily battery charge totalizer (resets to zero @ 00:00) | kWh = raw value / 10

Byte 68-69 -> Daily battery discharge totalizer (resets to zero @ 00:00) | kWh = raw value / 10

Byte 74-75 -> Total battery discharge since beginning of time | kWh = raw value / 10

Byte 82-83 -> Total grid import since beginning of time | kWh = raw value / 10

Byte 84-85 -> Grid instantaneous frequency | Hz = raw value / 100

Byte 96-97 -> Load usage totalizer since beginning of time | kWh = raw value / 10

Byte 140-141 -> Battery capacity (Battery setup screen parameter) | Ah = raw value

Byte 182-183 -> V-grid-L1 | V = raw value / 10

Byte 186-187 -> Inverter AC output

Byte 190-191 -> Unidentified voltage

Byte 192-193 -> Unidentified voltage

Byte 212-213 -> Grid power (Instantaneous) | watt = raw value

Byte 216-217 -> Unidentified power

Byte 218-219 -> Unidentified power

Byte 220-221 -> Inverter power

Byte 228-229 -> Inverter load output | watt = raw value / 10

Byte 242-243 -> Battery watts (instantaneous)

Byte 244-245 -> Battery current (instantaneous)

Byte 246-247 -> Unidentified frequency

Byte 248-249 -> Unidentified frequency

Byte 280-281 -> Battery capacity (0 when grid present, capacity value when grid off)

Byte 282-283 -> Discharge limit

Byte 285 -> SOC (State of charge) | % = raw value

Byte 286-287 -> Battery voltage from BMS

Byte 288-289 -> Battery current from BMS

Byte 290-291 -> Real time battery temperature (range -99.9 degrees to +99.9 degrees scaled between 0x01 AND 0x7CF, where 0 degrees = 0x3E8

 

 

That mostly agrees with what I've found, at least on the fields where we both have something (see here), but for me 248-249 is PV power, not a frequency, and bytes 212-213 are always zero. So perhaps they change the fields around depending on the firmware version?

I did dump out the initial communication. I'd hoped to see something like a JSON document mapping offsets to identifiers, but all I could clearly identify were a bunch of version information, serial numbers, the connection key (in plaintext, yay) etc.

Maybe I should see what I can get out of the cloud API. If I can download a full day of data it'll be easier to match to what I capture with tcpdump (currently I'm doing the matching by eyeball comparison to the phone app).

By the way, has anyone had their inverter long enough to charge/discharge/generate more than 6553.6 kWh? I assume the totals must actually be 32 bit, but until it ticks past that point it's going to be hard to say where the high bits are stored (assuming that they are stored, rather than reconstructed on the server side from history).

10 minutes ago, bmerry said:

(currently I'm doing the matching by eyeball comparison to the phone app).

Yeah also doing eyeball matching but with the browser link

I've starting hacking together a Python script to scrape the API for data and get it into a Pandas dataframe. I'm planning to use it to automate matching fields to the raw TCP data, but it could probably also be used directly as a source of data e.g. to stash it in a local time-series database. See https://gist.github.com/bmerry/1f9317485830e736974c98cb1e5fd3b5. At the moment it just prints the dataframe, so to be useful you'll need to extend it to do something with the data.

Ok, I went a bit further with my script and found matches for all the graphs the web interface shows (in the Custom tab) that isn't just all zeros. In some cases there are multiple matches because several plots are identical. P-pv is a slightly odd case because it mostly matches to offset 248, but is sometimes off by 1.

The values in each entry are offset:scale:bias, and where there are multiple entries there are multiple matching offsets. What will still be interesting is to check which offsets are NOT matched (and not all zeros). @PLCguyalready spotted one bit in one field that seems to be a boolean for grid connectivity, but I don't think we know what the other bits are.

Grid P-grid ['214:1.0:0.0', '216:1.0:0.0', '220:1.0:0.0']
Grid F-grid ['84:0.01:0.0']
Grid I-grid-L1 ['196:0.01:0.0']
Grid V-grid-L1 ['176:0.1:0.0', '180:0.1:0.0']
Grid Import ['78:0.1:0.0']
Grid Total Import ['82:0.1:0.0']
Grid Total Export ['88:0.1:0.0', '112:0.1:0.0']
Grid P-L1 ['210:1.0:0.0']
Inv P-inv ['222:1.0:0.0', '226:1.0:0.0']
Inv P-pv []
Inv F-ac ['260:0.01:0.0', '262:0.01:0.0']
Inv V-ac-1 ['184:0.1:0.0', '188:0.1:0.0']
Inv I-ac-1 ['204:0.01:0.0']
Inv DC TEMP ['106:0.1:-100.0']
Inv AC TEMP ['108:0.1:-100.0']
String I-pv-1 ['146:0.1:0.0']
String I-pv-2 ['150:0.1:0.0']
String V-pv-1 ['144:0.1:0.0']
String V-pv-2 ['148:0.1:0.0']
String Daily Production ['142:0.1:0.0']
String Total Production ['118:0.1:0.0']
Battery SOC ['244:1.0:0.0']
Battery T-bat ['240:0.1:-100.0']
Battery V-bat ['242:0.01:0.0']
Battery I-bat ['258:0.01:0.0']
Battery P-bat ['256:1.0:0.0']
Battery Capacity ['140:1.0:0.0']
Battery Today Charging ['66:0.1:0.0']
Battery Today Discharging ['68:0.1:0.0']
Battery Total Discharging ['74:0.1:0.0']
Battery Total Charging ['70:0.1:0.0']
Battery T-BMS ['290:0.1:-100.0']
Battery V-BMS ['286:0.01:0.0']
Battery V-Charge-BMS ['276:0.01:0.0']
Battery I-BMS ['288:1.0:0.0']
Battery I-Charge-Limit-BMS ['280:1.0:0.0']
Battery I-Discharge-Limit-BMS ['282:1.0:0.0']
Load P-load ['228:1.0:0.0', '232:1.0:0.0']
Load P-Load-L1 ['228:1.0:0.0', '232:1.0:0.0']
Load Daily Consumption ['94:0.1:0.0']
Load Cumulative Consumption ['96:0.1:0.0']
Meter P-External-CT-L1 ['214:1.0:0.0', '216:1.0:0.0', '220:1.0:0.0']
Meter P-External-CT-Total ['214:1.0:0.0', '216:1.0:0.0', '220:1.0:0.0']

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.