Documentation
¶
Overview ¶
Package modbusone provides a Modbus library to implement both server and client using one set of APIs.
For sample code, see examples/memory, and handler2serial_test.go
Example (SerialPort) ¶
package main import ( "fmt" "io" "github.com/xiegeo/modbusone" ) // handlerGenerator returns ProtocolHandlers that interact with our application. // In this example, we are only using Holding Registers. func handlerGenerator(name string) modbusone.ProtocolHandler { return &modbusone.SimpleHandler{ ReadHoldingRegisters: func(address, quantity uint16) ([]uint16, error) { fmt.Printf("%v ReadHoldingRegisters from %v, quantity %v\n", name, address, quantity) r := make([]uint16, quantity) return r, nil }, WriteHoldingRegisters: func(address uint16, values []uint16) error { fmt.Printf("%v WriteHoldingRegisters from %v, quantity %v\n", name, address, len(values)) return nil }, OnErrorImp: func(req modbusone.PDU, errRep modbusone.PDU) { fmt.Printf("%v received error:%x in request:%x", name, errRep, req) }, } } // serial is a fake serial port. type serial struct { io.ReadCloser io.WriteCloser } func newInternalSerial() (io.ReadWriteCloser, io.ReadWriteCloser) { r1, w1 := io.Pipe() r2, w2 := io.Pipe() return &serial{ReadCloser: r1, WriteCloser: w2}, &serial{ReadCloser: r2, WriteCloser: w1} } func (s *serial) Close() error { s.ReadCloser.Close() return s.WriteCloser.Close() } func main() { // Server id and baudRate, for Modbus over serial port. id := byte(1) baudRate := int64(19200) // Open serial connections: clientSerial, serverSerial := newInternalSerial() // Normally we want to open a serial connection from serial.OpenPort // such as github.com/tarm/serial. modbusone can take any io.ReadWriteCloser, // so we created two that talks to each other for demonstration here. // SerialContext adds baudRate information to calculate // the duration that data transfers should takes. // It also records Stats of read and dropped packets. clientSerialContext := modbusone.NewSerialContext(clientSerial, baudRate) serverSerialContext := modbusone.NewSerialContext(serverSerial, baudRate) // You can create either a client or a server from a SerialContext and an id. client := modbusone.NewRTUClient(clientSerialContext, id) server := modbusone.NewRTUServer(serverSerialContext, id) useClientAndServer(client, server, id) // follow the next function } func useClientAndServer(client modbusone.Client, server modbusone.ServerCloser, id byte) { termChan := make(chan error) go client.Serve(handlerGenerator("client")) go func() { err := server.Serve(handlerGenerator("server")) termChan <- err }() defer client.Close() defer server.Close() clientDoTransactions(client, id) server.Close() fmt.Println("serve terminated:", <-termChan) } func clientDoTransactions(client modbusone.Client, id byte) { startAddress := uint16(0) quantity := uint16(200) reqs, err := modbusone.MakePDURequestHeaders(modbusone.FcReadHoldingRegisters, startAddress, quantity, nil) if err != nil { fmt.Println(err) } fmt.Println("reqs count:", len(reqs)) startAddress = uint16(1000) quantity = uint16(100) reqs, err = modbusone.MakePDURequestHeaders(modbusone.FcWriteMultipleRegisters, startAddress, quantity, reqs) if err != nil { fmt.Println(err) } fmt.Println("reqs count:", len(reqs)) for _, r := range reqs { err = client.DoTransaction(r) if err != nil { fmt.Println(err, "on", r) } } n, err := modbusone.DoTransactions(client, id, reqs) if err != nil { fmt.Println(err, "on", reqs[n]) } }
Output: reqs count: 2 reqs count: 3 server ReadHoldingRegisters from 0, quantity 125 client WriteHoldingRegisters from 0, quantity 125 server ReadHoldingRegisters from 125, quantity 75 client WriteHoldingRegisters from 125, quantity 75 client ReadHoldingRegisters from 1000, quantity 100 server WriteHoldingRegisters from 1000, quantity 100 server ReadHoldingRegisters from 0, quantity 125 client WriteHoldingRegisters from 0, quantity 125 server ReadHoldingRegisters from 125, quantity 75 client WriteHoldingRegisters from 125, quantity 75 client ReadHoldingRegisters from 1000, quantity 100 server WriteHoldingRegisters from 1000, quantity 100 serve terminated: io: read/write on closed pipe
Example (Tcp) ¶
package main import ( "fmt" "net" "github.com/xiegeo/modbusone" ) // handlerGenerator returns ProtocolHandlers that interact with our application. // In this example, we are only using Holding Registers. func handlerGenerator(name string) modbusone.ProtocolHandler { return &modbusone.SimpleHandler{ ReadHoldingRegisters: func(address, quantity uint16) ([]uint16, error) { fmt.Printf("%v ReadHoldingRegisters from %v, quantity %v\n", name, address, quantity) r := make([]uint16, quantity) return r, nil }, WriteHoldingRegisters: func(address uint16, values []uint16) error { fmt.Printf("%v WriteHoldingRegisters from %v, quantity %v\n", name, address, len(values)) return nil }, OnErrorImp: func(req modbusone.PDU, errRep modbusone.PDU) { fmt.Printf("%v received error:%x in request:%x", name, errRep, req) }, } } func useClientAndServer(client modbusone.Client, server modbusone.ServerCloser, id byte) { termChan := make(chan error) go client.Serve(handlerGenerator("client")) go func() { err := server.Serve(handlerGenerator("server")) termChan <- err }() defer client.Close() defer server.Close() clientDoTransactions(client, id) server.Close() fmt.Println("serve terminated:", <-termChan) } func clientDoTransactions(client modbusone.Client, id byte) { startAddress := uint16(0) quantity := uint16(200) reqs, err := modbusone.MakePDURequestHeaders(modbusone.FcReadHoldingRegisters, startAddress, quantity, nil) if err != nil { fmt.Println(err) } fmt.Println("reqs count:", len(reqs)) startAddress = uint16(1000) quantity = uint16(100) reqs, err = modbusone.MakePDURequestHeaders(modbusone.FcWriteMultipleRegisters, startAddress, quantity, reqs) if err != nil { fmt.Println(err) } fmt.Println("reqs count:", len(reqs)) for _, r := range reqs { err = client.DoTransaction(r) if err != nil { fmt.Println(err, "on", r) } } n, err := modbusone.DoTransactions(client, id, reqs) if err != nil { fmt.Println(err, "on", reqs[n]) } } func main() { // TCP address of the host host := "127.2.9.1:12345" // Default server id id := byte(1) // Open server tcp listener: listener, err := net.Listen("tcp", host) if err != nil { fmt.Println(err) return } // Connect to server: conn, err := net.Dial("tcp", host) if err != nil { fmt.Println(err) return } // You can create either a client or a server client := modbusone.NewTCPClient(conn, 0) server := modbusone.NewTCPServer(listener) // shared example code with serial port useClientAndServer(client, server, id) }
Output: reqs count: 2 reqs count: 3 server ReadHoldingRegisters from 0, quantity 125 client WriteHoldingRegisters from 0, quantity 125 server ReadHoldingRegisters from 125, quantity 75 client WriteHoldingRegisters from 125, quantity 75 client ReadHoldingRegisters from 1000, quantity 100 server WriteHoldingRegisters from 1000, quantity 100 server ReadHoldingRegisters from 0, quantity 125 client WriteHoldingRegisters from 0, quantity 125 server ReadHoldingRegisters from 125, quantity 75 client WriteHoldingRegisters from 125, quantity 75 client ReadHoldingRegisters from 1000, quantity 100 server WriteHoldingRegisters from 1000, quantity 100 serve terminated: accept tcp 127.2.9.1:12345: use of closed network connection
Index ¶
- Constants
- Variables
- func BoolsToData(values []bool, fc FunctionCode) ([]byte, error)
- func BytesDelay(baudRate int64, n int) time.Duration
- func DataToBools(data []byte, count uint16, fc FunctionCode) ([]bool, error)
- func DataToRegisters(data []byte) ([]uint16, error)
- func DoTransactions(c RTUTransactionStarter, slaveID byte, reqs []PDU) (int, error)
- func GetPDUSizeFromHeader(header []byte, isClient bool) int
- func GetPacketCutoffDurationFromSerialContext(s SerialContext, n int) time.Duration
- func GetRTUBidirectionalSizeFromHeader(header []byte) int
- func GetRTUSizeFromHeader(header []byte, isClient bool) int
- func IsRequestReply(r, a PDU) bool
- func MatchPDU(ask PDU, ans PDU) bool
- func MinDelay(baudRate int64) time.Duration
- func PacketCutoffDuration(baudRate int64, n int, cpuHiccup time.Duration) time.Duration
- func RegistersToData(values []uint16) ([]byte, error)
- func SetDebugOut(w io.Writer)
- func Uint64ToSlaveID(n uint64) (byte, error)
- type Client
- type ExceptionCode
- type FailoverRTUClient
- func (c *FailoverRTUClient) Close() error
- func (c *FailoverRTUClient) DoTransaction(req PDU) error
- func (c *FailoverRTUClient) GetTransactionTimeOut(reqLen, ansLen int) time.Duration
- func (c *FailoverRTUClient) Serve(handler ProtocolHandler) error
- func (c *FailoverRTUClient) SetServerProcessingTime(t time.Duration)
- func (c *FailoverRTUClient) StartTransactionToServer(slaveID byte, req PDU, errChan chan error)
- type FailoverSerialConn
- type FunctionCode
- func (f FunctionCode) IsBool() bool
- func (f FunctionCode) IsReadToServer() bool
- func (f FunctionCode) IsSingle() bool
- func (f FunctionCode) IsUint16() bool
- func (f FunctionCode) IsWriteToServer() bool
- func (f FunctionCode) MakeRequestHeader(address, quantity uint16) (PDU, error)
- func (f FunctionCode) MaxPerPacket() uint16
- func (f FunctionCode) MaxPerPacketSized(size int) uint16
- func (f FunctionCode) MaxRange() uint16
- func (f FunctionCode) SeparateError() (bool, FunctionCode)
- func (f FunctionCode) Valid() bool
- func (f FunctionCode) WithError() FunctionCode
- type Option
- type PDU
- func (p PDU) GetAddress() uint16
- func (p PDU) GetFunctionCode() FunctionCode
- func (p PDU) GetReplyValues() ([]byte, error)
- func (p PDU) GetRequestCount() (uint16, error)
- func (p PDU) GetRequestValues() ([]byte, error)
- func (p PDU) MakeReadReply(data []byte) PDU
- func (p PDU) MakeWriteReply() PDU
- func (p PDU) MakeWriteRequest(data []byte) PDU
- func (p PDU) ValidateRequest() error
- type PacketReader
- type ProtocolHandler
- type RTU
- type RTUClient
- func (c *RTUClient) Close() error
- func (c *RTUClient) DoTransaction(req PDU) error
- func (c *RTUClient) GetTransactionTimeOut(reqLen, ansLen int) time.Duration
- func (c *RTUClient) Serve(handler ProtocolHandler) error
- func (c *RTUClient) SetServerProcessingTime(t time.Duration)
- func (c *RTUClient) StartTransactionToServer(slaveID byte, req PDU, errChan chan error)
- type RTUServer
- type RTUTransactionStarter
- type SerialContext
- type SerialContextV2
- type ServerCloser
- type SimpleHandler
- type Stats
- type TCPClient
- type TCPServer
Examples ¶
Constants ¶
const ( TCPHeaderLength = 6 MBAPHeaderLength = TCPHeaderLength + 1 )
const MaxPDUSize = 253
MaxPDUSize is the max possible size of a PDU packet.
const MaxRTUSize = 256
MaxRTUSize is the max possible size of a RTU packet.
Variables ¶
var DefaultCPUHiccup = time.Second / 10
DefaultCPUHiccup is the max amount of time the local host is allowed to freeze before we break packets appear and throw away old unused partial packet data.
var ErrFcNotSupported = errors.New("this FunctionCode is not supported")
ErrFcNotSupported is another version of EcIllegalFunction, encountering of this error shows the error is locally generated, not a remote ExceptionCode.
var ErrServerTimeOut = errors.New("server timed out")
ErrServerTimeOut is the time out error for StartTransaction.
var ErrorCrc = fmt.Errorf("RTU data crc not valid")
ErrorCrc indicates data corruption detected by checking the CRC.
var OverSizeMaxRTU = MaxRTUSize
OverSizeMaxRTU overrides MaxRTUSize when OverSizeSupport is true.
var OverSizeSupport = false
OverSizeSupport ignores max packet size and encoded number of bytes to support over sided implementations encountered in the wild. This setting only applies to the server end, since client is always reserved in what it requests. Also change OverSizeMaxRTU properly.
Functions ¶
func BoolsToData ¶
func BoolsToData(values []bool, fc FunctionCode) ([]byte, error)
BoolsToData translates []bool to the data part of PDU dependent on FunctionCode.
func BytesDelay ¶
BytesDelay returns the time it takes to send n bytes in baudRate.
func DataToBools ¶
func DataToBools(data []byte, count uint16, fc FunctionCode) ([]bool, error)
DataToBools translates the data part of PDU to []bool dependent on FunctionCode.
func DataToRegisters ¶
DataToRegisters translates the data part of PDU to []uint16.
func DoTransactions ¶
func DoTransactions(c RTUTransactionStarter, slaveID byte, reqs []PDU) (int, error)
DoTransactions runs the reqs transactions in order. If any error is encountered, it returns early and reports the index number and error message.
func GetPDUSizeFromHeader ¶
GetPDUSizeFromHeader returns the expected sized of a PDU packet with the given PDU header, if not enough info is in the header, then it returns the shortest possible. isClient is true if a client/master is reading the packet.
func GetPacketCutoffDurationFromSerialContext ¶ added in v0.9.0
func GetPacketCutoffDurationFromSerialContext(s SerialContext, n int) time.Duration
func GetRTUBidirectionalSizeFromHeader ¶ added in v0.2.0
GetRTUBidirectionalSizeFromHeader is like GetRTUSizeFromHeader, except for any direction by checking the CRC for disambiguation of length.
func GetRTUSizeFromHeader ¶
GetRTUSizeFromHeader returns the expected sized of a RTU packet with the given RTU header, if not enough info is in the header, then it returns the shortest possible. isClient is true if a client/master is reading the packet.
func IsRequestReply ¶ added in v0.2.0
IsRequestReply test if PDUs are a request reply pair, useful for listening to transactions passively.
func MatchPDU ¶
MatchPDU returns true if ans is a valid reply to ask, including normal and error code replies.
func PacketCutoffDuration ¶ added in v0.9.0
func RegistersToData ¶
RegistersToData translates []uint16 to the data part of PDU.
func SetDebugOut ¶ added in v0.2.0
SetDebugOut to print debug messages, set to nil to turn off debug output.
func Uint64ToSlaveID ¶
Uint64ToSlaveID is a helper function for reading configuration data to SlaveID. See also flag.Uint64 and strconv.ParseUint.
Types ¶
type Client ¶ added in v0.9.0
type Client interface { ServerCloser RTUTransactionStarter DoTransaction(req PDU) error }
Client interface can both start and serve transactions.
type ExceptionCode ¶
type ExceptionCode byte //nolint:errname
ExceptionCode Modbus exception codes.
const ( // EcOK is invented for no error. EcOK ExceptionCode = 0 // EcInternal is invented for error reading ExceptionCode. EcInternal ExceptionCode = 255 EcIllegalFunction ExceptionCode = 1 EcIllegalDataAddress ExceptionCode = 2 EcIllegalDataValue ExceptionCode = 3 EcServerDeviceFailure ExceptionCode = 4 EcAcknowledge ExceptionCode = 5 EcServerDeviceBusy ExceptionCode = 6 EcMemoryParityError ExceptionCode = 8 EcGatewayTargetDeviceFailedToRespond ExceptionCode = 11 )
Defined exception codes, 5 to 11 are not used.
func ToExceptionCode ¶
func ToExceptionCode(err error) ExceptionCode
ToExceptionCode turns an error into an ExceptionCode (to send in PDU). Best effort with EcServerDeviceFailure as fail back.
- If the error is a ExceptionCode or warped ExceptionCode, the original ExceptionCode is returned. - IF the error is ErrFcNotSupported or warped ErrFcNotSupported, EcIllegalFunction is returned. - For all other cases, EcServerDeviceFailure is returned.
func (ExceptionCode) Error ¶
func (e ExceptionCode) Error() string
Error implements error for ExceptionCode.
type FailoverRTUClient ¶ added in v0.2.0
type FailoverRTUClient struct { SlaveID byte // contains filtered or unexported fields }
FailoverRTUClient implements Client/Master side logic for RTU over a SerialContext to be used by a ProtocolHandler with failover function.
func NewFailoverRTUClient ¶ added in v0.2.0
func NewFailoverRTUClient(com SerialContext, isFailover bool, slaveID byte) *FailoverRTUClient
NewFailoverRTUClient create a new client with failover function communicating over SerialContext with the give slaveID as default.
If isFailover is true, it is the secondary.
func (*FailoverRTUClient) Close ¶ added in v0.2.0
func (c *FailoverRTUClient) Close() error
Close closes the client and closes the connect.
func (*FailoverRTUClient) DoTransaction ¶ added in v0.2.0
func (c *FailoverRTUClient) DoTransaction(req PDU) error
DoTransaction starts a transaction, and returns a channel that returns an error or nil, with the default slaveID.
DoTransaction is blocking.
For read from server, the PDU is sent as is (after been warped up in RTU) For write to server, the data part given will be ignored, and filled in by data from handler.
func (*FailoverRTUClient) GetTransactionTimeOut ¶ added in v0.2.0
func (c *FailoverRTUClient) GetTransactionTimeOut(reqLen, ansLen int) time.Duration
GetTransactionTimeOut returns the total time to wait for a transaction (server response) to time out, given the expected length of RTU packets. This function is also used internally to calculate timeout.
func (*FailoverRTUClient) Serve ¶ added in v0.2.0
func (c *FailoverRTUClient) Serve(handler ProtocolHandler) error
Serve serves FailoverRTUClient side handlers,
A FailoverRTUClient expects a lot of "unexpected" read packets and "lost" writes so it is does not do the error checking that a normal client does, but instead try to guess the best interpretation.
func (*FailoverRTUClient) SetServerProcessingTime ¶ added in v0.2.0
func (c *FailoverRTUClient) SetServerProcessingTime(t time.Duration)
SetServerProcessingTime sets the time to wait for a server response, the total wait time also includes the time needed for data transmission.
func (*FailoverRTUClient) StartTransactionToServer ¶ added in v0.2.0
func (c *FailoverRTUClient) StartTransactionToServer(slaveID byte, req PDU, errChan chan error)
StartTransactionToServer starts a transaction, with a custom slaveID. errChan is required and usable, an error is set is the transaction failed, or nil for success.
StartTransactionToServer is not blocking.
For read from server, the PDU is sent as is (after been warped up in RTU) For write to server, the data part given will be ignored, and filled in by data from handler.
type FailoverSerialConn ¶ added in v0.2.0
type FailoverSerialConn struct { SerialContext // base SerialContext PacketReader // if primary has not received data for this long, it thinks it's disconnected // and go passive, just like at restart. // This potentially allows a long running passive with better data to take over // until primary has gained enough historical data. // default 10 seconds PrimaryDisconnectDelay time.Duration // when a failover is running, // the time it waits to take over again. // default 10 mins PrimaryForceBackDelay time.Duration // SecondaryDelay is the delay to use on a secondary to give time for the primary to reply first. // Default 0.1 seconds. SecondaryDelay time.Duration // MissDelay is the delay to use by the primary when passive to detect missed packets by secondary. // It must be bigger than SecondaryDelay for primary to detect an active failover. // Default 0.2 seconds. MissDelay time.Duration // number of misses until the other is detected as down // default 2 for primary, 4 for failover MissesMax int32 // contains filtered or unexported fields }
FailoverSerialConn manages a failover connection, which does failover using shared serial bus and shared slaveID. Slaves using other ids on the same bus is not supported. If the other side supports multiple slave ids, then it is better to implement failover on the other side by call different slaveIDs, or using separated serial ports.
func NewFailoverConn ¶ added in v0.2.0
func NewFailoverConn(sc SerialContext, isFailover, isClient bool) *FailoverSerialConn
NewFailoverConn adds failover function to a SerialContext.
func (*FailoverSerialConn) BytesDelay ¶ added in v0.2.0
func (s *FailoverSerialConn) BytesDelay(n int) time.Duration
BytesDelay implements BytesDelay for SerialContext.
func (*FailoverSerialConn) IsActive ¶ added in v0.2.0
func (s *FailoverSerialConn) IsActive() bool
IsActive returns if the connetion is in the active state. This state can change asynchronous so it is not useful for logic other than status gethering or testing in a controlled envernment.
type FunctionCode ¶
type FunctionCode byte
FunctionCode Modbus function codes.
const ( FcReadCoils FunctionCode = 1 FcReadDiscreteInputs FunctionCode = 2 FcReadHoldingRegisters FunctionCode = 3 FcReadInputRegisters FunctionCode = 4 FcWriteSingleCoil FunctionCode = 5 FcWriteSingleRegister FunctionCode = 6 FcWriteMultipleCoils FunctionCode = 15 FcWriteMultipleRegisters FunctionCode = 16 )
Implemented FunctionCodes.
func (FunctionCode) IsBool ¶
func (f FunctionCode) IsBool() bool
IsBool returns true if the FunctionCode concerns boolean values.
func (FunctionCode) IsReadToServer ¶
func (f FunctionCode) IsReadToServer() bool
IsReadToServer returns true if the FunctionCode is a read. FunctionCode 23 is both a read and write.
func (FunctionCode) IsSingle ¶
func (f FunctionCode) IsSingle() bool
IsSingle returns true if the FunctionCode can transmit only one value.
func (FunctionCode) IsUint16 ¶
func (f FunctionCode) IsUint16() bool
IsUint16 returns true if the FunctionCode concerns 16bit values.
func (FunctionCode) IsWriteToServer ¶
func (f FunctionCode) IsWriteToServer() bool
IsWriteToServer returns true if the FunctionCode is a write. FunctionCode 23 is both a read and write.
func (FunctionCode) MakeRequestHeader ¶
func (f FunctionCode) MakeRequestHeader(address, quantity uint16) (PDU, error)
MakeRequestHeader makes a particular PDU without any data, to be used for client side StartTransaction. The inverse functions are PDU.GetFunctionCode() .GetAddress() and .GetRequestCount().
func (FunctionCode) MaxPerPacket ¶
func (f FunctionCode) MaxPerPacket() uint16
MaxPerPacket returns the max number of values a FunctionCode can carry.
func (FunctionCode) MaxPerPacketSized ¶
func (f FunctionCode) MaxPerPacketSized(size int) uint16
MaxPerPacketSized returns the max number of values a FunctionCode can carry, if we are to further limit PDU packet size from MaxRTUSize. At least 1 (8 for bools) is returned if size is too small.
func (FunctionCode) MaxRange ¶
func (f FunctionCode) MaxRange() uint16
MaxRange is the largest address in the Modbus protocol.
func (FunctionCode) SeparateError ¶
func (f FunctionCode) SeparateError() (bool, FunctionCode)
SeparateError test if FunctionCode is an error response, and also return the version without error flag set.
func (FunctionCode) Valid ¶
func (f FunctionCode) Valid() bool
Valid test if FunctionCode is a supported function, and not an error response.
func (FunctionCode) WithError ¶
func (f FunctionCode) WithError() FunctionCode
WithError return a copy of FunctionCode with the error flag set.
type PDU ¶
type PDU []byte
PDU is the Modbus Protocol Data Unit.
func ExceptionReplyPacket ¶
func ExceptionReplyPacket(req PDU, e ExceptionCode) PDU
ExceptionReplyPacket make a PDU packet to reply to request req with ExceptionCode e.
func MakePDURequestHeaders ¶
func MakePDURequestHeaders(fc FunctionCode, address, quantity uint16, appendTO []PDU) ([]PDU, error)
MakePDURequestHeaders generates the list of PDU request headers by splitting quantity into allowed sizes. Returns an error if quantity is out of range.
func MakePDURequestHeadersSized ¶
func MakePDURequestHeadersSized(fc FunctionCode, address, quantity uint16, maxPerPacket uint16, appendTO []PDU) ([]PDU, error)
MakePDURequestHeadersSized generates the list of PDU request headers by splitting quantity into sizes of maxPerPacket or less. Returns an error if quantity is out of range.
You can use FunctionCode.MaxPerPacketSized to calculate one with the wanted byte length.
func (PDU) GetAddress ¶
GetAddress returns the starting address, If PDU is invalid, behavior is undefined (can panic).
func (PDU) GetFunctionCode ¶
func (p PDU) GetFunctionCode() FunctionCode
GetFunctionCode returns the function code.
func (PDU) GetReplyValues ¶
GetReplyValues returns the values in a read reply.
func (PDU) GetRequestCount ¶
GetRequestCount returns the number of values requested, If PDU is invalid (too short), return 0 with error.
func (PDU) GetRequestValues ¶
GetRequestValues returns the values in a write request.
func (PDU) MakeReadReply ¶
MakeReadReply produces the reply PDU based on the request PDU and read data.
func (PDU) MakeWriteReply ¶
MakeWriteReply assumes the request is a successful write, and make the associated response.
func (PDU) MakeWriteRequest ¶
MakeWriteRequest produces the request PDU based on the request PDU header and (locally) read data.
func (PDU) ValidateRequest ¶
ValidateRequest tests for errors in a received Request PDU packet. Use ToExceptionCode to get the ExceptionCode for error. Checks for errors 2 and 3 are done in GetRequestValues.
type PacketReader ¶ added in v0.2.0
PacketReader signals that this reader returns full ADU packets.
func NewRTUBidirectionalPacketReader ¶ added in v0.2.0
func NewRTUBidirectionalPacketReader(r SerialContext) PacketReader
NewRTUBidirectionalPacketReader create a Reader that attempt to read full packets that comes from either server or client.
func NewRTUPacketReader ¶
func NewRTUPacketReader(r SerialContext, isClient bool) PacketReader
NewRTUPacketReader create a Reader that attempt to read full packets.
type ProtocolHandler ¶ added in v0.2.0
type ProtocolHandler interface { // OnWrite is called on the server for a write request, // or on the client for read reply. // For write to server on server side, data is part of req. // For read from server on client side, req is the req from client, and // data is part of reply. OnWrite(req PDU, data []byte) error // OnRead is called on the server for a read request, // or on the client before write request. // For read from server on the server side, req is from client and data is // part of reply. // For write to server on the client side, req is from local action // (such as RTUClient.StartTransaction), and data will be added to req to send // to server. OnRead(req PDU) (data []byte, err error) // OnError is called on the client when it receive a well formed // error from server OnError(req PDU, errRep PDU) }
ProtocolHandler handles PDUs based on if it is a write or read from the local perspective.
type RTU ¶
type RTU []byte
RTU is the Modbus RTU Application Data Unit.
func (RTU) IsMulticast ¶
IsMulticast returns true if slaveID is the multicast address 0.
type RTUClient ¶
type RTUClient struct { SlaveID byte // contains filtered or unexported fields }
RTUClient implements Client/Master side logic for RTU over a SerialContext to be used by a ProtocolHandler.
func NewRTUClient ¶ added in v0.2.0
func NewRTUClient(com SerialContext, slaveID byte) *RTUClient
NewRTUClient create a new client communicating over SerialContext with the given slaveID as default.
func (*RTUClient) DoTransaction ¶
DoTransaction starts a transaction, and returns a channel that returns an error or nil, with the default slaveID.
DoTransaction is blocking.
For read from server, the PDU is sent as is (after been warped up in RTU) For write to server, the data part given will be ignored, and filled in by data from handler.
func (*RTUClient) GetTransactionTimeOut ¶
GetTransactionTimeOut returns the total time to wait for a transaction (server response) to time out, given the expected length of RTU packets. This function is also used internally to calculate timeout.
func (*RTUClient) Serve ¶
func (c *RTUClient) Serve(handler ProtocolHandler) error
Serve serves RTUClient handlers.
func (*RTUClient) SetServerProcessingTime ¶
SetServerProcessingTime sets the time to wait for a server response, the total wait time also includes the time needed for data transmission.
func (*RTUClient) StartTransactionToServer ¶
StartTransactionToServer starts a transaction, with a custom slaveID. errChan is required, an error is set is the transaction failed, or nil for success.
StartTransactionToServer is not blocking.
For read from server, the PDU is sent as is (after been warped up in RTU) For write to server, the data part given will be ignored, and filled in by data from handler.
type RTUServer ¶
type RTUServer struct { SlaveID byte // contains filtered or unexported fields }
RTUServer implements Server/Slave side logic for RTU over a SerialContext to be used by a ProtocolHandler.
func NewRTUServer ¶
func NewRTUServer(com SerialContext, slaveID byte) *RTUServer
NewRTUServer creates a RTU server on SerialContext listening on slaveID.
func (*RTUServer) Serve ¶
func (s *RTUServer) Serve(handler ProtocolHandler) error
Serve runs the server and only returns after unrecoverable error, such as SerialContext is closed.
type RTUTransactionStarter ¶
type RTUTransactionStarter interface {
StartTransactionToServer(slaveID byte, req PDU, errChan chan error)
}
RTUTransactionStarter is an interface implemented by RTUClient.
type SerialContext ¶
type SerialContext interface { io.ReadWriteCloser // RTUMinDelay returns the minimum required delay between packets for framing. MinDelay() time.Duration // RTUBytesDelay returns the duration it takes to send n bytes. BytesDelay(n int) time.Duration // Stats reporting. Stats() *Stats }
SerialContext is an interface implemented by SerialPort, can also be mocked for testing.
func NewSerialContext ¶
func NewSerialContext(conn io.ReadWriteCloser, baudRate int64) SerialContext
NewSerialContext creates a SerialContext from any io.ReadWriteCloser.
func NewSerialContextWithOption ¶ added in v0.9.0
func NewSerialContextWithOption(conn io.ReadWriteCloser, baudRate int64, option Option) SerialContext
type SerialContextV2 ¶ added in v0.9.0
type SerialContextV2 interface { SerialContext // PacketCutoffDuration returns the duration to force packet breaks, // with the duration it took to read current data considered. PacketCutoffDuration(n int) time.Duration }
SerialContextV2 is an superset interface of SerialContext, to support customizing CPU hiccup time.
type ServerCloser ¶ added in v0.9.0
type ServerCloser interface { Serve(handler ProtocolHandler) error io.Closer }
ServerCloser is the common interface for all Clients and Servers that use ProtocolHandlers.
type SimpleHandler ¶
type SimpleHandler struct { // ReadDiscreteInputs handles server side FC=2 ReadDiscreteInputs func(address, quantity uint16) ([]bool, error) // ReadDiscreteInputs handles client side FC=2 WriteDiscreteInputs func(address uint16, values []bool) error // ReadCoils handles client side FC=5&15, server side FC=1 ReadCoils func(address, quantity uint16) ([]bool, error) // WriteCoils handles client side FC=1, server side FC=5&15 WriteCoils func(address uint16, values []bool) error // ReadInputRegisters handles server side FC=4 ReadInputRegisters func(address, quantity uint16) ([]uint16, error) // ReadDiscreteInputs handles client side FC=4 WriteInputRegisters func(address uint16, values []uint16) error // ReadHoldingRegisters handles client side FC=6&16, server side FC=3 ReadHoldingRegisters func(address, quantity uint16) ([]uint16, error) // WriteHoldingRegisters handles client side FC=3, server side FC=6&16 WriteHoldingRegisters func(address uint16, values []uint16) error // OnErrorImp handles OnError OnErrorImp func(req PDU, errRep PDU) }
SimpleHandler implements ProtocolHandler, any nil function returns ErrFcNotSupported.
func (*SimpleHandler) OnError ¶
func (h *SimpleHandler) OnError(req PDU, errRep PDU)
OnError is called by a Server, set OnErrorImp to catch the calls.
type Stats ¶
type Stats struct { ReadPackets int64 CrcErrors int64 RemoteErrors int64 OtherErrors int64 LongReadWarnings int64 FormateWarnings int64 IDDrops int64 OtherDrops int64 }
Stats records statics on a SerialContext, must be aligned to 64 bits on 32 bit systems.
func (*Stats) TotalDrops ¶ added in v0.2.0
TotalDrops adds up all the errors for the total number of read packets dropped.
type TCPClient ¶ added in v0.9.0
type TCPClient struct { SlaveID byte // contains filtered or unexported fields }
TCPClient implements Client/Master side logic for Modbus over a TCP connection to be used by a ProtocolHandler.
func NewTCPClient ¶ added in v0.9.0
func NewTCPClient(conn io.ReadWriteCloser, slaveID byte) *TCPClient
NewTCPClient create a new client communicating over a TCP connection with the given slaveID as default.
func (*TCPClient) DoTransaction ¶ added in v0.9.0
DoTransaction starts a transaction, and returns a channel that returns an error or nil, with the default slaveID.
DoTransaction is blocking.
For read from server, the PDU is sent as is (after been warped up in RTU) For write to server, the data part given will be ignored, and filled in by data from handler.
func (*TCPClient) DoTransaction2 ¶ added in v0.9.0
DoTransaction2 is DoTransaction with a settable slaveID.
func (*TCPClient) Serve ¶ added in v0.9.0
func (c *TCPClient) Serve(handler ProtocolHandler) error
Serve serves TCPClient handlers.
func (*TCPClient) StartTransactionToServer ¶ added in v0.9.0
StartTransactionToServer starts a transaction, with a custom slaveID. errChan is required, an error is set if the transaction failed, or nil for success.
StartTransactionToServer is not blocking.
For read from server, the PDU is sent as is (after been warped up in RTU). For write to server, the data part given will be ignored, and filled in by data from handler.
type TCPServer ¶ added in v0.9.0
type TCPServer struct {
// contains filtered or unexported fields
}
TCPServer implements Server/Slave side logic for Modbus over TCP to be used by a ProtocolHandler.
func NewTCPServer ¶ added in v0.9.0
NewTCPServer runs TCP server.
func (*TCPServer) Serve ¶ added in v0.9.0
func (s *TCPServer) Serve(handler ProtocolHandler) error
Serve runs the server and only returns after a connection or data error occurred. The underling connection is always closed before this function returns.