S7NetPlus / s7netplus

S7.NET+ -- A .NET library to connect to Siemens Step7 devices
MIT License
1.29k stars 577 forks source link

Set PLC-time through code #502

Closed SmackyPappelroy closed 11 months ago

SmackyPappelroy commented 1 year ago

Is it possible to implement a method to set the PLC-time? That would be a great feature to have in a logging application so the clocks are synchronized. I have seen Wireshark captures of how to set the clock. That might provide some guidance.

mycroes commented 1 year ago

Yes. Are those captures from the Wireshark page about the S7 dissector? I think I might have seen them as well...

I recently implemented PLC status reading, which uses the SZL subset of communication, I believe system time uses SZL commands as well.

SmackyPappelroy commented 1 year ago

I have seen that you implemented that great feature. I am not sure that the set clock function is an SZL protocol function, by just browsing the wireshark capture, SZL seems to be related to diagnostic and status data FROM the PLC. Here is the link to the wireshark captures: https://wiki.wireshark.org/S7comm. FYI I found this manual where it describes the SZL-id and the index for all the functions starting on page 151 in this manual: "Instructions List: S7–300 Programmable Controller" After further digging I found this on GitHub as well https://github.com/moki-ics/s7commwireshark

mycroes commented 12 months ago

Just started on this. Will start by implementing clock read, then I'll add clock write. I hope to have this finished within a few days.

SmackyPappelroy commented 11 months ago

Perfect. I tried to create a set clock method. I never got it to work, I just got a socket exception. But it might be something useful for you.

``public static void WriteClockRequest(System.IO.MemoryStream stream, System.DateTime dateTime) { // Parameter head stream.Write(new byte[] { 0x32, 0x07, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x08, 0x00, 0x0e });

// Parameter
stream.Write(new byte[] { 0x00, 0x01, 0x12 });
stream.WriteByte(0x04); // Parameter length
stream.WriteByte(0x11); // Method
stream.WriteByte(0x47); // Type / function group
stream.WriteByte(0x02); // Subfunction
stream.WriteByte(0x00); // Sequence number

// Data
stream.WriteByte(0xff); // Return code
stream.WriteByte(0x09); // Transport size
stream.Write(new byte[] { 0x00, 0x0a }); // Length

stream.WriteByte(0x00); // Reserved
var s7DateTime = DateTime.ToByteArray(dateTime);
stream.Write(s7DateTime); // Date and time

}``

mycroes commented 11 months ago

Hi @SmackyPappelroy,

I think what's wrong is your stream.WriteByte(0x00); // Reserved. That's actually part of the date value, so now your data doesn't match the protocol. That's also why I recently started adding some unit tests and the infrastructure to test full communication cycles, which easily exposes problems like these.

Anyway, in the meantime I added clock read in #507, you can take that for a spin and see if it works as expected. I'll be adding clock write soon.

SmackyPappelroy commented 11 months ago

Hello @mycroes. I tried your code on a simulated S7300 and it worked as expected. I also tried against a real S71500 CPU but then i got an exception:

System.Exception HResult=0x80131500 Message=Response from PLC indicates error 0x8104. Source=S7.Net StackTrace: at S7.Net.Plc.ParseClockReadResponse(Byte[] message) in C:\temp\temp\S7.Net\Plc.Clock.cs:line 41 at S7.Net.Plc.ReadClock() in C:\temp\temp\S7.Net\PlcSynchronous.cs:line 504 at Program.

$(String[] args) in C:\temp\temp\testing\Program.cs:line 7

This exception was originally thrown at this call stack: S7.Net.Plc.ParseClockReadResponse(byte[]) in Plc.Clock.cs S7.Net.Plc.ReadClock() in PlcSynchronous.cs Program.

$(string[]) in Program.cs

I am not sure why this happens. If I can be of any assistance, let me know. I have access to real Siemens PLCs, so I can test changes.

mycroes commented 11 months ago

Could you provide the contents of message in Plc.Clock.cs ParseClockReadResponse? I'll also add a new exception type that includes the data by default so it's apparent from the exception.

SmackyPappelroy commented 11 months ago

pduErr = 33028 and message is:

Byte

50 7 0 0 0 0 0 12 0 4 0 1 18 8 18 135 1 0 0 0 129 4 10 0 0 0

mycroes commented 11 months ago

I'm not sure if this maybe just doesn't work on S7 1500. I've been comparing my code to st-one-io/nodes7 and from what I see it's producing the same data. The message you received from the PLC seems to be a valid response message to the request (all of the data seems to indicate this is indeed a response to the get time request that was sent), it just doesn't have actual time data but the error fields are all set. The returned error code is actually defined in nodes7:

"33028": "This service is not implemented on the module or a frame error was reported",

That does seem to indicate it might just not be available on S7 1500. If you can get the time from TIA portal there's probably a different communication method to do that.

I'll continue on adding this feature though, but I guess it might only work for 300/400 PLC's.

SmackyPappelroy commented 11 months ago

I haven't been able to get it to work on an S7-1500. The S7COMM filter for WireShark is mainly for S7300/S7400 CPUs so I haven't been able to capture anything useful when I try to do a clock read in TIA Portal. Nevertheless it is a good feature to have even if we can't figure out how to do it for an S7-1500 CPU.

mycroes commented 11 months ago

Clock write support is there as well now. I guess it only works for S7 300 / S7 400, but if you could give that a spin that would be nice. I didn't expand the exception messages yet, I haven't decided on including that in the PR or as a separate PR. Looking forward to your response.

SmackyPappelroy commented 11 months ago

I have tried both the sync and the async methods against an S7300 and it worked perfectly.

I have also tried against both a real S71500 and a simulated. That did not work, I only get this exception:

System.Exception HResult=0x80131500 Message=Response from PLC indicates error 0x8104. Source=S7.Net StackTrace: at S7.Net.Plc.AssertPduResult(Byte[] message) in C:\Test\S7.Net\Plc.Clock.cs:line 82 at S7.Net.Plc.ParseClockWriteResponse(Byte[] message) in C:\Test\S7.Net\Plc.Clock.cs:line 71 at S7.Net.Plc.WriteClock(DateTime value) in C:\Test\S7.Net\PlcSynchronous.cs:line 516 at Program.

$(String[] args) in C:\Test\ConsoleApp1\Program.cs:line 8

This exception was originally thrown at this call stack: S7.Net.Plc.AssertPduResult(byte[]) in Plc.Clock.cs S7.Net.Plc.ParseClockWriteResponse(byte[]) in Plc.Clock.cs S7.Net.Plc.WriteClock(System.DateTime) in PlcSynchronous.cs Program.

$(string[]) in Program.cs

mycroes commented 11 months ago

Thanks a lot for testing, I'll try to release this soon.