| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- // Copyright 2014 Quoc-Viet Nguyen. All rights reserved.
- // This software may be modified and distributed under the terms
- // of the BSD license. See the LICENSE file for details.
- package modbus
- import (
- "encoding/binary"
- "fmt"
- )
- // ClientHandler is the interface that groups the Packager and Transporter methods.
- type ClientHandler interface {
- Packager
- Transporter
- }
- type client struct {
- packager Packager
- transporter Transporter
- //发送后存储的数据
- aduRequest []byte
- quantity uint16
- address uint16
- functionCode byte
- }
- // NewClient creates a new modbus client with given backend handler.
- func NewClient(handler ClientHandler) Client {
- return &client{packager: handler, transporter: handler}
- }
- // NewClient2 creates a new modbus client with given backend packager and transporter.
- func NewClient2(packager Packager, transporter Transporter) Client {
- return &client{packager: packager, transporter: transporter}
- }
- // Request:
- //
- // Function code : 1 byte (0x01)
- // Starting address : 2 bytes
- // Quantity of coils : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x01)
- // Byte count : 1 byte
- // Coil status : N* bytes (=N or N+1)
- func (mb *client) ReadCoils(address, quantity uint16) (results []byte, err error) {
- if quantity < 1 || quantity > 2000 {
- err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 2000)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeReadCoils,
- Data: dataBlock(address, quantity),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- count := int(response.Data[0])
- length := len(response.Data) - 1
- if count != length {
- err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
- return
- }
- results = response.Data[1:]
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x02)
- // Starting address : 2 bytes
- // Quantity of inputs : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x02)
- // Byte count : 1 byte
- // Input status : N* bytes (=N or N+1)
- func (mb *client) ReadDiscreteInputs(address, quantity uint16) (results []byte, err error) {
- if quantity < 1 || quantity > 2000 {
- err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 2000)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeReadDiscreteInputs,
- Data: dataBlock(address, quantity),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- count := int(response.Data[0])
- length := len(response.Data) - 1
- if count != length {
- err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
- return
- }
- results = response.Data[1:]
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x03)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x03)
- // Byte count : 1 byte
- // Register value : Nx2 bytes
- func (mb *client) PackReadHoldingRegisters(address, quantity uint16) (results []byte, err error) {
- if quantity < 1 || quantity > 125 {
- err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeReadHoldingRegisters,
- Data: dataBlock(address, quantity),
- }
- response, err := mb.pack_post(&request)
- if err != nil {
- return
- }
- //存储要发送的字节数组,用于接收数据时验证
- mb.aduRequest = response
- mb.quantity = quantity
- mb.address = address
- mb.functionCode = request.FunctionCode
- results = response
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x10)
- // Starting address : 2 bytes
- // Quantity of outputs : 2 bytes
- // Byte count : 1 byte
- // Registers value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x10)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- func (mb *client) PackWriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error) {
- if quantity < 1 || quantity > 123 {
- err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 123)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeWriteMultipleRegisters,
- Data: dataBlockSuffix(value, address, quantity),
- }
- response, err := mb.pack_post(&request)
- if err != nil {
- return
- }
- //存储要发送的字节数组,用于接收数据时验证
- mb.aduRequest = response
- mb.quantity = quantity
- mb.address = address
- mb.functionCode = request.FunctionCode
- results = response
- return
- }
- func (mb *client) DePackSetRegs(aduResponse []byte) (results []byte, err error) {
- response, err := mb.depack_rcv(mb.aduRequest, aduResponse)
- if err != nil {
- return
- }
- // Fixed response length
- if len(response.Data) != 4 {
- err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
- return
- }
- respValue := binary.BigEndian.Uint16(response.Data)
- if mb.address != respValue {
- err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, mb.address)
- return
- }
- results = response.Data[2:]
- respValue = binary.BigEndian.Uint16(results)
- if mb.quantity != respValue {
- err = fmt.Errorf("modbus: response quantity '%v' does not match request '%v'", respValue, mb.quantity)
- return
- }
- return
- }
- func (mb *client) DePackRegisters(aduResponse []byte) (results []byte, err error) {
- response, err := mb.depack_rcv(mb.aduRequest, aduResponse)
- if err != nil {
- return
- }
- count := int(response.Data[0])
- length := len(response.Data) - 1
- if count != length {
- err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
- return
- }
- results = response.Data[1:]
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x03)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x03)
- // Byte count : 1 byte
- // Register value : Nx2 bytes
- func (mb *client) ReadHoldingRegisters(address, quantity uint16) (results []byte, err error) {
- if quantity < 1 || quantity > 125 {
- err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeReadHoldingRegisters,
- Data: dataBlock(address, quantity),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- count := int(response.Data[0])
- length := len(response.Data) - 1
- if count != length {
- err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
- return
- }
- results = response.Data[1:]
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x04)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x04)
- // Byte count : 1 byte
- // Input registers : N bytes
- func (mb *client) ReadInputRegisters(address, quantity uint16) (results []byte, err error) {
- if quantity < 1 || quantity > 125 {
- err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 125)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeReadInputRegisters,
- Data: dataBlock(address, quantity),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- count := int(response.Data[0])
- length := len(response.Data) - 1
- if count != length {
- err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", length, count)
- return
- }
- results = response.Data[1:]
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x05)
- // Output address : 2 bytes
- // Output value : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x05)
- // Output address : 2 bytes
- // Output value : 2 bytes
- func (mb *client) WriteSingleCoil(address, value uint16) (results []byte, err error) {
- // The requested ON/OFF state can only be 0xFF00 and 0x0000
- if value != 0xFF00 && value != 0x0000 {
- err = fmt.Errorf("modbus: state '%v' must be either 0xFF00 (ON) or 0x0000 (OFF)", value)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeWriteSingleCoil,
- Data: dataBlock(address, value),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- // Fixed response length
- if len(response.Data) != 4 {
- err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
- return
- }
- respValue := binary.BigEndian.Uint16(response.Data)
- if address != respValue {
- err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
- return
- }
- results = response.Data[2:]
- respValue = binary.BigEndian.Uint16(results)
- if value != respValue {
- err = fmt.Errorf("modbus: response value '%v' does not match request '%v'", respValue, value)
- return
- }
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x06)
- // Register address : 2 bytes
- // Register value : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x06)
- // Register address : 2 bytes
- // Register value : 2 bytes
- func (mb *client) WriteSingleRegister(address, value uint16) (results []byte, err error) {
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeWriteSingleRegister,
- Data: dataBlock(address, value),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- // Fixed response length
- if len(response.Data) != 4 {
- err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
- return
- }
- respValue := binary.BigEndian.Uint16(response.Data)
- if address != respValue {
- err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
- return
- }
- results = response.Data[2:]
- respValue = binary.BigEndian.Uint16(results)
- if value != respValue {
- err = fmt.Errorf("modbus: response value '%v' does not match request '%v'", respValue, value)
- return
- }
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x0F)
- // Starting address : 2 bytes
- // Quantity of outputs : 2 bytes
- // Byte count : 1 byte
- // Outputs value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x0F)
- // Starting address : 2 bytes
- // Quantity of outputs : 2 bytes
- func (mb *client) WriteMultipleCoils(address, quantity uint16, value []byte) (results []byte, err error) {
- if quantity < 1 || quantity > 1968 {
- err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 1968)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeWriteMultipleCoils,
- Data: dataBlockSuffix(value, address, quantity),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- // Fixed response length
- if len(response.Data) != 4 {
- err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
- return
- }
- respValue := binary.BigEndian.Uint16(response.Data)
- if address != respValue {
- err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
- return
- }
- results = response.Data[2:]
- respValue = binary.BigEndian.Uint16(results)
- if quantity != respValue {
- err = fmt.Errorf("modbus: response quantity '%v' does not match request '%v'", respValue, quantity)
- return
- }
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x10)
- // Starting address : 2 bytes
- // Quantity of outputs : 2 bytes
- // Byte count : 1 byte
- // Registers value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x10)
- // Starting address : 2 bytes
- // Quantity of registers : 2 bytes
- func (mb *client) WriteMultipleRegisters(address, quantity uint16, value []byte) (results []byte, err error) {
- if quantity < 1 || quantity > 123 {
- err = fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v',", quantity, 1, 123)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeWriteMultipleRegisters,
- Data: dataBlockSuffix(value, address, quantity),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- // Fixed response length
- if len(response.Data) != 4 {
- err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 4)
- return
- }
- respValue := binary.BigEndian.Uint16(response.Data)
- if address != respValue {
- err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
- return
- }
- results = response.Data[2:]
- respValue = binary.BigEndian.Uint16(results)
- if quantity != respValue {
- err = fmt.Errorf("modbus: response quantity '%v' does not match request '%v'", respValue, quantity)
- return
- }
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x16)
- // Reference address : 2 bytes
- // AND-mask : 2 bytes
- // OR-mask : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x16)
- // Reference address : 2 bytes
- // AND-mask : 2 bytes
- // OR-mask : 2 bytes
- func (mb *client) MaskWriteRegister(address, andMask, orMask uint16) (results []byte, err error) {
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeMaskWriteRegister,
- Data: dataBlock(address, andMask, orMask),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- // Fixed response length
- if len(response.Data) != 6 {
- err = fmt.Errorf("modbus: response data size '%v' does not match expected '%v'", len(response.Data), 6)
- return
- }
- respValue := binary.BigEndian.Uint16(response.Data)
- if address != respValue {
- err = fmt.Errorf("modbus: response address '%v' does not match request '%v'", respValue, address)
- return
- }
- respValue = binary.BigEndian.Uint16(response.Data[2:])
- if andMask != respValue {
- err = fmt.Errorf("modbus: response AND-mask '%v' does not match request '%v'", respValue, andMask)
- return
- }
- respValue = binary.BigEndian.Uint16(response.Data[4:])
- if orMask != respValue {
- err = fmt.Errorf("modbus: response OR-mask '%v' does not match request '%v'", respValue, orMask)
- return
- }
- results = response.Data[2:]
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x17)
- // Read starting address : 2 bytes
- // Quantity to read : 2 bytes
- // Write starting address: 2 bytes
- // Quantity to write : 2 bytes
- // Write byte count : 1 byte
- // Write registers value : N* bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x17)
- // Byte count : 1 byte
- // Read registers value : Nx2 bytes
- func (mb *client) ReadWriteMultipleRegisters(readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) (results []byte, err error) {
- if readQuantity < 1 || readQuantity > 125 {
- err = fmt.Errorf("modbus: quantity to read '%v' must be between '%v' and '%v',", readQuantity, 1, 125)
- return
- }
- if writeQuantity < 1 || writeQuantity > 121 {
- err = fmt.Errorf("modbus: quantity to write '%v' must be between '%v' and '%v',", writeQuantity, 1, 121)
- return
- }
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeReadWriteMultipleRegisters,
- Data: dataBlockSuffix(value, readAddress, readQuantity, writeAddress, writeQuantity),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- count := int(response.Data[0])
- if count != (len(response.Data) - 1) {
- err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", len(response.Data)-1, count)
- return
- }
- results = response.Data[1:]
- return
- }
- // Request:
- //
- // Function code : 1 byte (0x18)
- // FIFO pointer address : 2 bytes
- //
- // Response:
- //
- // Function code : 1 byte (0x18)
- // Byte count : 2 bytes
- // FIFO count : 2 bytes
- // FIFO count : 2 bytes (<=31)
- // FIFO value register : Nx2 bytes
- func (mb *client) ReadFIFOQueue(address uint16) (results []byte, err error) {
- request := ProtocolDataUnit{
- FunctionCode: FuncCodeReadFIFOQueue,
- Data: dataBlock(address),
- }
- response, err := mb.send(&request)
- if err != nil {
- return
- }
- if len(response.Data) < 4 {
- err = fmt.Errorf("modbus: response data size '%v' is less than expected '%v'", len(response.Data), 4)
- return
- }
- count := int(binary.BigEndian.Uint16(response.Data))
- if count != (len(response.Data) - 1) {
- err = fmt.Errorf("modbus: response data size '%v' does not match count '%v'", len(response.Data)-1, count)
- return
- }
- count = int(binary.BigEndian.Uint16(response.Data[2:]))
- if count > 31 {
- err = fmt.Errorf("modbus: fifo count '%v' is greater than expected '%v'", count, 31)
- return
- }
- results = response.Data[4:]
- return
- }
- // Helpers
- func (mb *client) pack_post(request *ProtocolDataUnit) (aduRequest []byte, err error) {
- aduRequest, err = mb.packager.Encode(request)
- return
- }
- func (mb *client) depack_rcv(aduRequest, aduResponse []byte) (response *ProtocolDataUnit, err error) {
- //var functionCode byte
- if err = mb.packager.Verify(aduRequest, aduResponse); err != nil {
- return
- }
- //fmt.Println("depack_rcv:", aduResponse)
- response, err = mb.packager.Decode(aduResponse)
- if err != nil {
- return
- }
- //functionCode = aduRequest[1]
- // Check correct function code returned (exception)
- if response.FunctionCode != mb.functionCode {
- err = responseError(response)
- return
- }
- if response.Data == nil || len(response.Data) == 0 {
- // Empty response
- err = fmt.Errorf("modbus: response data is empty")
- return
- }
- return
- }
- // send sends request and checks possible exception in the response.
- func (mb *client) send(request *ProtocolDataUnit) (response *ProtocolDataUnit, err error) {
- aduRequest, err := mb.packager.Encode(request)
- if err != nil {
- return
- }
- aduResponse, err := mb.transporter.Send(aduRequest)
- if err != nil {
- return
- }
- if err = mb.packager.Verify(aduRequest, aduResponse); err != nil {
- return
- }
- response, err = mb.packager.Decode(aduResponse)
- if err != nil {
- return
- }
- // Check correct function code returned (exception)
- if response.FunctionCode != request.FunctionCode {
- err = responseError(response)
- return
- }
- if response.Data == nil || len(response.Data) == 0 {
- // Empty response
- err = fmt.Errorf("modbus: response data is empty")
- return
- }
- return
- }
- // dataBlock creates a sequence of uint16 data.
- func dataBlock(value ...uint16) []byte {
- data := make([]byte, 2*len(value))
- for i, v := range value {
- binary.BigEndian.PutUint16(data[i*2:], v)
- }
- return data
- }
- // dataBlockSuffix creates a sequence of uint16 data and append the suffix plus its length.
- func dataBlockSuffix(suffix []byte, value ...uint16) []byte {
- length := 2 * len(value)
- data := make([]byte, length+1+len(suffix))
- for i, v := range value {
- binary.BigEndian.PutUint16(data[i*2:], v)
- }
- data[length] = uint8(len(suffix))
- copy(data[length+1:], suffix)
- return data
- }
- func responseError(response *ProtocolDataUnit) error {
- mbError := &ModbusError{FunctionCode: response.FunctionCode}
- if response.Data != nil && len(response.Data) > 0 {
- mbError.ExceptionCode = response.Data[0]
- }
- return mbError
- }
|