You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2026/08/18 10:38:05 UTC

(plc4x) 08/09: feat(plc4go/s7): real round-trip ping

This is an automated email from the ASF dual-hosted git repository.

sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 203cfdd5ad5048b81b13f1fe125a5021e75c1121
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Tue Aug 18 12:17:25 2026 +0200

    feat(plc4go/s7): real round-trip ping
    
    The default ping only checked connection flags and couldn't detect a
    half-open TCP connection or an unresponsive CPU. Port the plc4j approach:
    a one-byte ReadVar of %M0 where any S7 answer counts as alive.
---
 plc4go/internal/s7/Connection.go | 60 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/plc4go/internal/s7/Connection.go b/plc4go/internal/s7/Connection.go
index 7f3589bb5a..9e0d293623 100644
--- a/plc4go/internal/s7/Connection.go
+++ b/plc4go/internal/s7/Connection.go
@@ -415,6 +415,66 @@ func (c *Connection) GetMetadata() apiModel.PlcConnectionMetadata {
 	}
 }
 
+// Ping does a real round trip: S7 has no dedicated ping, so a one-byte read of %M0 is
+// issued and any S7 answer (including a PLC error) counts as alive - only transport
+// failures and timeouts report an error.
+func (c *Connection) Ping(ctx context.Context) error {
+	if !c.IsConnected() {
+		return errors.New("not connected")
+	}
+	tpduId := c.tpduGenerator.getAndIncrement()
+	pingRequest := readWriteModel.NewTPKTPacket(readWriteModel.NewCOTPPacketData(
+		nil,
+		readWriteModel.NewS7MessageRequest(
+			tpduId,
+			readWriteModel.NewS7ParameterReadVarRequest([]readWriteModel.S7VarRequestParameterItem{
+				readWriteModel.NewS7VarRequestParameterItemAddress(
+					readWriteModel.NewS7AddressAny(readWriteModel.TransportSize_BYTE, 1, 0, readWriteModel.MemoryArea_FLAGS_MARKERS, 0, 0),
+				),
+			}),
+			nil,
+		),
+		true,
+		uint8(tpduId),
+	))
+	successChan := make(chan struct{}, 1)
+	errChan := make(chan error, 1)
+	if err := c.messageCodec.SendRequest(ctx, "ping", pingRequest, func(message spi.Message) bool {
+		tpktPacket, ok := message.(readWriteModel.TPKTPacket)
+		if !ok {
+			return false
+		}
+		cotpPacketData, ok := tpktPacket.GetPayload().(readWriteModel.COTPPacketData)
+		if !ok {
+			return false
+		}
+		payload := cotpPacketData.GetPayload()
+		return payload != nil && payload.GetTpduReference() == tpduId
+	}, func(message spi.Message) error {
+		select {
+		case successChan <- struct{}{}:
+		default:
+		}
+		return nil
+	}, func(err error) error {
+		select {
+		case errChan <- err:
+		default:
+		}
+		return nil
+	}); err != nil {
+		return errors.Wrap(err, "error sending ping request")
+	}
+	select {
+	case <-successChan:
+		return nil
+	case err := <-errChan:
+		return errors.Wrap(err, "got error while waiting for ping response")
+	case <-ctx.Done():
+		return ctx.Err()
+	}
+}
+
 func (c *Connection) dispatchUnsolicitedUserData(channel <-chan readWriteModel.S7MessageUserData) {
 	for message := range channel {
 		c.subscriberMutex.Lock()