Skip to content

Commit

Permalink
Merge pull request #64 from grid-x/feat/cli_improve_modbus_write_feat
Browse files Browse the repository at this point in the history
Add support for common register encoding/decoding orders
  • Loading branch information
dammarco authored May 11, 2023
2 parents 7112c6d + 44180bf commit e90d491
Show file tree
Hide file tree
Showing 3 changed files with 422 additions and 81 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ test:
diagslave -m tcp -p 5020 & diagslave -m enc -p 5021 & go test -run TCP -v $(shell glide nv)
socat -d -d pty,raw,echo=0 pty,raw,echo=0 & diagslave -m rtu /dev/pts/1 & go test -run RTU -v $(shell glide nv)
socat -d -d pty,raw,echo=0 pty,raw,echo=0 & diagslave -m ascii /dev/pts/3 & go test -run ASCII -v $(shell glide nv)
go test -v -count=1 github.com/grid-x/modbus/cmd/modbus-cli

.PHONY: lint
lint:
Expand Down
217 changes: 136 additions & 81 deletions cmd/modbus-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@ func main() {
flag.BoolVar(&opt.rtu.rs485.rxDuringTx, "rs485-rxDuringTx", false, "Allow bidirectional rx during tx")

var (
register = flag.Int("register", -1, "")
fnCode = flag.Int("fn-code", 0x03, "fn")
quantity = flag.Int("quantity", 2, "register quantity, length in bytes")
ignoreCRCError = flag.Bool("ignore-crc", false, "ignore crc")
eType = flag.String("type-exec", "uint16", "")
pType = flag.String("type-parse", "raw", "type to parse the register result. Use 'raw' if you want to see the raw bits and bytes. Use 'all' if you want to decode the result to different commonly used formats.")
writeValue = flag.Float64("write-value", math.MaxFloat64, "")
parseBigEndian = flag.Bool("order-parse-bigendian", true, "t: big, f: little")
execBigEndian = flag.Bool("order-exec-bigendian", true, "t: big, f: little")
filename = flag.String("filename", "", "")
logframe = flag.Bool("log-frame", false, "prints received and send modbus frame to stdout")
register = flag.Int("register", -1, "")
fnCode = flag.Int("fn-code", 0x03, "fn")
quantity = flag.Int("quantity", 2, "register quantity, length in bytes")
ignoreCRCError = flag.Bool("ignore-crc", false, "ignore crc")
eType = flag.String("type-exec", "uint16", "")
pType = flag.String("type-parse", "raw", "type to parse the register result. Use 'raw' if you want to see the raw bits and bytes. Use 'all' if you want to decode the result to different commonly used formats.")
writeValue = flag.Float64("write-value", math.MaxFloat64, "")
readParseOrder = flag.String("read-parse-order", "", "order to parse the register that was read out. Valid values: [AB, BA, ABCD, DCBA, BADC, CDAB]. Can only be used for 16bit (1 register) and 32bit (2 registers). If used, it will overwrite the big-endian or little-endian parameter.")
writeParseOrder = flag.String("write-exec-order", "", "order to execute the register(s) that should be written to. Valid values: [AB, BA, ABCD, DCBA, BADC, CDAB]. Can only be used for 16bit (1 register) and 32bit (2 registers). If used, it will overwrite the big-endian or little-endian parameter.")
parseBigEndian = flag.Bool("order-parse-bigendian", true, "t: big, f: little")
execBigEndian = flag.Bool("order-exec-bigendian", true, "t: big, f: little")
filename = flag.String("filename", "", "")
logframe = flag.Bool("log-frame", false, "prints received and send modbus frame to stdout")
)

flag.Parse()
Expand Down Expand Up @@ -96,7 +98,7 @@ func main() {

client := modbus.NewClient(handler)

result, err := exec(client, eo, *register, *fnCode, *writeValue, *eType, *quantity)
result, err := exec(client, eo, *writeParseOrder, *register, *fnCode, *writeValue, *eType, *quantity)
if err != nil && strings.Contains(err.Error(), "crc") && *ignoreCRCError {
logger.Printf("ignoring crc error: %+v\n", err)
} else if err != nil {
Expand All @@ -110,7 +112,7 @@ func main() {
case "all":
res, err = resultToAllString(result)
default:
res, err = resultToString(result, po, *pType)
res, err = resultToString(result, po, *readParseOrder, *pType)
}

if err != nil {
Expand All @@ -130,12 +132,15 @@ func main() {
func exec(
client modbus.Client,
o binary.ByteOrder,
forcedOrder string,
register int,
fnCode int,
wval float64,
etype string,
quantity int,
) (result []byte, err error) {
) ([]byte, error) {
var err error
var result []byte
switch fnCode {
case 0x01:
result, err = client.ReadCoils(uint16(register), uint16(quantity))
Expand All @@ -153,40 +158,14 @@ func exec(
max := float64(math.MaxUint16)
if wval > max || wval < 0 {
err = fmt.Errorf("overflow: %f does not fit into datatype uint16", wval)
return
break
}
result, err = client.WriteSingleRegister(uint16(register), uint16(wval))
case 0x10:
w := newWriter(o)
var buf []byte
switch etype {
case "uint16":
max := float64(math.MaxUint16)
if wval > max || wval < 0 {
err = fmt.Errorf("overflow: %f does not fit into datatype %s", wval, etype)
return
}
buf = make([]byte, 2)
w.PutUint16(buf, uint16(wval))
case "uint32":
max := float64(math.MaxUint32)
if wval > max || wval < 0 {
err = fmt.Errorf("overflow: %f does not fit into datatype %s", wval, etype)
return
}
buf = make([]byte, 4)
w.PutUint32(buf, uint32(wval))
case "float32":
max := float64(math.MaxFloat32)
if wval > max || wval < 0 {
err = fmt.Errorf("overflow: %f does not fit into datatype %s", wval, etype)
return
}
buf = make([]byte, 4)
w.PutFloat32(buf, float32(wval))
case "float64":
buf = make([]byte, 8)
w.PutFloat64(buf, float64(wval))
buf, err = convertToBytes(etype, o, forcedOrder, wval)
if err != nil {
break
}
result, err = client.WriteMultipleRegisters(uint16(register), uint16(len(buf))/2, buf)
case 0x04:
Expand All @@ -196,7 +175,58 @@ func exec(
default:
err = fmt.Errorf("function code %d is unsupported", fnCode)
}
return
return result, err
}

func convertToBytes(eType string, order binary.ByteOrder, forcedOrder string, val float64) ([]byte, error) {
fo := strings.ToUpper(forcedOrder)
switch fo {
case "":
// nothing is forced
case "AB", "ABCD", "BADC":
order = binary.BigEndian
case "BA", "DCBA", "CDAB":
order = binary.LittleEndian
default:
return nil, fmt.Errorf("forced order %s not known", strings.ToUpper(forcedOrder))
}

w := newWriter(order)
var buf []byte
var err error
switch eType {
case "uint16":
max := float64(math.MaxUint16)
if val > max || val < 0 {
err = fmt.Errorf("overflow: %f does not fit into datatype %s", val, eType)
break
}
buf = w.ToUint16(uint16(val))
case "uint32":
max := float64(math.MaxUint32)
if val > max || val < 0 {
err = fmt.Errorf("overflow: %f does not fit into datatype %s", val, eType)
break
}
buf = w.ToUint32(uint32(val))
case "float32":
max := float64(math.MaxFloat32)
min := -float64(math.MaxFloat32)
if val > max || val < min {
err = fmt.Errorf("overflow: %f does not fit into datatype %s", val, eType)
break
}
buf = w.ToFloat32(float32(val))
case "float64":
buf = w.ToFloat64(float64(val))
}

// flip bytes when CDAB or BADC are used (and we have 4 bytes)
if fo == "CDAB" || fo == "BADC" && len(buf) == 4 {
buf = []byte{buf[1], buf[0], buf[3], buf[2]}
}

return buf, err
}

func resultToFile(r []byte, filename string) error {
Expand All @@ -218,19 +248,19 @@ func resultToAllString(result []byte) (string, error) {

switch len(result) {
case 2:
bigUint16, err := resultToString(result, binary.BigEndian, "uint16")
bigUint16, err := resultToString(result, binary.BigEndian, "", "uint16")
if err != nil {
return "", err
}
bigInt16, err := resultToString(result, binary.BigEndian, "int16")
bigInt16, err := resultToString(result, binary.BigEndian, "", "int16")
if err != nil {
return "", err
}
littleUint16, err := resultToString(result, binary.LittleEndian, "uint16")
littleUint16, err := resultToString(result, binary.LittleEndian, "", "uint16")
if err != nil {
return "", err
}
littleInt16, err := resultToString(result, binary.LittleEndian, "int16")
littleInt16, err := resultToString(result, binary.LittleEndian, "", "int16")
if err != nil {
return "", err
}
Expand All @@ -248,55 +278,52 @@ func resultToAllString(result []byte) (string, error) {

return buf.String(), nil
case 4:
bigUint32, err := resultToString(result, binary.BigEndian, "uint32")
bigUint32, err := resultToString(result, binary.BigEndian, "", "uint32")
if err != nil {
return "", err
}
bigInt32, err := resultToString(result, binary.BigEndian, "int32")
bigInt32, err := resultToString(result, binary.BigEndian, "", "int32")
if err != nil {
return "", err
}
bigFloat32, err := resultToString(result, binary.BigEndian, "float32")
bigFloat32, err := resultToString(result, binary.BigEndian, "", "float32")
if err != nil {
return "", err
}
littleUint32, err := resultToString(result, binary.LittleEndian, "uint32")
littleUint32, err := resultToString(result, binary.LittleEndian, "", "uint32")
if err != nil {
return "", err
}
littleInt32, err := resultToString(result, binary.LittleEndian, "int32")
littleInt32, err := resultToString(result, binary.LittleEndian, "", "int32")
if err != nil {
return "", err
}
littleFloat32, err := resultToString(result, binary.LittleEndian, "float32")
littleFloat32, err := resultToString(result, binary.LittleEndian, "", "float32")
if err != nil {
return "", err
}

// flip result
result := []byte{result[1], result[0], result[3], result[2]}

midBigUint32, err := resultToString(result, binary.BigEndian, "uint32")
midBigUint32, err := resultToString(result, binary.BigEndian, "BADC", "uint32")
if err != nil {
return "", err
}
midBigInt32, err := resultToString(result, binary.BigEndian, "int32")
midBigInt32, err := resultToString(result, binary.BigEndian, "BADC", "int32")
if err != nil {
return "", err
}
midBigFloat32, err := resultToString(result, binary.BigEndian, "float32")
midBigFloat32, err := resultToString(result, binary.BigEndian, "BADC", "float32")
if err != nil {
return "", err
}
midLittleUint32, err := resultToString(result, binary.LittleEndian, "uint32")
midLittleUint32, err := resultToString(result, binary.LittleEndian, "CDAB", "uint32")
if err != nil {
return "", err
}
midLittleInt32, err := resultToString(result, binary.LittleEndian, "int32")
midLittleInt32, err := resultToString(result, binary.LittleEndian, "CDAB", "int32")
if err != nil {
return "", err
}
midLittleFloat32, err := resultToString(result, binary.LittleEndian, "float32")
midLittleFloat32, err := resultToString(result, binary.LittleEndian, "CDAB", "float32")
if err != nil {
return "", err
}
Expand Down Expand Up @@ -326,7 +353,24 @@ func resultToAllString(result []byte) (string, error) {
}
}

func resultToString(r []byte, order binary.ByteOrder, varType string) (string, error) {
func resultToString(r []byte, order binary.ByteOrder, forcedOrder string, varType string) (string, error) {
fo := strings.ToUpper(forcedOrder)
switch fo {
case "":
// nothing is forced
case "AB", "ABCD", "BADC":
order = binary.BigEndian
case "BA", "DCBA", "CDAB":
order = binary.LittleEndian
default:
return "", fmt.Errorf("forced order %s not known", strings.ToUpper(forcedOrder))
}

if fo == "CDAB" || fo == "BADC" && len(r) == 4 {
// flip result
r = []byte{r[1], r[0], r[3], r[2]}
}

switch varType {
case "string":
return string(r), nil
Expand Down Expand Up @@ -432,33 +476,44 @@ func newHandler(o option) (modbus.ClientHandler, error) {
return nil, fmt.Errorf("unsupported scheme: %s", u.Scheme)
}

type binaryWriter interface {
PutUint32(b []byte, v uint32)
PutUint16(b []byte, v uint16)
PutFloat32(b []byte, v float32)
PutFloat64(b []byte, v float64)
}

func newWriter(o binary.ByteOrder) *writer {
return &writer{o}
return &writer{order: o}
}

type writer struct {
binary.ByteOrder
order binary.ByteOrder
}

func (w *writer) ToUint16(v uint16) []byte {
var buf bytes.Buffer
w.to(&buf, v)
b, _ := io.ReadAll(&buf)
return b
}

func (w *writer) ToUint32(v uint32) []byte {
var buf bytes.Buffer
w.to(&buf, v)
b, _ := io.ReadAll(&buf)
return b
}

func (w *writer) PutFloat32(b []byte, v float32) {
buf := bytes.NewBuffer(b)
w.to(buf, v)
func (w *writer) ToFloat32(v float32) []byte {
var buf bytes.Buffer
w.to(&buf, v)
b, _ := io.ReadAll(&buf)
return b
}

func (w *writer) PutFloat64(b []byte, v float64) {
buf := bytes.NewBuffer(b)
w.to(buf, v)
func (w *writer) ToFloat64(v float64) []byte {
var buf bytes.Buffer
w.to(&buf, v)
b, _ := io.ReadAll(&buf)
return b
}

func (w *writer) to(buf io.Writer, f interface{}) {
if err := binary.Write(buf, w.ByteOrder, f); err != nil {
if err := binary.Write(buf, w.order, f); err != nil {
panic(fmt.Sprintf("binary.Write failed: %s", err.Error()))
}
}
Loading

0 comments on commit e90d491

Please sign in to comment.