sunspec / pysunspec2

SunSpec Python library for interfacing with SunSpec devices.
Apache License 2.0
54 stars 20 forks source link

Modbus exception 2 with d.scan() on Model ID 714 SMA Sunny Tripower X 20 #89

Open Friedemannn opened 2 months ago

Friedemannn commented 2 months ago

Hi, I'm trying to read out a SMA Tripower X Inverter. During daytime I can read up to and including Model 712, but on Model 714 I'm getting following error:

ModbusClientException: Modbus exception 2: addr: 41161 count: 95

Code up to this point is: import sunspec2.modbus.client as client d = client.SunSpecModbusClientDeviceTCP(slave_id=126,ipaddr='172.23.58.22', ipport=502,trace_func=print) d.scan()

According to the model_714.json and the PICS file supplied by SMA this adress corresponds to the Model ID and should be of size 1.

grafik 41161

As far as I understand pysunspec2 should do d.read(41161,1) and not d.read(41161,95). (Count corresponds to size/length?)

If I do .read() manually I'm getting the following results: grafik With d.read(41161,3) its the exception again: ModbusClientException: Modbus exception 2: addr: 41161 count: 3 If I do d.read(41162,1) its b'\x00]' which corresonds to [0,93].

This probably has something to do with Issue #36, but I don't fully understand whats happening. I've also run @bijwaard's sma-test.py and got this result:

41161: ID=234 41162: L=93 traceback ... Modbus exception 2: addr: 41256 count: 1

Just to check if I understand whats happening: With d.read(41161,1) I'm reading the first byte of 41161 which should be something like [0,714] but the Inverter responds with something wrong which trips up d.scan()? Edit: [2,202] is correct, it equal 2*256+202=714 With d.read(41161,2) I'm reading the first two bytes of 41161, which is 41161 and 41162? Edit: That is whats happening. With d.read(41161,3) I'm trying to read 41163 which doesn't exist/isn't implemented. Because of that I'm getting the exception. @bijwaard's sma-test.py somehow adds an offset with which one can somehow successfully read the whole model? Why is that?

To conclude, is this a problem with pysunspec2 or should I contact SMA? (Or with my implementation?)

(When I played around with it yesterday d.scan() didn't work during daytime but did work during nighttime. Without sun I've gotten the following result for 714:

{"ID": 714, "L": 18, "PrtAlrms": null, "NPrt": 0, "DCA": null, "DCW": null, "DCWhInj": null, "DCWhAbs": null, "DCA_SF": -1, "DCV_SF": 0, "DCW_SF": 1, "DCWH_SF": 0, "Tmp_SF": null}]}

But L should be 93? Anyhow, today I was not able to replicate it, the exception still pops up even without sun. Maybe because it has nothing to do with it?) Edit: Is replicable, see below.

bijwaard commented 2 months ago

Hi, Hope someone can spot the error with your inverter, my inverter does not have your Model, so hard for me to test this. The checkWithOffsets() function in the script sma-test.py simply walks through the Models via modbus respecting their lengths. I noticed that the script is silently ignoring exceptions, you may want to change the exception handling to something like below, to see actual exceptions (you can choose to ignore them by not setting valid=False):

    except Exception as e:
      print("Problem reading register %s: %s"%(reg+1,str(e)))
      valid=False
      pass

Kind regards, Dennis

Friedemannn commented 2 months ago

Hi @bijwaard,

thank you for your reply! I've added your code snippet like this:

def checkWithOffsets():
  valid=True
  reg=41161
  while valid:
    res1=d.read(reg,1)
    res1=16*res1[0]+res1[1]
    print("%d: ID=%s"%(reg,res1))
    try:
      res2=d.read(reg+1,1)
      res2=16*res2[0]+res2[1]
      print("%d: L=%s"%(reg+1,res2))
    except Exception as e:
      print("Problem reading register %s: %s"%(reg+1,str(e)))
      valid=False
      pass

    if res2==0:
      break
    reg=reg+2+res2

