Skip to content

Commit

Permalink
feat(cmd/modbus-cli): extend cli tool to add a forcerd order in the f…
Browse files Browse the repository at this point in the history
…orm of "AB", "BA", "ABCD", "DCBA", "BADC" or "CDAB. This can be used for 1 or 2 registers and will overwrite the order. Also, added unit tests to test the new conversion function
  • Loading branch information
dammarco committed Mar 8, 2023
1 parent 2371399 commit c9ee23b
Show file tree
Hide file tree
Showing 2 changed files with 394 additions and 65 deletions.
174 changes: 109 additions & 65 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
Loading

0 comments on commit c9ee23b

Please sign in to comment.