Open stuartornum opened 3 years ago
Hi @stuartornum , I'm using a really cheap one like the one below: (search terms: USB RS485).
The dip switches must all be down (the first dip switch sets the speed: 115200 vs 9600 Bd).
Cheers and don't hesitate if you need more info !
PS: I don't know the pinout on the one you showed, but I'm pretty sure that it doesn't match the Pylontech RJ45 pinout ! You'd need a custom RJ45 patch cable (easy to crimp).
It's easy to do with the one I showed above because you can just take a standard RJ45/ethernet cable, cut it in half and take the two strands you're interested in
Awesome, thanks @Frankkkkk - I've ordered exactly the same from Amazon. I also asked the question to the seller regarding the pinout for the converter I have... no response yet. I'll keep you posted... thanks again!
Hi @Frankkkkk ,
I managed to get my hands on the USB RS485 adapter you referenced above. Also, made a new cable from some CAT6 on to a RJ45 (568b). As per the Pylontech manual it says pin 7 and 8 are recommended for RS485, so converting that to 568b we get (Pin 7: brown/white, Pin 8: brown).
I get the following when trying to run the library: `
import pylontech p = pylontech.Pylontech() print(p.get_values()) Traceback (most recent call last): File "
", line 1, in File "/home/pi/python-pylontech/pylontech/pylontech.py", line 211, in get_values d = self.get_values_fmt.parse(f.info[1:]) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 288, in parse return self.parse_stream(io.BytesIO(data), **contextkw) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 300, in parse_stream return self._parsereport(stream, context, "(parsing)") File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 312, in _parsereport obj = self._parse(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 2120, in _parse subobj = sc._parsereport(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 312, in _parsereport obj = self._parse(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 2653, in _parse return self.subcon._parsereport(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 312, in _parsereport obj = self._parse(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 2413, in _parse e = self.subcon._parsereport(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 312, in _parsereport obj = self._parse(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 2120, in _parse subobj = sc._parsereport(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 312, in _parsereport obj = self._parse(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 2653, in _parse return self.subcon._parsereport(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 312, in _parsereport obj = self._parse(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 2413, in _parse e = self.subcon._parsereport(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 312, in _parsereport obj = self._parse(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 703, in _parse obj = self.subcon._parsereport(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 312, in _parsereport obj = self._parse(stream, context, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 1041, in _parse data = stream_read(stream, self.length, path) File "/home/pi/.local/lib/python3.7/site-packages/construct/core.py", line 91, in stream_read raise StreamError("stream read less than specified amount, expected %d, found %d" % (length, len(data)), path=path) construct.core.StreamError: Error in path (parsing) -> Module -> GroupedCellsTemperatures stream read less than specified amount, expected 2, found 1`
I also switched the brown/brown-white wires around on the RS485-to-USB adapter to see if I got a different output, I did...:
`
p = pylontech.Pylontech() print(p.get_values()) Traceback (most recent call last): File "
", line 1, in File "/home/pi/python-pylontech/pylontech/pylontech.py", line 208, in get_values f = self.read_frame() File "/home/pi/python-pylontech/pylontech/pylontech.py", line 167, in read_frame f = self._decode_hw_frame(raw_frame=raw_frame) File "/home/pi/python-pylontech/pylontech/pylontech.py", line 148, in _decode_hw_frame assert got_frame_checksum == int(frame_chksum, 16) ValueError: invalid literal for int() with base 16: b''`
Any thoughts?
Thanks again for your help! Really appreciate it.
(I'm using a Raspberry Pi 3b+)
Hi,
How are your dip switches set ? They should be all four off (down). Which pylontech modules have you got (model) ?
It would be great to show the received raw frame by either adding a print(raw_frame)
after here or by launching wireshark and capturing the serial port communications.
Cheers !
I suppose that your first wiring must be correct as the frame passed the checksum validation.
Maybe your setup is a bit different than mine and we must change the frame protocol. If you can manage to dump the raw frame I can try to check the differences and patch the lib ;-)
Hi @Frankkkkk , thanks for getting back to me.
Debug out from printing L169:
b'~2002460010F011020F0CCD0CCE0CCC0CCE0CCB0CCC0CCD0CCC0CCD0CCB0CCC0CCD0CCD0CCE0CCC050BE10BCD0BCD0BD70BCDFFC3BFFDFFFF04FFFF0234007F300121100F0CCA0CCA0CCB0CCC0CCA0CCC0CCB0CCB0CCB0CCB0CCB0CCA0CCC0CCC0CCB050BEB0BCD0BCD0BCD0BC3FFD1BFE5FFFF04FFFF0292005FB400C350C4A7\r'
Cheers
Well, I managed to decode part of the frame:
Container:
NumberOfModules = 2
Module = ListContainer:
Container:
NumberOfCells = 15
CellVoltages = ListContainer:
3.277
3.278
3.276
3.278
3.275
3.276
3.277
3.276
3.277
3.275
3.276
3.277
3.277
3.278
3.276
NumberOfTemperatures = 5
AverageBMSTemperature = 30.41
GroupedCellsTemperatures = ListContainer:
30.21
30.21
30.31
30.21
Current = -6.1
Voltage = 49.149
Power = -299.8089
RemainingCapacity = 65.535
TotalCapacity = 65.535
CycleNumber = 564
foobar = 16
Module2 = ListContainer:
Container:
NumberOfCells = 15
CellVoltages = ListContainer:
3.274
3.274
3.275
3.276
3.274
3.276
3.275
3.275
3.275
3.275
3.275
3.274
3.276
3.276
3.275
NumberOfTemperatures = 5
AverageBMSTemperature = 30.51
GroupedCellsTemperatures = ListContainer:
30.21
30.21
30.21
30.11
Current = -4.7
Voltage = 49.125
Power = -230.88750000000002
RemainingCapacity = 65.535
TotalCapacity = 65.535
CycleNumber = 658
greedy = ListContainer:
0
95
180
0
195
80
TotalPower = -299.8089
StateOfCharge = 1.0
But I specified the protocol manually:
get_values_fmt = construct.Struct(
"NumberOfModules" / construct.Byte,
"Module" / construct.Array(1, construct.Struct(
"NumberOfCells" / construct.Int8ub,
"CellVoltages" / construct.Array(construct.this.NumberOfCells, ToVolt(construct.Int16sb)),
"NumberOfTemperatures" / construct.Int8ub,
"AverageBMSTemperature" / ToCelsius(construct.Int16sb),
"GroupedCellsTemperatures" / construct.Array(construct.this.NumberOfTemperatures - 1, ToCelsius(construct.Int16sb)),
"Current" / ToAmp(construct.Int16sb),
"Voltage" / ToVolt(construct.Int16ub),
"Power" / construct.Computed(construct.this.Current * construct.this.Voltage),
"RemainingCapacity" / DivideBy1000(construct.Int16ub),
"_undef1" / construct.Int8ub,
"TotalCapacity" / DivideBy1000(construct.Int16ub),
"CycleNumber" / construct.Int16ub,
)),
"foobar" / construct.Int8ub,
"foobar" / construct.Int8ub,
"foobar" / construct.Int8ub,
"foobar" / construct.Int8ub,
"foobar" / construct.Int8ub,
"foobar" / construct.Int8ub,
"Module2" / construct.Array(1, construct.Struct(
"NumberOfCells" / construct.Int8ub,
"CellVoltages" / construct.Array(construct.this.NumberOfCells, ToVolt(construct.Int16sb)),
"NumberOfTemperatures" / construct.Int8ub,
"AverageBMSTemperature" / ToCelsius(construct.Int16sb),
"GroupedCellsTemperatures" / construct.Array(construct.this.NumberOfTemperatures - 1, ToCelsius(construct.Int16sb)),
"Current" / ToAmp(construct.Int16sb),
"Voltage" / ToVolt(construct.Int16ub),
"Power" / construct.Computed(construct.this.Current * construct.this.Voltage),
"RemainingCapacity" / DivideBy1000(construct.Int16ub),
"_undef1" / construct.Int8ub,
"TotalCapacity" / DivideBy1000(construct.Int16ub),
"CycleNumber" / construct.Int16ub,
)),
"greedy" / construct.GreedyRange(construct.Byte),
"TotalPower" / construct.Computed(lambda this: sum([x.Power for x in this.Module])),
"StateOfCharge" / construct.Computed(lambda this: sum([x.RemainingCapacity for x in this.Module]) / sum([x.TotalCapacity for x in this.Module])),
)
There seems to be some extra bytes sent after the first module (written as foobar
in the construct proto). So, my questions are:
Interesting though
Cheers !
And the code if you want to try manually:
def get_values(self):
#self.send_cmd(2, 0x42, b'FF')
#f = self.read_frame()
rf = b'~2002460010F011020F0CCD0CCE0CCC0CCE0CCB0CCC0CCD0CCC0CCD0CCB0CCC0CCD0CCD0CCE0CCC050BE10BCD0BCD0BD70BCDFFC3BFFDFFFF04FFFF0234007F300121100F0CCA0CCA0CCB0CCC0CCA0CCC0CCB0CCB0CCB0CCB0CCB0CCA0CCC0CCC0CCB050BEB0BCD0BCD0BCD0BC3FFD1BFE5FFFF04FFFF0292005FB400C350C4A7\r'
ff = self._decode_hw_frame(raw_frame=rf)
f = self._decode_frame(ff)
print(f)
print(f.info[1:])
# infoflag = f.info[0]
d = self.get_values_fmt.parse(f.info[1:])
return d
Hi @Frankkkkk ,
Apologies for the delay in getting back to you:
Thanks for your help on this, it's really is appreciated
I believe I've switched the US2000 to become the master, however, the documentation says to always use the US3000 as the primary when you have a mixture of US2000's/US3000's.
Here is the output:
NumberOfModules = 2
Module = ListContainer:
Container:
NumberOfCells = 15
CellVoltages = ListContainer:
3.287
3.289
3.288
3.287
3.287
3.289
3.289
3.288
3.286
3.287
3.287
3.288
3.288
3.287
3.288
NumberOfTemperatures = 5
AverageBMSTemperature = 30.11
GroupedCellsTemperatures = ListContainer:
30.11
30.11
30.21
30.11
Current = -1.8
Voltage = 49.315
Power = -88.767
RemainingCapacity = 29.0
TotalCapacity = 50.0
CycleNumber = 659
Container:
NumberOfCells = 15
CellVoltages = ListContainer:
3.288
3.289
3.289
3.288
3.289
3.288
3.288
3.29
3.289
3.289
3.289
3.289
3.289
3.29
3.289
NumberOfTemperatures = 5
AverageBMSTemperature = 30.21
GroupedCellsTemperatures = ListContainer:
30.21
30.21
30.31
30.21
Current = -1.7
Voltage = 49.333
Power = -83.86609999999999
RemainingCapacity = 43.66
TotalCapacity = 8.464
CycleNumber = 565
TotalPower = -172.63309999999998
StateOfCharge = 1.242816091954023
I'm not sure how much I believe some of the numbers, "StateOfCharge" for example...?
Hi, No problem for the delays ! :-) Interesting results :thinking:
I suppose we could patch the decoding to handle US3000 as primary modules.. It's strange though. Sadly I don't have one so I can't really test this edge case. Maybe we could add a warning in the readme as a workaround.
As for the StateOfCharge
, it's meant to be a percent (0-1: sum(all remaining capacities)/sum(total capacities)
). However in your case the US3000 states a RemainingCapacity
of 43
but a TotalCapacity
of... 8.464
:frowning: which thus skews the calculation
If that's okay with you, I'll just add a warning for this edge case in the readme, and maybe in the future we can fix the US3000-US2000 bug ?
Cheers
I would like to assist on this, what would be the preferred next steps. I have both US2000 and US3000 with the 3000 as the primary right now
Hello, using a cable like mentioned above I get the following error message:
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/thonny/backend.py", line 1171, in wrapper
result = method(self, *args, **kwargs)
File "/usr/lib/python3/dist-packages/thonny/backend.py", line 1158, in wrapper
return method(self, *args, **kwargs)
File "/usr/lib/python3/dist-packages/thonny/backend.py", line 1232, in _execute_prepared_user_code
exec(statements, global_vars)
File "/home/pi/syncthing/Lixy/Python/Pylontech/pylontech/Pylontech-test.py", line 6, in <module>
print(p.get_values())
File "/home/pi/syncthing/Lixy/Python/Pylontech/pylontech/pylontech.py", line 273, in get_values
f = self.read_frame()
File "/home/pi/syncthing/Lixy/Python/Pylontech/pylontech/pylontech.py", line 203, in read_frame
f = self._decode_hw_frame(raw_frame=raw_frame)
File "/home/pi/syncthing/Lixy/Python/Pylontech/pylontech/pylontech.py", line 184, in _decode_hw_frame
assert got_frame_checksum == int(frame_chksum, 16)
ValueError: invalid literal for int() with base 16: b'\xbf\xff\xff\xff'
If I insert a print(raw_frame)
in _decode_hw_frame()
I get the result
b'\xf0\xff\xff\xff\xff\xbf\xff\xff\xff\xff\xff\xbf\xff\xff\xff\x1f'
Do you have any idea what could be wrong? I have a single US3000C connected on port "B/RS485" and I only wired pins 7 and 8.
Without any change now I get the following result as raw_data:
b'\xf0\xff\xff\xff\xff\xbf\xff\xff\xff\xff\xff\xff\xff\xbf\xf7\xf7\x1f~20024600F07A11020F0CF80CF80CF80CF80CF90CF80CF80CF80CF80CF80CF90CF90CF90CF90CF9050B9D0B7A0B770B770B8D0000C28EFFFF04FFFF000000DBB0012110E1B6\r\x00'
What does the header mean or how can I remove it?
Hi @michaelhutter Indeed it is strange as part of the second frame looks valid enough. Is your cable shielded or near high-enough EM radiations ? Did you try changing your USB-rs485 converter ? Cheers
The first adapter which I bought did not work at all. The second adapter is working but gives these extra bytes in the beginning. I think I try to change your code a little bit, so that it removes all chars until the first occurence of '~'. May be using a regex or so. I am experienced in other languages, but not in Python. So would it be possible for you to give me a hint about which Python commands could do me the job? Is there a command like "FindFirstOccurence(haystack, needle)" and "right(string, pos)"?
I am still struggling with my Pylontech US3000C.
If I run print(p.get_values())
then raw_frame
in _decode_hw_frame()
has the value
b'\xf0\xff\xff\xff\xff\xbf\xff\xff\xff\xff\xff\xbf\xff\xff\xff\x1f'
If I run print(p.get_values_single(2))
then raw_frame
in _decode_hw_frame()
has the value
b'\xf0\xff\xff\xff\xff\xbf\xff\xff\xff\xff\xff\xff\xff\xbf\xf7\xf7\x1f~20024600F07A11020F0CF60CF60CF60CF60CF60CF60CF60CF60CF60CF60CF60CF60CF60CF60CF6050B8F0B710B6E0B6E0B7E0000C26AFFFF04FFFF000000D8CC012110E1CB\r\x00'
@Frankkkkk Can you explain what is the difference between get_values()
and get_values_single(2)
?
What does the parameter (2)
mean?
In both cases the CRC does not match and I don't get any result.
I tried a lot of things but unfortunately was not successful until now :-(
@michaelhutter i not python guy but I want to create pylontech emulator and investigate this scripts and pylon protocol description. Get_values and get_values_single is the same command but values hardcoded address as 255. I didn't find this address descruption in pylontech documentation. 2 is number of battery if we have more than one pattery. Do you have real battery? Could you try to execute requests on real battery with Hterm an provide responses? These request ask for protocol version from master battery from grpup 0, 1, 2, 3, 4, 5.
52 => 7E 30 30 35 32 34 36 34 46 30 30 30 30 46 44 39 35 0D 42 => 7E 30 30 34 32 34 36 34 46 30 30 30 30 46 44 39 36 0D 32 => 7E 30 30 33 32 34 36 34 46 30 30 30 30 46 44 39 37 0D 22 => 7E 30 30 32 32 34 36 34 46 30 30 30 30 46 44 39 38 0D 12 => 7E 30 30 31 32 34 36 34 46 30 30 30 30 46 44 39 39 0D 02 => 7E 30 30 30 32 34 36 34 46 30 30 30 30 46 44 39 41 0D
Could you execute these requests with HTerm 9https://www.der-hammer.info/pages/terminal.html) on real battery? Just copy 7E .. OD an send to battery?
Hello, unsure if I should make a new issue.
I tried using an ethernet - USB adapter and the unit (US3000c) just beeps like crazy.
So probably it's the wrong adapter?
Thanks!
Hi @EmCeBeh , yes please create a new issue. Unless I'm mistaken, even though they both use the same form factor (RJ45), the protocols are completely different. You need an RS485 adapter, and not ethernet ! Cheers !
Hi @Frankkkkk
Would you mind letting us know what hardware you are using to communicate with the Pylontech batteries. For example, which USB to RS485 cable you are using, which OS/Hardware etc.
I'm struggling with a Raspberry Pi 3B+ and USB to RS485 adapter (https://www.amazon.com/gp/product/B08RDZVP49)
Cheers!