mbserver

package module
v0.0.0-...-9397ee4 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2018 License: MIT Imports: 11 Imported by: 1

README

Build Status Coverage Status GoDoc Software License

Golang Modbus Server (Slave)

The Golang Modbus Server (Slave) responds to the following Modbus function requests:

Bit access:

  • Read Discrete Inputs
  • Read Coils
  • Write Single Coil
  • Write Multiple Coils

16-bit acess:

  • Read Input Registers
  • Read Multiple Holding Registers
  • Write Single Holding Register
  • Write Multiple Holding Registers

TCP and serial RTU access is supported.

A new Server does not allocate any new memory for coils/discreteInputs/HoldingRegisters/InputRegistetrs the programmer has to allocate them as byte slices manually in his desired length, but because of the Modbus-Protocoll only 653356 Registers(653356*2 Bytes) can be accessed The Method Server.ListenRequests() returns a non-blocking string channel which tells what ModbusRequests the Server is faced with. Modbus requests are processed in the order they are received and will not overlap/interfere with each other.

The golang mbserver documentation.

Example Modbus TCP Server

Create a Modbus TCP Server (Slave):

package main

import (
	"log"
	"time"

	"github.com/tbrandon/mbserver"
)

func main() {
	serv := mbserver.NewServer(255) // argument for constructor determines the ModbusSlave-ID
	serv.HoldingRegisters = make([]byte, 653356*2)
	err := serv.ListenTCP("127.0.0.1:1502")
	if err != nil {
		log.Printf("%v\n", err)
	}
	defer serv.Close()

	// Wait forever
	for {
		time.Sleep(1 * time.Second)
	}
}

The server will continue to listen until killed (<ctrl>-c). Modbus typically uses port 502 (standard users require special permissions to listen on port 502). Change the port number as required. Change the address to 0.0.0.0 to listen on all network interfaces.

An example of a client writing and reading holding regsiters:

package main

import (
	"fmt"

	"github.com/goburrow/modbus"
)

func main() {
	handler := modbus.NewTCPClientHandler("localhost:1502")
	// Connect manually so that multiple requests are handled in one session
	err := handler.Connect()
	defer handler.Close()
	client := modbus.NewClient(handler)

	_, err = client.WriteMultipleRegisters(0, 3, []byte{0, 3, 0, 4, 0, 5})
	if err != nil {
		fmt.Printf("%v\n", err)
	}

	results, err := client.ReadHoldingRegisters(0, 3)
	if err != nil {
		fmt.Printf("%v\n", err)
	}
	fmt.Printf("results %v\n", results)
}

Outputs:
results [0 3 0 4 0 5]

Example Listening on Multiple TCP Ports and Serial Devices

