modbus

package
v0.0.0-...-94bc3c9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 15, 2025 License: Apache-2.0 Imports: 14 Imported by: 0

README

Simple IoT Modbus

This Simple IoT modbus packet is a package that implements both Modbus (RTU & TCP) client and server functionality.

See this test for an example of how to use this library. Substitute the wire simulator with real serial ports. There are also standalone client and server examples.

Why?

  • really want to be able to pass in my own io.ReadWriter into these libs. New and better serial libs are continually coming out, and it seems that hardcoding serial operations in the modbus lib makes this brittle.
  • allows use of https://pkg.go.dev/github.com/simpleiot/simpleiot/respreader to do packet framing. This may not be the most efficient, but is super easy and eliminates the need to parse packets as they come in.
  • passing in io.ReadWriter allows us to easily unit test end to end communication (see end-to-end test above)
  • library in constructed in a modular fashion from lowest units (PDU) on up, so it is easy to test
  • not satisfied with the mbserver way of hooking in register operations. Seems over complicated, and unfinished.
  • want one library for both client/server. This is necessary to test everything, and there is no big reason not to do this.
  • need a library that is maintained, accepts PRs, etc.
  • could not find any good tools for testing modbus (client or server). Most are old apps for windows (I use Linux) and are difficult to use. After messing around too long in a windows VM, I decided its time for a decent command line app that does this. Seems like there should be a simple command line tool that can be made to do this and run on any OS (Go is perfect for building this type of thing).

Documentation

Overview

Package modbus contains modbus RTU/TCP client/server code.

Index

Constants

View Source
const (
	WriteCoilValueOn  uint16 = 0xff00
	WriteCoilValueOff uint16 = 0
)

define valid values for write coil

Variables

View Source
var ErrCRC = errors.New("CRC error")

ErrCRC is returned if a crc check fails

View Source
var ErrNotEnoughData = errors.New("Not enough data to calculate CRC")

ErrNotEnoughData is returned if not enough data

View Source
var ErrUnknownRegister = errors.New("unknown register")

ErrUnknownRegister is returned if a validator is added on a register that has not been added.

Functions

func CheckRtuCrc

func CheckRtuCrc(packet []byte) error

CheckRtuCrc returns error if CRC fails

func DecodeASCIIByte

func DecodeASCIIByte(data []byte) (byte, []byte, error)

DecodeASCIIByte converts type ascii hex bytes to a binary byte

func DecodeASCIIByteEnd

func DecodeASCIIByteEnd(data []byte) (byte, []byte, error)

DecodeASCIIByteEnd converts type ascii hex bytes to a binary byte. This function takes from the end of the slice

func Float32ToRegs

func Float32ToRegs(in []float32) []uint16

Float32ToRegs converts float32 values to modbus regs

func Float32ToRegsSwapWords

func Float32ToRegsSwapWords(in []float32) []uint16

Float32ToRegsSwapWords converts float32 values to modbus regs and swaps the words

func Int32ToRegs

func Int32ToRegs(in []int32) []uint16

Int32ToRegs converts int32 values to modbus regs

func Int32ToRegsSwapWords

func Int32ToRegsSwapWords(in []int32) []uint16

Int32ToRegsSwapWords converts int32 values to modbus regs

func PutUint16Array

func PutUint16Array(value ...uint16) []byte

PutUint16Array creates a sequence of uint16 data.

func RegsToFloat32

func RegsToFloat32(in []uint16) []float32

RegsToFloat32 converts modbus regs to float32 values

func RegsToFloat32SwapWords

func RegsToFloat32SwapWords(in []uint16) []float32

RegsToFloat32SwapWords converts modbus regs to float32 values

func RegsToInt16

func RegsToInt16(in []uint16) []int16

RegsToInt16 converts modbus regs to int16 values

func RegsToInt32

func RegsToInt32(in []uint16) []int32

RegsToInt32 converts modbus regs to int32 values

