github.com/simpleiot/simpleiot@v0.18.3/modbus/pdu.go (about) 1 package modbus 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 8 "github.com/simpleiot/simpleiot/test" 9 ) 10 11 // PDU for Modbus packets 12 type PDU struct { 13 FunctionCode FunctionCode 14 Data []byte 15 } 16 17 func (p PDU) String() string { 18 return fmt.Sprintf("PDU: %v: %v", p.FunctionCode, 19 test.HexDump(p.Data)) 20 } 21 22 // handleError translates an error into a PDU, if possible. 23 func (p *PDU) handleError(err error) (bool, PDU, error) { 24 if err, ok := err.(ExceptionCode); ok { 25 resp := PDU{} 26 resp.FunctionCode = p.FunctionCode | 0x80 27 resp.Data = []byte{byte(err)} 28 return false, resp, nil 29 } 30 // TODO: Wrap the underlying error? 31 return p.handleError(ExcServerDeviceFailure) 32 } 33 34 // ProcessRequest a modbus request. Registers are read and written 35 // through the server interface argument. 36 // This function returns any register changes, the modbus respose, 37 // and any errors 38 func (p *PDU) ProcessRequest(regs RegProvider) (bool, PDU, error) { 39 regsChanged := false 40 resp := PDU{} 41 resp.FunctionCode = p.FunctionCode 42 43 minPacketLen := minRequestLen[p.FunctionCode] 44 45 if len(p.Data) < minPacketLen-1 { 46 return false, PDU{}, fmt.Errorf("not enough data for function code %v, expected %v, got %v", p.FunctionCode, minPacketLen, len(p.Data)) 47 } 48 49 switch p.FunctionCode { 50 case FuncCodeReadCoils, FuncCodeReadDiscreteInputs: 51 address := binary.BigEndian.Uint16(p.Data[:2]) 52 count := binary.BigEndian.Uint16(p.Data[2:4]) 53 bytes := byte((count + 7) / 8) 54 resp.Data = make([]byte, 1+bytes) 55 resp.Data[0] = bytes 56 var read = regs.ReadCoil 57 if p.FunctionCode == FuncCodeReadDiscreteInputs { 58 read = regs.ReadDiscreteInput 59 } 60 for i := 0; i < int(count); i++ { 61 v, err := read(int(address) + i) 62 if err != nil { 63 return p.handleError(err) 64 } 65 if v { 66 resp.Data[1+i/8] |= 1 << (i % 8) 67 } 68 } 69 case FuncCodeReadHoldingRegisters, FuncCodeReadInputRegisters: 70 address := binary.BigEndian.Uint16(p.Data[:2]) 71 count := binary.BigEndian.Uint16(p.Data[2:4]) 72 73 resp.Data = make([]byte, 1+2*count) 74 resp.Data[0] = uint8(count * 2) 75 var read = regs.ReadReg 76 if p.FunctionCode == FuncCodeReadInputRegisters { 77 read = regs.ReadInputReg 78 } 79 for i := 0; i < int(count); i++ { 80 v, err := read(int(address) + i) 81 if err != nil { 82 return p.handleError(err) 83 } 84 85 binary.BigEndian.PutUint16(resp.Data[1+i*2:], v) 86 87 } 88 89 case FuncCodeWriteSingleCoil: 90 address := binary.BigEndian.Uint16(p.Data[:2]) 91 v := binary.BigEndian.Uint16(p.Data[2:4]) 92 93 vBool := false 94 switch v { 95 case WriteCoilValueOff: 96 case WriteCoilValueOn: 97 vBool = true 98 default: 99 return p.handleError(ExcIllegalValue) 100 } 101 102 err := regs.WriteCoil(int(address), vBool) 103 if err != nil { 104 return p.handleError(err) 105 } 106 107 regsChanged = true 108 resp.Data = p.Data 109 110 case FuncCodeWriteMultipleCoils: 111 address := binary.BigEndian.Uint16(p.Data[:2]) 112 quantity := binary.BigEndian.Uint16(p.Data[2:4]) 113 if len(p.Data) != 5+((int(quantity)+7)/8) { 114 return p.handleError(ExcIllegalValue) 115 } 116 for i := 0; i < int(quantity); i++ { 117 value := (p.Data[5+i/8]>>(i%8))&1 == 1 118 if err := regs.WriteCoil(int(address)+i, value); err != nil { 119 return p.handleError(err) 120 } 121 } 122 resp.Data = make([]byte, 4) 123 binary.BigEndian.PutUint16(resp.Data[:2], address) 124 binary.BigEndian.PutUint16(resp.Data[2:4], quantity) 125 regsChanged = true 126 127 case FuncCodeWriteSingleRegister: 128 address := binary.BigEndian.Uint16(p.Data[:2]) 129 v := binary.BigEndian.Uint16(p.Data[2:4]) 130 131 err := regs.WriteReg(int(address), v) 132 if err != nil { 133 return p.handleError(err) 134 } 135 136 resp = *p 137 regsChanged = true 138 139 case FuncCodeWriteMultipleRegisters: 140 address := binary.BigEndian.Uint16(p.Data[:2]) 141 quantity := binary.BigEndian.Uint16(p.Data[2:4]) 142 if len(p.Data) != 5+(int(quantity)*2) { 143 return p.handleError(ExcIllegalValue) 144 } 145 for i := 0; i < int(quantity); i++ { 146 value := binary.BigEndian.Uint16(p.Data[5+i*2 : 5+i*2+2]) 147 if err := regs.WriteReg(int(address)+i, value); err != nil { 148 return p.handleError(err) 149 } 150 } 151 resp.Data = make([]byte, 4) 152 binary.BigEndian.PutUint16(resp.Data[:2], address) 153 binary.BigEndian.PutUint16(resp.Data[2:4], quantity) 154 regsChanged = true 155 156 default: 157 return p.handleError(ExcIllegalFunction) 158 } 159 160 return regsChanged, resp, nil 161 } 162 163 // RespReadBits reads coils and discrete inputs from a 164 // response PDU. 165 func (p *PDU) RespReadBits() ([]bool, error) { 166 if len(p.Data) < 2 { 167 return []bool{}, errors.New("not enough data") 168 } 169 switch p.FunctionCode { 170 case FuncCodeReadCoils, FuncCodeReadDiscreteInputs: 171 // ok 172 default: 173 return []bool{}, errors.New("invalid function code to read bits") 174 } 175 176 count := p.Data[0] 177 ret := make([]bool, count) 178 byteIndex := 0 179 bitIndex := uint(0) 180 181 for i := byte(0); i < count; i++ { 182 ret[i] = ((p.Data[byteIndex+1] >> bitIndex) & 0x1) == 0x1 183 bitIndex++ 184 if bitIndex >= 8 { 185 byteIndex++ 186 bitIndex = 0 187 } 188 } 189 190 return ret, nil 191 } 192 193 // RespReadRegs reads register values from a 194 // response PDU. 195 func (p *PDU) RespReadRegs() ([]uint16, error) { 196 if len(p.Data) < 2 { 197 return []uint16{}, errors.New("not enough data") 198 } 199 switch p.FunctionCode { 200 case FuncCodeReadHoldingRegisters, FuncCodeReadInputRegisters: 201 // ok 202 default: 203 return []uint16{}, errors.New("invalid function code to read regs") 204 } 205 206 count := p.Data[0] / 2 207 208 if len(p.Data) < 1+int(count)*2 { 209 return []uint16{}, errors.New("RespReadRegs not enough data") 210 } 211 212 ret := make([]uint16, count) 213 214 for i := 0; i < int(count); i++ { 215 ret[i] = binary.BigEndian.Uint16(p.Data[1+i*2 : 1+i*2+2]) 216 } 217 218 return ret, nil 219 } 220 221 // Add address units below are the packet address, typically drop 222 // first digit from register and subtract 1 223 224 // ReadDiscreteInputs creates PDU to read descrete inputs 225 func ReadDiscreteInputs(address uint16, count uint16) PDU { 226 return PDU{ 227 FunctionCode: FuncCodeReadDiscreteInputs, 228 Data: PutUint16Array(address, count), 229 } 230 } 231 232 // ReadCoils creates PDU to read coils 233 func ReadCoils(address uint16, count uint16) PDU { 234 return PDU{ 235 FunctionCode: FuncCodeReadCoils, 236 Data: PutUint16Array(address, count), 237 } 238 } 239 240 // WriteSingleCoil creates PDU to read coils 241 func WriteSingleCoil(address uint16, v bool) PDU { 242 value := WriteCoilValueOff 243 if v { 244 value = WriteCoilValueOn 245 } 246 247 return PDU{ 248 FunctionCode: FuncCodeWriteSingleCoil, 249 Data: PutUint16Array(address, value), 250 } 251 } 252 253 // WriteSingleReg creates PDU to read coils 254 func WriteSingleReg(address, value uint16) PDU { 255 return PDU{ 256 FunctionCode: FuncCodeWriteSingleRegister, 257 Data: PutUint16Array(address, value), 258 } 259 } 260 261 // ReadHoldingRegs creates a PDU to read a holding regs 262 func ReadHoldingRegs(address uint16, count uint16) PDU { 263 return PDU{ 264 FunctionCode: FuncCodeReadHoldingRegisters, 265 Data: PutUint16Array(address, count), 266 } 267 } 268 269 // ReadInputRegs creates a PDU to read input regs 270 func ReadInputRegs(address uint16, count uint16) PDU { 271 return PDU{ 272 FunctionCode: FuncCodeReadInputRegisters, 273 Data: PutUint16Array(address, count), 274 } 275 }