And got this output this afternoon:

41161: ID=234 41162: L=93 Traceback (most recent call last): ... sunspec2.modbus.modbus.ModbusClientException: Modbus exception 2: addr: 41256 count: 1

and without sun a few minutes ago:

41161: ID=234 41162: L=18 41181: ID=4335 41182: L=0

So the additional code snippet doesn't make a difference during sunlight. (I've never tested sma-test.py wo sun.) d.scan() and d.DERMeasureDC[0].get_dict(computed=True) is once again outputting this without sun:

{'ID': 714, 'L': 18, 'PrtAlrms': None, 'NPrt': 0, 'DCA': None, 'DCW': None, 'DCWhInj': None, 'DCWhAbs': None, 'DCA_SF': -1, 'DCV_SF': 0, 'DCW_SF': 1, 'DCWH_SF': 0, 'Tmp_SF': None}

With sun im getting the same exception today as the days before.

So it does indeed make a difference if the sun is shining or not. Maybe it just wasnt dark (long) enough yesterday in order for the second cpu in the inverter to be shut down.

What I'm wondering now is: Why does d.read(41161,3) and sma-test.py (with or wo sun) give different results for the Model ID on register 41161 than d.scan() without sunlight? 234 vs. 714

(And of course I'm still wondering why d.scan() isn't working while the sun is shining.)

I guess I'm gonna have to investigate what d,scan does in detail in the next few days.

bijwaard commented 2 months ago

Regarding sma_test: Since d.read() returns byte array insead of hexadecimal, so you'll need to multiply res1[0] and res2[0] with 256 instead of 16, this probably fixes the model number 234-32+512 and maybe also length 18-16+256.

Furthermore, I would suggest to enable trace logging, so sunspec/modbus experts can follow the complete modbus interaction of d.scan().

Friedemannn commented 2 months ago

Thanks once again for the input. So i guess the plot thickens, that something with d.scan() is going wrong(?).

Here is the output of d.scan() with trace logging. scan_w_tracelogging.txt

How does one read this?

bijwaard commented 2 months ago

The tracelog gives sunspec/modbus requests (>) and inverter responses (<) in hexademimal format. You may be able to spot the ID=714 (i.e. 0x02CA) of your model 7 lines from the end. Normally pysunspec2 interprets this for you using the JSON files;-)

Your trace seems to be when everything is ok, you can retry when the inverter is sleepy. The outlier seems to be the length field of model 714 that sometimes gives L=18 with sma-test.py and d.scan(), and sometimes gives L=93 with sma-test.py while d.scan() fails. The sunspec table in your first message specifies the length to be L=93. The sizes in the JSON file are probably the number of bytes to read for certain attributes.

With the factor 256 explained above, sma-test.py will give ID=65535 (0xffff) for the last model with length L=0, i.e. for my inverter it closes with:

...
40555: ID=132
40556: L=64
40621: ID=160
40622: L=48
40671: ID=65535
40672: L=0
Friedemannn commented 2 months ago

Thanks for the explanation, I think I'm slowly getting the hang of it.

The tracelog is during sunlight when L=93. I'm not sure if everything is ok because when you look at the last 8 lines something strange happens:

> 0000000000067E03A0C90001 ->request Register: 41161 Count: 1 (Model ID)
< 0000000000057E030202CA ->response: 714 (uint16)
> 0000000000067E03A0CA0001 ->request Register: 41162 Count: 1 (Model Length)
< 0000000000057E0302005D ->response: 93 (uint16)
> 0000000000067E03A0CB0003 ->request Register: 41163 Count: 3 (PrtAlrms and should be size 2 not 3, PrTAlrms is marked as unimplemented)
< 0000000000097E0306FFFFFFFF0003 ->response: ? (should be bitfield32) (The inverter only responded with 97E0306 on the very first request)
> 0000000000067E03A0C9005F ->request Register: 41161 Count: 95 (Tries to read up to 41255 which should work because last address is 41244 with size 2 according to the table provided by SMA)
< 0000000000037E8302 ->response: Disconnect/modbus exception 2 (?)