func RegsToInt32SwapWords

func RegsToInt32SwapWords(in []uint16) []int32

RegsToInt32SwapWords converts modbus regs to int32 values

func RegsToUint32

func RegsToUint32(in []uint16) []uint32

RegsToUint32 converts modbus regs to uint32 values

func RegsToUint32SwapWords

func RegsToUint32SwapWords(in []uint16) []uint32

RegsToUint32SwapWords converts modbus regs to uint32 values

func RtuCrc

func RtuCrc(buf []byte) uint16

RtuCrc calculates CRC for a Modbus RTU packet

func Uint16Array

func Uint16Array(data []byte) []uint16

Uint16Array unpacks 16 bit data values from a buffer (in big endian format)

func Uint32ToRegs

func Uint32ToRegs(in []uint32) []uint16

Uint32ToRegs converts uint32 values to modbus regs

func Uint32ToRegsSwapRegs

func Uint32ToRegsSwapRegs(in []uint32) []uint16

Uint32ToRegsSwapRegs converts uint32 values to modbus regs

Types

type ASCIIADU

type ASCIIADU struct {
	Address      byte
	FunctionCode FunctionCode
	Data         []byte
	LRC          byte
	End          []byte // should be "\r\n"
}

ASCIIADU is a modbus protocol data unit

func DecodeASCIIPDU

func DecodeASCIIPDU(data []byte) (ret ASCIIADU, err error)

DecodeASCIIPDU decodes a ASCII modbus packet

func (*ASCIIADU) CheckLRC

func (adu *ASCIIADU) CheckLRC() bool

CheckLRC verifies the LRC is valid

func (*ASCIIADU) DecodeFunctionData

func (adu *ASCIIADU) DecodeFunctionData() (ret interface{}, err error)

DecodeFunctionData extracts the function data from the PDU

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client defines a Modbus client (master)

func NewClient

func NewClient(transport Transport, debug int) *Client

NewClient is used to create a new modbus client port must return an entire packet for each Read(). github.com/simpleiot/simpleiot/respreader is a good way to do this.

func (*Client) Close

func (c *Client) Close() error

Close closes the client transport

func (*Client) ReadCoils

func (c *Client) ReadCoils(id byte, coil, count uint16) ([]bool, error)

ReadCoils is used to read modbus coils

func (*Client) ReadDiscreteInputs

func (c *Client) ReadDiscreteInputs(id byte, input, count uint16) ([]bool, error)

ReadDiscreteInputs is used to read modbus discrete inputs

func (*Client) ReadHoldingRegs

func (c *Client) ReadHoldingRegs(id byte, reg, count uint16) ([]uint16, error)

ReadHoldingRegs is used to read modbus coils

func (*Client) ReadInputRegs

func (c *Client) ReadInputRegs(id byte, reg, count uint16) ([]uint16, error)

ReadInputRegs is used to read modbus coils

func (*Client) SetDebugLevel

func (c *Client) SetDebugLevel(debug int)

SetDebugLevel allows you to change debug level on the fly

func (*Client) WriteSingleCoil

func (c *Client) WriteSingleCoil(id byte, coil uint16, v bool) error

WriteSingleCoil is used to read modbus coils

func (*Client) WriteSingleReg

func (c *Client) WriteSingleReg(id byte, reg, value uint16) error

WriteSingleReg writes to a single holding register

type ExceptionCode

type ExceptionCode byte

ExceptionCode represents a modbus exception code

const (
	ExcIllegalFunction              ExceptionCode = 1
	ExcIllegalAddress               ExceptionCode = 2
	ExcIllegalValue                 ExceptionCode = 3
	ExcServerDeviceFailure          ExceptionCode = 4
	ExcAcknowledge                  ExceptionCode = 5
	ExcServerDeviceBusy             ExceptionCode = 6
	ExcMemoryParityError            ExceptionCode = 8
	ExcGatewayPathUnavilable        ExceptionCode = 0x0a
	ExcGatewayTargetFailedToRespond ExceptionCode = 0x0b
)

