github.com/simpleiot/simpleiot@v0.18.3/modbus/ascii.go (about)

     1  package modbus
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  )
    10  
    11  const (
    12  	asciiStart   = ':'
    13  	asciiEnd     = "\r\n"
    14  	asciiMinSize = 9
    15  	asciiMaxSize = 513
    16  )
    17  
    18  // Modbus is a type that implements modbus ascii communication.
    19  // Currently, only "sniffing" a network is implemented
    20  type Modbus struct {
    21  	io      io.ReadWriter
    22  	bufRead *bufio.Reader
    23  }
    24  
    25  // NewModbus creates a new Modbus
    26  func NewModbus(port io.ReadWriter) *Modbus {
    27  	return &Modbus{
    28  		io:      port,
    29  		bufRead: bufio.NewReader(port),
    30  	}
    31  }
    32  
    33  // Read returns an ASCII modbus packet. Blocks until
    34  // a full packet is received or error
    35  func (m *Modbus) Read() ([]byte, error) {
    36  	return m.bufRead.ReadBytes(0xA)
    37  }
    38  
    39  // ASCIIADU is a modbus protocol data unit
    40  type ASCIIADU struct {
    41  	Address      byte
    42  	FunctionCode FunctionCode
    43  	Data         []byte
    44  	LRC          byte
    45  	End          []byte // should be "\r\n"
    46  }
    47  
    48  // CheckLRC verifies the LRC is valid
    49  func (adu *ASCIIADU) CheckLRC() bool {
    50  	var sum byte
    51  	sum += adu.Address
    52  	sum += byte(adu.FunctionCode)
    53  	for _, b := range adu.Data {
    54  		sum += b
    55  	}
    56  
    57  	return byte(-int8(sum)) == adu.LRC
    58  }
    59  
    60  // DecodeFunctionData extracts the function data from the PDU
    61  func (adu *ASCIIADU) DecodeFunctionData() (ret interface{}, err error) {
    62  	switch adu.FunctionCode {
    63  	case FuncCodeWriteMultipleRegisters:
    64  		if len(adu.Data) < 5 {
    65  			err = errors.New("not enough data for Write Mult Regs")
    66  			return
    67  		}
    68  		r := FuncWriteMultipleRegisterRequest{}
    69  		r.FunctionCode = adu.FunctionCode
    70  		r.StartingAddress = uint16(adu.Data[0])<<8 | uint16(adu.Data[1])
    71  		r.RegCount = uint16(adu.Data[2])<<8 | uint16(adu.Data[3])
    72  		r.ByteCount = adu.Data[4]
    73  		if r.RegCount*2 != uint16(r.ByteCount) {
    74  			err = errors.New("Byte count does not match reg count")
    75  			return
    76  		}
    77  		regData := adu.Data[5:]
    78  		if len(regData) != int(r.ByteCount) {
    79  			err = errors.New("not enough reg data")
    80  			return
    81  		}
    82  		for i := 0; i < int(r.RegCount); i++ {
    83  			v := uint16(regData[i*2])<<8 | uint16(regData[i*2+1])
    84  			r.RegValues = append(r.RegValues, v)
    85  		}
    86  		ret = r
    87  	default:
    88  		err = fmt.Errorf("Unhandled function code %v", adu.FunctionCode)
    89  	}
    90  
    91  	return
    92  }
    93  
    94  // FuncReadHoldingRegistersRequest represents the request to read holding reg
    95  type FuncReadHoldingRegistersRequest struct {
    96  	FunctionCode    FunctionCode
    97  	StartingAddress uint16
    98  	RegCount        uint16
    99  }
   100  
   101  // FuncReadHoldingRegisterResponse response to read holding reg
   102  type FuncReadHoldingRegisterResponse struct {
   103  	FunctionCode FunctionCode
   104  	RegCount     byte
   105  	RegValues    []uint16
   106  }
   107  
   108  // FuncWriteMultipleRegisterRequest represents the request to write multiple regs
   109  type FuncWriteMultipleRegisterRequest struct {
   110  	FunctionCode    FunctionCode
   111  	StartingAddress uint16
   112  	RegCount        uint16
   113  	ByteCount       byte
   114  	RegValues       []uint16
   115  }
   116  
   117  // DecodeASCIIByte converts type ascii hex bytes to a binary
   118  // byte
   119  func DecodeASCIIByte(data []byte) (byte, []byte, error) {
   120  	if len(data) < 2 {
   121  		return 0, []byte{}, errors.New("Not enough data to decode")
   122  	}
   123  
   124  	ret := make([]byte, 1)
   125  	_, err := hex.Decode(ret, data[:2])
   126  	if err != nil {
   127  		return 0, []byte{}, err
   128  	}
   129  
   130  	return ret[0], data[2:], nil
   131  }
   132  
   133  // DecodeASCIIByteEnd converts type ascii hex bytes to a binary
   134  // byte. This function takes from the end of the slice
   135  func DecodeASCIIByteEnd(data []byte) (byte, []byte, error) {
   136  	if len(data) < 2 {
   137  		return 0, []byte{}, errors.New("Not enough data to decode")
   138  	}
   139  
   140  	ret := make([]byte, 1)
   141  	_, err := hex.Decode(ret, data[len(data)-2:])
   142  	if err != nil {
   143  		return 0, []byte{}, err
   144  	}
   145  
   146  	return ret[0], data[:len(data)-2], nil
   147  }
   148  
   149  // DecodeASCIIPDU decodes a ASCII modbus packet
   150  func DecodeASCIIPDU(data []byte) (ret ASCIIADU, err error) {
   151  	if len(data) < asciiMinSize {
   152  		err = errors.New("not enough data to decode")
   153  		return
   154  	}
   155  
   156  	if data[0] != asciiStart {
   157  		return ASCIIADU{}, errors.New("invalid start char")
   158  	}
   159  
   160  	// chop start
   161  	data = data[1:]
   162  
   163  	cnt := len(data)
   164  	ret.End = make([]byte, 2)
   165  	copy(ret.End, data[cnt-2:])
   166  
   167  	if string(ret.End) != asciiEnd {
   168  		err = fmt.Errorf("ending is not correct: %v", ret.End)
   169  		return
   170  	}
   171  
   172  	// chop end
   173  	data = data[:cnt-2]
   174  
   175  	// pop address and function code off the front end of the data
   176  	ret.Address, data, err = DecodeASCIIByte(data)
   177  	if err != nil {
   178  		return
   179  	}
   180  
   181  	var fc byte
   182  	fc, data, err = DecodeASCIIByte(data)
   183  	ret.FunctionCode = FunctionCode(fc)
   184  	if err != nil {
   185  		return
   186  	}
   187  
   188  	// pop LRC off the end of the data
   189  	ret.LRC, data, err = DecodeASCIIByteEnd(data)
   190  	if err != nil {
   191  		return
   192  	}
   193  
   194  	// what we are left with is the data payload
   195  	ret.Data = make([]byte, hex.DecodedLen(len(data)))
   196  	_, err = hex.Decode(ret.Data, data)
   197  	if err != nil {
   198  		return
   199  	}
   200  
   201  	if !ret.CheckLRC() {
   202  		err = errors.New("LRC check failed")
   203  	}
   204  
   205  	return
   206  }