If I look at the other requests and responses (tbh I only compared it to the first 13 lines (Edit: now until line 27)) in the tracelog, I'm noticting that:

On the first two models pysunspec individually reads Model ID and Length after that it reads the whole model (from first adress to last adress) at once with address [Model ID adress] and count: [Model length + 2] and then probably does the same thing with the next models. Edit: Not always on Model 701 for example it reads the last adress separately maybe because its a string with size 32?

With Model 714 it also reads PrtAlarms besides ID and Length. Which is weird because it seems kinda random and it should respond with None(?) because its unimplemented. And then the Inverter disconnects when pysunspec tries to read [Model ID adress] [Length + 2].

Edit2: I've tried to max out count on d.read(41161, count) And reached 22, which corresponds to 41182 which is Prt.1.ID. Everything above yields Modbus exception 2 (prbly, I tried until 30 to see if something changes if I read the whole IDStr).

So I guess the plot "de-thickens" that somethings going wrong with d.scan()? :D And its probably the fault of the inverter?

Edit3: I just remembered that we have another Tripower X 20 Inverter, which currently has no panels connected to it. I've just run d.scan() on that one and it also responds with L=18 so its even more likely that it does make a difference if DC Power is supplied or not.

bijwaard commented 2 months ago

Can you try res2=d.read(41181,1) and d.read(41182,1)when you get L=93? I wonder if you still get ID=0xffff and L=0 on these adresses, like after L=18. In that case your inverter sunspec may be wrong.

Friedemannn commented 2 months ago

Here's the result: grafik If that is what you meant?

This still has the expected output but d.read(41183,8) has not: (I think this should result in None) grafik

With the adress table for reference: grafik

And here's the whole overview: STP-X_STP_xx-50_Sunspec_Modbus_Model_PICS_Overview_V1.zip

Edit: I'm getting the same results with this tool. (But you have to shift every adress by 1.) grafik

bijwaard commented 2 months ago

Yes, that is what I expected. The length given for model 714 by your inverter should probably always be 18 to correctly read the next model, which is the end of the chain with ID=0xffff and L=0.

It sometimes returns 93 as in the table of your first model. Do you have the rest of the table, e.g. what registers are supposed to be just before address 41181+93?

Friedemannn commented 2 months ago

I don't think so, it should be 93, because during power production it should also display the data of the different DC ports.

You're maybe confusing the start adress of this model. 41161 is the Model ID. One before 41161+93 is 41253 which is Prt.3.DCSta. Right before 41181+93 there is nothing, because Model 714 is the last model and ends with adress 41255 which belongs to Prt.3.DCAlrm of size 2.

This is the complete table for 714 the complete xlsx is linked in my previous comment:

grafik grafik grafik

bijwaard commented 2 months ago

summarizing, the next sunspec model read would start at:

Not sure if sunspec requires the last model to be 0xffff with 0 length, else this may be sunspec compliance issue of your inverter. Does SMA have a firmware update that fixes it? Else you could contact SMA about it.

In the meantime, pysunspec2 could be modified to ignore the read error of the last model of d.scan() and return everything read upto that point. On second thought, pysunspec2 may still remember the read models, in that case you could just put try/except around d.scan() call, and try pysunspec2 to get the model 714 contents.

Friedemannn commented 2 months ago

Hi Dennis,

thank you for all your help. Ig the issue is then that the inverter changes the Model Length correctly with sun/no sun but does'nt provide more adresses with sun.

I already updated the firmware to the newest version available. (03.06.15.R) I've created a support request with SMA and will report back with news, if there are any.

Yeah, I'll prbly use try/except until it's fixed.

Until then, thank you again for the help and time invested.

Kudrat9 commented 2 months ago

Hi Friedemannn, this seems like an issue with the inverter itself. The length of a model should be fixed (with or without DC power). Since you've created a support request with SMA, I hope they can help you out. We'll also try and reach out to them to get some input.