Defined valid exception codes

func (ExceptionCode) Error

func (e ExceptionCode) Error() string

type FuncReadHoldingRegisterResponse

type FuncReadHoldingRegisterResponse struct {
	FunctionCode FunctionCode
	RegCount     byte
	RegValues    []uint16
}

FuncReadHoldingRegisterResponse response to read holding reg

type FuncReadHoldingRegistersRequest

type FuncReadHoldingRegistersRequest struct {
	FunctionCode    FunctionCode
	StartingAddress uint16
	RegCount        uint16
}

FuncReadHoldingRegistersRequest represents the request to read holding reg

type FuncWriteMultipleRegisterRequest

type FuncWriteMultipleRegisterRequest struct {
	FunctionCode    FunctionCode
	StartingAddress uint16
	RegCount        uint16
	ByteCount       byte
	RegValues       []uint16
}

FuncWriteMultipleRegisterRequest represents the request to write multiple regs

type FunctionCode

type FunctionCode byte

FunctionCode represents a modbus function code

const (
	// Bit access
	FuncCodeReadDiscreteInputs FunctionCode = 2
	FuncCodeReadCoils          FunctionCode = 1
	FuncCodeWriteSingleCoil    FunctionCode = 5
	FuncCodeWriteMultipleCoils FunctionCode = 15

	// 16-bit access
	FuncCodeReadInputRegisters         FunctionCode = 4
	FuncCodeReadHoldingRegisters       FunctionCode = 3
	FuncCodeWriteSingleRegister        FunctionCode = 6
	FuncCodeWriteMultipleRegisters     FunctionCode = 16
	FuncCodeReadWriteMultipleRegisters FunctionCode = 23
	FuncCodeMaskWriteRegister          FunctionCode = 22
	FuncCodeReadFIFOQueue              FunctionCode = 24
)

Defined valid function codes

type Modbus

type Modbus struct {
	// contains filtered or unexported fields
}

Modbus is a type that implements modbus ascii communication. Currently, only "sniffing" a network is implemented

func NewModbus

func NewModbus(port io.ReadWriter) *Modbus

NewModbus creates a new Modbus

func (*Modbus) Read

func (m *Modbus) Read() ([]byte, error)

Read returns an ASCII modbus packet. Blocks until a full packet is received or error

type PDU

type PDU struct {
	FunctionCode FunctionCode
	Data         []byte
}

PDU for Modbus packets

func ReadCoils

func ReadCoils(address uint16, count uint16) PDU

ReadCoils creates PDU to read coils

func ReadDiscreteInputs

func ReadDiscreteInputs(address uint16, count uint16) PDU

ReadDiscreteInputs creates PDU to read descrete inputs

func ReadHoldingRegs

func ReadHoldingRegs(address uint16, count uint16) PDU

ReadHoldingRegs creates a PDU to read a holding regs

func ReadInputRegs

func ReadInputRegs(address uint16, count uint16) PDU

ReadInputRegs creates a PDU to read input regs

func WriteSingleCoil

func WriteSingleCoil(address uint16, v bool) PDU

WriteSingleCoil creates PDU to read coils

func WriteSingleReg

func WriteSingleReg(address, value uint16) PDU

WriteSingleReg creates PDU to read coils

func (*PDU) ProcessRequest

func (p *PDU) ProcessRequest(regs RegProvider, changedRegs *types.ChangedRegisters) (bool, PDU, error)

ProcessRequest a modbus request. Registers are read and written through the server interface argument. This function returns any register changes, the modbus respose, and any errors

func (*PDU) RespReadBits

func (p *PDU) RespReadBits() ([]bool, error)

RespReadBits reads coils and discrete inputs from a response PDU.

func (*PDU) RespReadRegs

func (p *PDU) RespReadRegs() ([]uint16, error)

