|
| 1 | +// Copyright 2018 xft. All rights reserved. |
| 2 | +// This software may be modified and distributed under the terms |
| 3 | +// of the BSD license. See the LICENSE file for details. |
| 4 | + |
| 5 | +package modbus |
| 6 | + |
| 7 | +import ( |
| 8 | + "io" |
| 9 | + "time" |
| 10 | +) |
| 11 | + |
| 12 | +// RTUOverTCPClientHandler implements Packager and Transporter interface. |
| 13 | +type RTUOverTCPClientHandler struct { |
| 14 | + rtuPackager |
| 15 | + rtuTCPTransporter |
| 16 | +} |
| 17 | + |
| 18 | +// NewRTUOverTCPClientHandler allocates and initializes a RTUOverTCPClientHandler. |
| 19 | +func NewRTUOverTCPClientHandler(address string) *RTUOverTCPClientHandler { |
| 20 | + handler := &RTUOverTCPClientHandler{} |
| 21 | + handler.Address = address |
| 22 | + handler.Timeout = tcpTimeout |
| 23 | + handler.IdleTimeout = tcpIdleTimeout |
| 24 | + return handler |
| 25 | +} |
| 26 | + |
| 27 | +// RTUOverTCPClient creates RTU over TCP client with default handler and given connect string. |
| 28 | +func RTUOverTCPClient(address string) Client { |
| 29 | + handler := NewRTUOverTCPClientHandler(address) |
| 30 | + return NewClient(handler) |
| 31 | +} |
| 32 | + |
| 33 | +// rtuTCPTransporter implements Transporter interface. |
| 34 | +type rtuTCPTransporter struct { |
| 35 | + tcpTransporter |
| 36 | +} |
| 37 | + |
| 38 | +func (mb *rtuTCPTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) { |
| 39 | + mb.tcpTransporter.mu.Lock() |
| 40 | + defer mb.tcpTransporter.mu.Unlock() |
| 41 | + |
| 42 | + // Establish a new connection if not connected |
| 43 | + if err = mb.tcpTransporter.connect(); err != nil { |
| 44 | + return |
| 45 | + } |
| 46 | + // Set timer to close when idle |
| 47 | + mb.tcpTransporter.lastActivity = time.Now() |
| 48 | + mb.tcpTransporter.startCloseTimer() |
| 49 | + // Set write and read timeout |
| 50 | + var timeout time.Time |
| 51 | + if mb.Timeout > 0 { |
| 52 | + timeout = mb.lastActivity.Add(mb.Timeout) |
| 53 | + } |
| 54 | + if err = mb.conn.SetDeadline(timeout); err != nil { |
| 55 | + return |
| 56 | + } |
| 57 | + |
| 58 | + // Send the request |
| 59 | + mb.tcpTransporter.logf("modbus: sending % x\n", aduRequest) |
| 60 | + if _, err = mb.conn.Write(aduRequest); err != nil { |
| 61 | + return |
| 62 | + } |
| 63 | + function := aduRequest[1] |
| 64 | + functionFail := aduRequest[1] & 0x80 |
| 65 | + bytesToRead := calculateResponseLength(aduRequest) |
| 66 | + |
| 67 | + var n int |
| 68 | + var n1 int |
| 69 | + var data [rtuMaxSize]byte |
| 70 | + //We first read the minimum length and then read either the full package |
| 71 | + //or the error package, depending on the error status (byte 2 of the response) |
| 72 | + n, err = io.ReadAtLeast(mb.conn, data[:], rtuMinSize) |
| 73 | + if err != nil { |
| 74 | + return |
| 75 | + } |
| 76 | + //if the function is correct |
| 77 | + if data[1] == function { |
| 78 | + //we read the rest of the bytes |
| 79 | + if n < bytesToRead { |
| 80 | + if bytesToRead > rtuMinSize && bytesToRead <= rtuMaxSize { |
| 81 | + if bytesToRead > n { |
| 82 | + n1, err = io.ReadFull(mb.conn, data[n:bytesToRead]) |
| 83 | + n += n1 |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + } else if data[1] == functionFail { |
| 88 | + //for error we need to read 5 bytes |
| 89 | + if n < rtuExceptionSize { |
| 90 | + n1, err = io.ReadFull(mb.conn, data[n:rtuExceptionSize]) |
| 91 | + } |
| 92 | + n += n1 |
| 93 | + } |
| 94 | + |
| 95 | + if err != nil { |
| 96 | + return |
| 97 | + } |
| 98 | + aduResponse = data[:n] |
| 99 | + mb.logf("modbus: received % x\n", aduResponse) |
| 100 | + return |
| 101 | +} |
0 commit comments