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 }