RespReadRegs reads register values from a response PDU.

func (PDU) String

func (p PDU) String() string

type RTU

type RTU struct {
	// contains filtered or unexported fields
}

RTU defines an RTU connection

func NewRTU

func NewRTU(port io.ReadWriteCloser) *RTU

NewRTU creates a new RTU transport

func (*RTU) Close

func (r *RTU) Close() error

Close closes the serial port

func (*RTU) Decode

func (r *RTU) Decode(packet []byte) (byte, PDU, error)

Decode decodes a RTU packet

func (*RTU) Encode

func (r *RTU) Encode(id byte, pdu PDU) ([]byte, error)

Encode encodes a RTU packet

func (*RTU) Read

func (r *RTU) Read(p []byte) (int, error)

func (*RTU) Type

func (r *RTU) Type() TransportType

Type returns TransportType

func (*RTU) Write

func (r *RTU) Write(p []byte) (int, error)

type Reg

type Reg struct {
	Address  uint16
	Value    uint16
	Validate func(value uint16) bool
}

Reg defines a Modbus register

type RegProvider

type RegProvider interface {
	ReadReg(address int) (uint16, error)
	WriteReg(address int, value uint16) error
	ReadInputReg(address int) (uint16, error)
	ReadDiscreteInput(num int) (bool, error)
	ReadCoil(num int) (bool, error)
	WriteCoil(num int, value bool) error
}

RegProvider is the interface for a register provider. Regs is the canonical implementation.

type Regs

type Regs struct {
	// contains filtered or unexported fields
}

Regs represents all registers in a modbus device and provides functions to read/write 16-bit and bit values. This register module assumes all register types map into one address space as described in the modbus spec (http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf) on page 6 and 7. All operations on Regs are threadsafe and protected by a mutex.

func (*Regs) AddCoil

func (r *Regs) AddCoil(num int)

AddCoil is used to add a discrete io to the register map. Note coils are aliased on top of other registers, so coil 20 would be register 1 bit 4 (16 + 4 = 20).

func (*Regs) AddReg

func (r *Regs) AddReg(address int, count int)

AddReg is used to add a modbus register to the server. the callback function is called when the reg is updated The register can be updated by word or bit operations.

func (*Regs) AddRegValueValidator

func (r *Regs) AddRegValueValidator(address int, validate func(uint16) bool) error

AddRegValueValidator is used to add a validator function to a modbus register. The validator function is called when a modbus client tries to write a value. If the value is invalid, ExcIllegalValue (modbus exception 3) is returned to the client.

func (*Regs) ReadCoil

func (r *Regs) ReadCoil(num int) (bool, error)

ReadCoil gets a coil value

func (*Regs) ReadDiscreteInput

func (r *Regs) ReadDiscreteInput(num int) (bool, error)

ReadDiscreteInput gets a discrete input

func (*Regs) ReadInputReg

func (r *Regs) ReadInputReg(address int) (uint16, error)

ReadInputReg is used to read a modbus input register

func (*Regs) ReadReg

func (r *Regs) ReadReg(address int) (uint16, error)

ReadReg is used to read a modbus holding register

func (*Regs) ReadRegFloat32

func (r *Regs) ReadRegFloat32(address int) (float32, error)

ReadRegFloat32 reads a float32 from regs

func (*Regs) ReadRegInt32

func (r *Regs) ReadRegInt32(address int) (int32, error)

ReadRegInt32 reads a int32 from regs

func (*Regs) ReadRegUint32

func (r *Regs) ReadRegUint32(address int) (uint32, error)

ReadRegUint32 reads a uint32 from regs

func (*Regs) WriteCoil

func (r *Regs) WriteCoil(num int, value bool) error

WriteCoil writes a coil value

func (*Regs) WriteReg

func (r *Regs) WriteReg(address int, value uint16) error

WriteReg is used to write a modbus register

func (*Regs) WriteRegFloat32