The Golang Modbus Server can listen on multiple TCP ports and serial devices. In the following example, the Modbus server will be configured to listen on 127.0.0.1:1502, 0.0.0.0:3502, /dev/ttyUSB0 and /dev/ttyACM0

	serv := mbserver.NewServer(255)
	err := serv.ListenTCP("127.0.0.1:1502")
	if err != nil {
		log.Printf("%v\n", err)
	}

	err := serv.ListenTCP("0.0.0.0:3502")
	if err != nil {
		log.Printf("%v\n", err)
	}

	err := s.ListenRTU(&serial.Config{
		Address:  "/dev/ttyUSB0",
		BaudRate: 115200,
		DataBits: 8,
		StopBits: 1,
		Parity:   "N",
		Timeout:  10 * time.Second})
	if err != nil {
		t.Fatalf("failed to listen, got %v\n", err)
	}

	err := s.ListenRTU(&serial.Config{
		Address:  "/dev/ttyACM0",
		BaudRate: 9600,
		DataBits: 8,
		StopBits: 1,
		Parity:   "N",
		Timeout:  10 * time.Second,
		RS485: serial.RS485Config{
			Enabled: true,
			DelayRtsBeforeSend: 2 * time.Millisecond
			DelayRtsAfterSend: 3 * time.Millisecond
			RtsHighDuringSend: false,
			RtsHighAfterSend: false,
			RxDuringTx: false
			})
	if err != nil {
		t.Fatalf("failed to listen, got %v\n", err)
	}

	defer serv.Close()

Information on serial port settings.

Server Customization

RegisterFunctionHandler allows the default server functionality to be overridden for a Modbus function code.

func (s *Server) RegisterFunctionHandler(funcCode uint8, function func(*Server, Framer) ([]byte, *Exception))

Example of overriding the default ReadDiscreteInputs funtion:

serv := NewServer()

// Override ReadDiscreteInputs function.
serv.RegisterFunctionHandler(2,
    func(s *Server, frame Framer) ([]byte, *Exception) {
        register, numRegs, endRegister := frame.registerAddressAndNumber()
        // Check the request is within the allocated memory
        if endRegister > 65535 {
            return []byte{}, &IllegalDataAddress
        }
        dataSize := numRegs / 8
        if (numRegs % 8) != 0 {
            dataSize++
        }
        data := make([]byte, 1+dataSize)
        data[0] = byte(dataSize)
        for i := range s.DiscreteInputs[register:endRegister] {
            // Return all 1s, regardless of the value in the DiscreteInputs array.
            shift := uint(i) % 8
            data[1+i/8] |= byte(1 << shift)
        }
        return data, &Success
    })

// Start the server.
err := serv.ListenTCP("localhost:4321")
if err != nil {
    log.Printf("%v\n", err)
    return
}
defer serv.Close()

// Wait for the server to start
time.Sleep(1 * time.Millisecond)

// Example of a client reading from the server started above.
// Connect a client.
handler := modbus.NewTCPClientHandler("localhost:4321")
err = handler.Connect()
if err != nil {
    log.Printf("%v\n", err)
    return
}
defer handler.Close()
client := modbus.NewClient(handler)

// Read discrete inputs.
results, err := client.ReadDiscreteInputs(0, 16)
if err != nil {
    log.Printf("%v\n", err)
}

fmt.Printf("results %v\n", results)

Output:

results [255 255]

Documentation

Overview

Example

Start a Modbus server and use a client to write to and read from the serer.

// Start the server.
serv, _ := NewServer(255)
serv.HoldingRegisters = make([]byte, 500)
err := serv.ListenTCP("127.0.0.1:1502")
if err != nil {
	log.Printf("%v\n", err)
	return
}
defer serv.Close()

// Wait for the server to start
time.Sleep(1 * time.Millisecond)

// Connect a client.
handler := modbus.NewTCPClientHandler("localhost:1502")
err = handler.Connect()
if err != nil {
	log.Printf("%v\n", err)
	return
}
defer handler.Close()
client := modbus.NewClient(handler)

// Write some registers.
_, err = client.WriteMultipleRegisters(0, 3, []byte{0, 3, 0, 4, 0, 5})
if err != nil {
	log.Printf("%v\n", err)
}

// Read those registers back.
results, err := client.ReadHoldingRegisters(0, 3)
if err != nil {
	log.Printf("%v\n", err)
}
fmt.Printf("results %v\n", results)
Output:
results [0 3 0 4 0 5]

Index

Examples

Constants

View Source
const (
	ReadCoils_fc             uint8 = 1
	ReadDiscreteInput_fc     uint8 = 2
	ReadHoldingRegisters_fc  uint8 = 3
	ReadInputRegisters_fc    uint8 = 4
	WriteSingleCoil_fc       uint8 = 5
	WriteHoldingRegister_fc  uint8 = 6
	WriteMultipleCoils_fc    uint8 = 15
	WriteHoldingRegisters_fc uint8 = 16

	ReadExceptionStatus_fc        uint8 = 7
	GetCommEventCounter_fc        uint8 = 11
	GetCommEventLog_fc            uint8 = 12
	ReportSlaveId_fc              uint8 = 17
	ReadFileRecord_fc             uint8 = 20
	WriteFileRecord_fc            uint8 = 21
	MaskWriteRegister_fc          uint8 = 22
	ReadWriteMultipleRegisters_fc uint8 = 23
	ReadFifoQueue_fc              uint8 = 24
	ReadDeviceIdentification_fc   uint8 = 43
)

Variables

This section is empty.

Functions

func BytesToUint16

func BytesToUint16(bytes []byte) []uint16

BytesToUint16 converts a big endian array of bytes to an array of unit16s

func RegisterAddressAndNumber

func RegisterAddressAndNumber(frame Framer) (register int, numRegs int, endRegister int)

func RegisterAddressAndValue

func RegisterAddressAndValue(frame Framer) (int, uint16)

func SetDataWithRegisterAndNumber

func SetDataWithRegisterAndNumber(frame Framer, register uint16, number uint16)

SetDataWithRegisterAndNumber sets the RTUFrame Data byte field to hold a register and number of registers

func SetDataWithRegisterAndNumberAndBytes

func SetDataWithRegisterAndNumberAndBytes(frame Framer, register uint16, number uint16, bytes []byte)

SetDataWithRegisterAndNumberAndBytes sets the TCPFrame Data byte field to hold a register and number of registers and coil bytes

func SetDataWithRegisterAndNumberAndValues

func SetDataWithRegisterAndNumberAndValues(frame Framer, register uint16, number uint16, values []uint16)

SetDataWithRegisterAndNumberAndValues sets the TCPFrame Data byte field to hold a register and number of registers and values

func Uint16ToBytes

func Uint16ToBytes(values []uint16) []byte

Uint16ToBytes converts an array of uint16s to a big endian array of bytes

Types

type Exception

type Exception uint8

Exception codes.

var (
	// Success operation successful.
	Success Exception
	// IllegalFunction function code received in the query is not recognized or allowed by slave.
	IllegalFunction Exception = 1
	// IllegalDataAddress data address of some or all the required entities are not allowed or do not exist in slave.
	IllegalDataAddress Exception = 2
	// IllegalDataValue value is not accepted by slave.
	IllegalDataValue Exception = 3
	// SlaveDeviceFailure Unrecoverable error occurred while slave was attempting to perform requested action.
	SlaveDeviceFailure Exception = 4
	// AcknowledgeSlave has accepted request and is processing it, but a long duration of time is required. This response is returned to prevent a timeout error from occurring in the master. Master can next issue a Poll Program Complete message to determine whether processing is completed.
	AcknowledgeSlave Exception = 5
	// SlaveDeviceBusy is engaged in processing a long-duration command. Master should retry later.
	SlaveDeviceBusy Exception = 6
	// NegativeAcknowledge Slave cannot perform the programming functions. Master should request diagnostic or error information from slave.
	NegativeAcknowledge Exception = 7
	// MemoryParityError Slave detected a parity error in memory. Master can retry the request, but service may be required on the slave device.
	MemoryParityError Exception = 8
	// GatewayPathUnavailable Specialized for Modbus gateways. Indicates a misconfigured gateway.
	GatewayPathUnavailable Exception = 10
	// GatewayTargetDeviceFailedtoRespond Specialized for Modbus gateways. Sent when slave fails to respond.
	GatewayTargetDeviceFailedtoRespond Exception = 11
)

func GetException

func GetException(frame Framer) (exception Exception)

GetException retunrns the Modbus exception or Success (indicating not exception).

func ReadCoils

func ReadCoils(s *Server, frame Framer) ([]byte, *Exception)

ReadCoils function 1, reads coils from internal memory.

func ReadDiscreteInputs

func ReadDiscreteInputs(s *Server, frame Framer) ([]byte, *Exception)

ReadDiscreteInputs function 2, reads discrete inputs from internal memory.

func ReadHoldingRegisters

func ReadHoldingRegisters(s *Server, frame Framer) ([]byte, *Exception)

ReadHoldingRegisters function 3, reads holding registers from internal memory.

func ReadInputRegisters

func ReadInputRegisters(s *Server, frame Framer) ([]byte, *Exception)

ReadInputRegisters function 4, reads input registers from internal memory.

func WriteHoldingRegister

func WriteHoldingRegister(s *Server, frame Framer) ([]byte, *Exception)

WriteHoldingRegister function 6, write a holding register to internal memory.

func WriteHoldingRegisters

func WriteHoldingRegisters(s *Server, frame Framer) ([]byte, *Exception)

WriteHoldingRegisters function 16, writes holding registers to internal memory.

func WriteMultipleCoils

func WriteMultipleCoils(s *Server, frame Framer) ([]byte, *Exception)

WriteMultipleCoils function 15, writes holding registers to internal memory.

func WriteSingleCoil

func WriteSingleCoil(s *Server, frame Framer) ([]byte, *Exception)

WriteSingleCoil function 5, write a coil to internal memory.

func (Exception) Error

func (e Exception) Error() string

func (Exception) String

func (e Exception) String() string

type Framer

type Framer interface {
	Bytes() []byte
	Copy() Framer
	GetData() []byte
	GetFunction() uint8
	SetException(exception *Exception)
	SetData(data []byte)
}

Framer is the interface that wraps Modbus frames.

type RTUFrame

type RTUFrame struct {
	Address  uint8
	Function uint8
	Data     []byte
	CRC      uint16
}

RTUFrame is the Modbus RTU frame.

func NewRTUFrame

func NewRTUFrame(packet []byte) (*RTUFrame, error)

NewRTUFrame converts a packet to a Modbus RTU frame.

func (*RTUFrame) Bytes

func (frame *RTUFrame) Bytes() []byte

Bytes returns the Modbus byte stream based on the RTUFrame fields

func (*RTUFrame) Copy

func (frame *RTUFrame) Copy() Framer

Copy the RTUFrame.

func (*RTUFrame) GetData

func (frame *RTUFrame) GetData() []byte

GetData returns the RTUFrame Data byte field.

func (*RTUFrame) GetFunction

func (frame *RTUFrame) GetFunction() uint8

GetFunction returns the Modbus function code.

func (*RTUFrame) SetData

func (frame *RTUFrame) SetData(data []byte)

SetData sets the RTUFrame Data byte field and updates the frame length accordingly.

func (*RTUFrame) SetException

func (frame *RTUFrame) SetException(exception *Exception)

SetException sets the Modbus exception code in the frame.

type Request

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

type Server

type Server struct {
	// Debug enables more verbose messaging.
	Debug bool

	DiscreteInputs   []byte
	Coils            []byte
	HoldingRegisters []byte
	InputRegisters   []byte
	// contains filtered or unexported fields
}

Server is a Modbus slave with allocated memory for discrete inputs, coils, etc.

func NewServer

func NewServer(id uint8) (*Server, error)

could improve the constructor to make it clearer to use

func (*Server) Close

func (s *Server) Close()

Close stops listening to TCP/IP ports and closes serial ports.

func (*Server) ListenRTU

func (s *Server) ListenRTU(serialConfig *serial.Config) (err error)

func (*Server) ListenRequests

func (s *Server) ListenRequests() chan string

func (*Server) ListenTCP

func (s *Server) ListenTCP(addressPort string) (err error)

ListenTCP starts the Modbus server listening on "address:port".

func (*Server) RegisterFunctionHandler

func (s *Server) RegisterFunctionHandler(funcCode uint8, function func(*Server, Framer) ([]byte, *Exception))

RegisterFunctionHandler override the default behavior for a given Modbus function.

Example

Override the default ReadDiscreteInputs funtion.

serv, _ := NewServer(255)
serv.DiscreteInputs = make([]byte, 500)

// Override ReadDiscreteInputs function.
serv.RegisterFunctionHandler(2,
	func(s *Server, frame Framer) ([]byte, *Exception) {
		register, numRegs, endRegister := RegisterAddressAndNumber(frame)
		// Check the request is within the allocated memory
		if endRegister > 65535 {
			return []byte{}, &IllegalDataAddress
		}
		dataSize := numRegs / 8
		if (numRegs % 8) != 0 {
			dataSize++
		}
		data := make([]byte, 1+dataSize)
		data[0] = byte(dataSize)
		for i := range s.DiscreteInputs[register:endRegister] {
			// Return all 1s, regardless of the value in the DiscreteInputs array.
			shift := uint(i) % 8
			data[1+i/8] |= byte(1 << shift)
		}
		return data, &Success
	})

// Start the server.
err := serv.ListenTCP("localhost:4321")
if err != nil {
	log.Printf("%v\n", err)
	return
}
defer serv.Close()

// Wait for the server to start
time.Sleep(1 * time.Millisecond)

// Connect a client.
handler := modbus.NewTCPClientHandler("localhost:4321")
err = handler.Connect()
if err != nil {
	log.Printf("%v\n", err)
	return
}
defer handler.Close()
client := modbus.NewClient(handler)

// Read discrete inputs.
results, err := client.ReadDiscreteInputs(0, 16)
if err != nil {
	log.Printf("%v\n", err)
}

fmt.Printf("results %v\n", results)
Output:
results [255 255]

type TCPFrame

type TCPFrame struct {
	TransactionIdentifier uint16
	ProtocolIdentifier    uint16
	Length                uint16
	Device                uint8
	Function              uint8
	Data                  []byte
}

TCPFrame is the Modbus TCP frame.

func NewTCPFrame

func NewTCPFrame(packet []byte) (*TCPFrame, error)

NewTCPFrame converts a packet to a Modbus TCP frame.

func (*TCPFrame) Bytes

func (frame *TCPFrame) Bytes() []byte

Bytes returns the Modbus byte stream based on the TCPFrame fields

func (*TCPFrame) Copy

func (frame *TCPFrame) Copy() Framer

Copy the TCPFrame.

func (*TCPFrame) GetData

func (frame *TCPFrame) GetData() []byte

GetData returns the TCPFrame Data byte field.

func (*TCPFrame) GetFunction

func (frame *TCPFrame) GetFunction() uint8

GetFunction returns the Modbus function code.

func (*TCPFrame) SetData

func (frame *TCPFrame) SetData(data []byte)

SetData sets the TCPFrame Data byte field and updates the frame length accordingly.

func (*TCPFrame) SetException

func (frame *TCPFrame) SetException(exception *Exception)

SetException sets the Modbus exception code in the frame.

Jump to

Keyboard shortcuts

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