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  }