func (r *Regs) WriteRegFloat32(address int, value float32) error

WriteRegFloat32 writes a float32 to regs

func (*Regs) WriteRegInt32

func (r *Regs) WriteRegInt32(address int, value int32) error

WriteRegInt32 writes a int32 to regs

func (*Regs) WriteRegUint32

func (r *Regs) WriteRegUint32(address int, value uint32) error

WriteRegUint32 writes a uint32 to regs

type RtuADU

type RtuADU struct {
	PDU
	Address byte
	CRC     uint16
}

RtuADU defines an ADU for RTU packets

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server defines a server (slave) Current Server only supports Modbus RTU, but could be expanded to do ASCII and TCP.

func NewServer

func NewServer(id byte, transport Transport, regs *Regs, debug int) *Server

NewServer creates a new server instance port must return an entire packet for each Read(). github.com/simpleiot/simpleiot/respreader is a good way to do this.

func (*Server) Close

func (s *Server) Close() error

Close stops the listening channel

func (*Server) Listen

func (s *Server) Listen(errorCallback func(error),
	changesCallback func(changedRegs *types.ChangedRegisters), done func())

Listen starts the server and listens for modbus requests this function does not return unless an error occurs The listen function supports various debug levels: 1 - dump packets 9 - dump raw data

type TCP

type TCP struct {
	// contains filtered or unexported fields
}

TCP defines an TCP connection

func NewTCP

func NewTCP(sock net.Conn, timeout time.Duration, clientServer TransportClientServer) *TCP

NewTCP creates a new TCP transport

func (*TCP) Close

func (t *TCP) Close() error

Close connection

func (*TCP) Decode

func (t *TCP) Decode(packet []byte) (byte, PDU, error)

Decode decodes a TCP packet

func (*TCP) Encode

func (t *TCP) Encode(id byte, pdu PDU) ([]byte, error)

Encode encodes a TCP packet

func (*TCP) Read

func (t *TCP) Read(p []byte) (int, error)

func (*TCP) Type

func (t *TCP) Type() TransportType

Type returns TransportType

func (*TCP) Write

func (t *TCP) Write(p []byte) (int, error)

type TCPADU

type TCPADU struct {
	PDU
	Address byte
}

TCPADU defines an ADU for TCP packets

type TCPServer

type TCPServer struct {
	// contains filtered or unexported fields
}

TCPServer listens for new connections and then starts a modbus listener on the port.

func NewTCPServer

func NewTCPServer(id, maxClients int, port string, regs *Regs, debug int) (*TCPServer, error)

NewTCPServer starts a new TCP modbus server

func (*TCPServer) Close

func (ts *TCPServer) Close() error

Close stops the server and closes all connections

func (*TCPServer) InitializeServer

func (ts *TCPServer) InitializeServer(sock net.Conn, errorCallback func(error), changesCallback func(*types.ChangedRegisters))

func (*TCPServer) Listen

func (ts *TCPServer) Listen(errorCallback func(error),
	changesCallback func(changedRegs *types.ChangedRegisters), done func())

Listen starts the server and listens for modbus requests this function does not return unless an error occurs The listen function supports various debug levels: 1 - dump packets 9 - dump raw data

type Transport

type Transport interface {
	io.ReadWriteCloser
	Encode(byte, PDU) ([]byte, error)
	Decode([]byte) (byte, PDU, error)
	Type() TransportType
}

Transport defines an interface that various transports (RTU, TCP, etc) implement and can be passed to clients/servers

type TransportClientServer

type TransportClientServer string

TransportClientServer defines if transport is being used for a client or server

const (
	TransportClient TransportClientServer = "client"
	TransportServer TransportClientServer = "server"
)

define valid client server types

type TransportType

type TransportType string

TransportType defines a modbus transport type

const (
	TransportTypeTCP TransportType = "tcp"
	TransportTypeRTU TransportType = "rtu"
)

define valid transport types

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL