From 966136b97c50b3fb8455f1d077a90aef2eaf1870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20M=C3=BCnch?= Date: Mon, 6 May 2024 13:02:46 +0200 Subject: [PATCH] restructure convertfunctions (#52) * Restructure README.md * convertmodes are now in a table * first steps to restructure convertmodes to external files, a cleaner interface Signed-off-by: Malte Muench * All convertmethods with none-function Signed-off-by: Malte Muench * Tiny steps to use convertfunctions via an interface Signed-off-by: Malte Muench * Some stuff that should have happened earlier... * Changed the default type for CAN-IDs to uint32 in all places * Changed the default type for MQTT Payload to []byte * Completely removed convert2CAN and convert2MQTT functions * New type for mqtt payload in test * Removes mockup _test files * Second convertmode in new interface format Signed-off-by: Malte Muench * Remove convertfunctions.go and fix log message * Implemented convertmode uint82ascii * Generic implementation for all uint*2ascii convertmodes * Enable uint*2ascii convertmodes * Adds new interfaced convertmode 2uint322ascii Placed hint for myself to use strings.Fields instead of split * Implement all uint convertmodes Signed-off-by: Malte Muench * Convert modes of uint and int now en par Signed-off-by: Malte Muench * New convertmode in interfaced-form bytecolor2colorcode Signed-off-by: Malte Muench * Last convertmode implemented Signed-off-by: Malte Muench * Removes helpers.go Signed-off-by: Malte Muench * Adds e2e test state Signed-off-by: Malte Muench * Updates readme Signed-off-by: Malte Muench --------- Signed-off-by: Malte Muench Co-authored-by: Malte Muench --- README.md | 45 +- e2e-test/e2e.sh | 14 +- src/internal/convertfunctions.go | 460 ------------------ .../convertfunctions/bytecolor2colorcode.go | 31 ++ src/internal/convertfunctions/int2ascii.go | 152 ++++++ src/internal/convertfunctions/none.go | 16 + src/internal/convertfunctions/none_test.go | 198 ++++++++ .../convertfunctions/pixelbin2ascii.go | 42 ++ .../convertfunctions/sixteenbool2ascii.go | 41 ++ src/internal/convertfunctions/uint2ascii.go | 152 ++++++ src/internal/main.go | 211 +++++++- src/internal/mqtthandling.go | 2 +- src/internal/receivehandling.go | 26 +- 13 files changed, 878 insertions(+), 512 deletions(-) delete mode 100644 src/internal/convertfunctions.go create mode 100644 src/internal/convertfunctions/bytecolor2colorcode.go create mode 100644 src/internal/convertfunctions/int2ascii.go create mode 100644 src/internal/convertfunctions/none.go create mode 100644 src/internal/convertfunctions/none_test.go create mode 100644 src/internal/convertfunctions/pixelbin2ascii.go create mode 100644 src/internal/convertfunctions/sixteenbool2ascii.go create mode 100644 src/internal/convertfunctions/uint2ascii.go diff --git a/README.md b/README.md index fdd68dd..f3e8c0a 100644 --- a/README.md +++ b/README.md @@ -48,23 +48,28 @@ Explanation for the 1st Line: For example our Doorstatus is published on the CAN ## convert-modes Here they are: -### none -does not convert anything. It just takes a bunch of bytes and hands it over to the other side. If you want to send strings, this will be your choice. -### 16bool2ascii -Interprets two bytes can-wise and publishes them as 16 boolean values to mqtt -### uint82ascii / uint162ascii / uint322ascii / uint642ascii -On the can2mqtt way it takes 1, 2, 4 or 8 byte and interprets it as an uint of that size and parses it to a human readable string for the mqtt side. The other way round this convert motde takes an int in a string representation and sends out an array of bytes representing that number (little-endian) -### 2uint322ascii -This one is a bit special but all it does is that it takes 8 bytes from the CAN-Bus and parses two uint32s out of it and sends them in a string representation to MQTT. The two numbers are seperated with a simple space(" "). MQTT2CAN-wise it takes two string representations of numbers and converts them to 8 bytes representing them as 2 uint32. -### 4uint162ascii -Interprets eight bytes can-wise and publishes them as 4 uint16 seperated by a space to the mqtt side -### 4int162ascii -Interprets eight bytes can-wise and publishes them as 4 int16 seperated by a space to the mqtt side -### 4uint82ascii -Interprets four bytes (byte 0, 2, 4 and 6) can-wise and publishes them as 4 uint8 seperated by a space to the mqtt side -### 8uint82ascii -Interprets eight bytes (byte 0 to 7) can-wise and publishes them as eight uint8 seperated by a space to the mqtt side. The other way around it expects eight bytes seperated by a space and publishes them as eight bytes on the can-side. -### bytecolor2colorcode -Converts an bytearray of 3 bytes to hexadecimal colorcode -### pixelbin2ascii -This mode was designed to adress colorized pixels. MQTT-wise you can insert a string like "<0-255> #RRGGBB" wich will be converted to 4 byte on the CAN-BUS the first byte will be the number of the LED 0-255 and bytes 1, 2, 3 are the color of red, green and blue. + +| convertmode | description | +|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `none` | does not convert anything. It just takes a bunch of bytes and hands it over to the other side. If you want to send strings, this will be your choice. If you have a mqtt payload that is longer than eight bytes, only the first eight bytes will be send via CAN. | +| `bytecolor2colorcode` | Converts an bytearray of 3 bytes to hexadecimal colorcode | +| `pixelbin2ascii` | This mode was designed to adress colorized pixels. MQTT-wise you can insert a string like "<0-255> #RRGGBB" wich will be converted to 4 byte on the CAN-BUS the first byte will be the number of the LED 0-255 and bytes 1, 2, 3 are the color of red, green and blue. | +| `16bool2ascii` | Interprets two bytes can-wise and publishes them as 16 boolean values to mqtt | +| *uint* | | +| `uint82ascii` | one uint8 in the CAN-Frame to one uint8 as string in the mqtt payload | +| `4uint82ascii` | four uint8 in the CAN-Frame to four uint8 as string seperated by spaces in the mqtt payload. | +| `8uint82ascii` | eight uint8 in the CAN-Frame to eight uint8 as string seperated by spaces in the mqtt payload. | +| `uint162ascii` | one uint16 in the CAN-Frame to one uint16 as string in the mqtt payload | +| `4uint162ascii` | four uint16 in the CAN-Frame to four uint16 as string seperated by spaces in the mqtt payload. | +| `uint322ascii` | one uint32 in the CAN-Frame to one uint32 as string in the mqtt payload | +| `2uint322ascii` | two uint32 in the CAN-Frame to two uint32 as string seperated by spaces in the mqtt payload. | +| `uint642ascii` | one uint64 in the CAN-Frame to one uint64 as string in the mqtt payload | +| *int* | | +| `int82ascii` | one int8 in the CAN-Frame to one int8 as string in the mqtt payload | +| `4int82ascii` | four int8 in the CAN-Frame to four int8 as string seperated by spaces in the mqtt payload. | +| `8int82ascii` | eight int8 in the CAN-Frame to eight int8 as string seperated by spaces in the mqtt payload. | +| `int162ascii` | one int16 in the CAN-Frame to one int16 as string in the mqtt payload | +| `4int162ascii` | four int16 in the CAN-Frame to four int16 as string seperated by spaces in the mqtt payload. | +| `int322ascii` | one int32 in the CAN-Frame to one int32 as string in the mqtt payload | +| `2int322ascii` | two int32 in the CAN-Frame to two int32 as string seperated by spaces in the mqtt payload. | +| `int642ascii` | one int64 in the CAN-Frame to one int64 as string in the mqtt payload | diff --git a/e2e-test/e2e.sh b/e2e-test/e2e.sh index 847da4e..3a4cde3 100644 --- a/e2e-test/e2e.sh +++ b/e2e-test/e2e.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Build -go build -C ../src -o ../e2e-test/can2mqtt +go build -C ../src -o ../e2e-test/can2mqtt || exit # Setup Virtual CAN Interface # TODO find something that does not require root privileges @@ -29,16 +29,16 @@ mosquitto_sub -h localhost -v -t 'e2e-test/#' > logs/mqtt 2>&1 & # Publish MQTT-Tests mosquitto_pub -h localhost -m "test" -t e2e-test/none mosquitto_pub -h localhost -m "0 0 1 0 1 0 1 1 0 0 1 1 1 1 0 1" -t e2e-test/16bool2ascii -mosquitto_pub -h localhost -m "test" -t e2e-test/uint82ascii -mosquitto_pub -h localhost -m "test" -t e2e-test/uint162ascii -mosquitto_pub -h localhost -m "test" -t e2e-test/uint322ascii -mosquitto_pub -h localhost -m "test" -t e2e-test/uint642ascii +mosquitto_pub -h localhost -m "75" -t e2e-test/uint82ascii +mosquitto_pub -h localhost -m "35000" -t e2e-test/uint162ascii +mosquitto_pub -h localhost -m "2000000001" -t e2e-test/uint322ascii +mosquitto_pub -h localhost -m "123470851232" -t e2e-test/uint642ascii mosquitto_pub -h localhost -m "0 123441234" -t e2e-test/2uint322ascii mosquitto_pub -h localhost -m "12 89 1234 4" -t e2e-test/4uint162ascii mosquitto_pub -h localhost -m "-1234 42 1243 2" -t e2e-test/4int162ascii mosquitto_pub -h localhost -m "12 2 21 2" -t e2e-test/4uint82ascii mosquitto_pub -h localhost -m "1 2 3 4 5 6 7 8" -t e2e-test/8uint82ascii -mosquitto_pub -h localhost -m "test" -t e2e-test/bytecolor2colorcode +mosquitto_pub -h localhost -m "#00ff00" -t e2e-test/bytecolor2colorcode mosquitto_pub -h localhost -m "12 #00ff00" -t e2e-test/pixelbin2ascii # Send CAN-Tests @@ -53,7 +53,7 @@ cansend vcan0 "06b#ABCD" cansend vcan0 "06c#ABCD" cansend vcan0 "06d#ABCD" cansend vcan0 "06e#ABCD" -cansend vcan0 "06f#ABCD" +cansend vcan0 "06f#ABCDEF" cansend vcan0 "070#ABCD" # diff --git a/src/internal/convertfunctions.go b/src/internal/convertfunctions.go deleted file mode 100644 index 2f36f62..0000000 --- a/src/internal/convertfunctions.go +++ /dev/null @@ -1,460 +0,0 @@ -package internal - -import ( - "encoding/binary" - "encoding/hex" - "fmt" - "github.com/brutella/can" - "strconv" - "strings" -) - -// convert2CAN does the following: -// 1. receive topic and payload -// 2. use topic to examine corresponding convertmode and CAN-ID -// 3. execute conversion -// 4. build CANFrame -// 5. returning the CANFrame -func convert2CAN(topic, payload string) can.Frame { - convertMethod := getConvModeFromTopic(topic) - var Id = uint32(getIdFromTopic(topic)) - var data [8]byte - var length uint8 - if convertMethod == "none" { - if dbg { - fmt.Printf("convertfunctions: using convertmode none (reverse of %s)\n", convertMethod) - } - data, length = ascii2bytes(payload) - } else if convertMethod == "16bool2ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii2bool (reverse of %s)\n", convertMethod) - } - tmp := ascii2bool(payload) - data[0] = tmp[0] - data[1] = tmp[1] - length = 2 - } else if convertMethod == "uint82ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii2uint8 (reverse of %s)\n", convertMethod) - } - data[0] = ascii2uint8(payload) - length = 1 - } else if convertMethod == "uint162ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii2uint16(reverse of %s)\n", convertMethod) - } - tmp := ascii2uint16(payload) - data[0] = tmp[0] - data[1] = tmp[1] - length = 2 - } else if convertMethod == "uint322ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii2uint32(reverse of %s)\n", convertMethod) - } - tmp := ascii2uint32(payload) - data[0] = tmp[0] - data[1] = tmp[1] - data[2] = tmp[2] - data[3] = tmp[3] - length = 4 - } else if convertMethod == "uint642ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii2uint64(reverse of %s)\n", convertMethod) - } - tmp := ascii2uint64(payload) - data[0] = tmp[0] - data[1] = tmp[1] - data[2] = tmp[2] - data[3] = tmp[3] - data[4] = tmp[4] - data[5] = tmp[5] - data[6] = tmp[6] - data[7] = tmp[7] - length = 8 - } else if convertMethod == "2uint322ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii22uint32(reverse of %s)\n", convertMethod) - } - nums := strings.Split(payload, " ") - tmp := ascii2uint32(nums[0]) - data[0] = tmp[0] - data[1] = tmp[1] - data[2] = tmp[2] - data[3] = tmp[3] - tmp = ascii2uint32(nums[1]) - data[4] = tmp[0] - data[5] = tmp[1] - data[6] = tmp[2] - data[7] = tmp[3] - length = 8 - } else if convertMethod == "4uint162ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii24uint16(reverse of %s)\n", convertMethod) - } - nums := strings.Split(payload, " ") - tmp := ascii2uint16(nums[0]) - data[0] = tmp[0] - data[1] = tmp[1] - tmp = ascii2uint16(nums[1]) - data[2] = tmp[0] - data[3] = tmp[1] - tmp = ascii2uint16(nums[2]) - data[4] = tmp[0] - data[5] = tmp[1] - tmp = ascii2uint16(nums[3]) - data[6] = tmp[0] - data[7] = tmp[1] - length = 8 - } else if convertMethod == "4int162ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii24int16(reverse of %s)\n", convertMethod) - } - nums := strings.Split(payload, " ") - tmp := ascii2int16(nums[0]) - data[0] = tmp[0] - data[1] = tmp[1] - tmp = ascii2int16(nums[1]) - data[2] = tmp[0] - data[3] = tmp[1] - tmp = ascii2int16(nums[2]) - data[4] = tmp[0] - data[5] = tmp[1] - tmp = ascii2int16(nums[3]) - data[6] = tmp[0] - data[7] = tmp[1] - length = 8 - } else if convertMethod == "4uint82ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii24uint8(reverse of %s)\n", convertMethod) - } - nums := strings.Split(payload, " ") - tmp := ascii2uint8(nums[0]) - data[0] = tmp - data[1] = 0 - tmp = ascii2uint8(nums[1]) - data[2] = tmp - data[3] = 0 - tmp = ascii2uint8(nums[2]) - data[4] = tmp - data[5] = 0 - tmp = ascii2uint8(nums[3]) - data[6] = tmp - data[7] = 0 - length = 8 - } else if convertMethod == "8uint82ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii28uint8(reverse of %s)\n", convertMethod) - } - nums := strings.Split(payload, " ") - if len(nums) != 8 { - fmt.Printf("Error, wrong number of bytes provided for convertmode 8uint82ascii, expected 8 got %d\n", len(nums)) - } - data[0] = ascii2uint8(nums[0]) - data[1] = ascii2uint8(nums[1]) - data[2] = ascii2uint8(nums[2]) - data[3] = ascii2uint8(nums[3]) - data[4] = ascii2uint8(nums[4]) - data[5] = ascii2uint8(nums[5]) - data[6] = ascii2uint8(nums[6]) - data[7] = ascii2uint8(nums[7]) - length = 8 - } else if convertMethod == "bytecolor2colorcode" { - if dbg { - fmt.Printf("convertfunctions: using convertmode colorcode2bytecolor(reverse of %s)\n", convertMethod) - } - tmp := colorcode2bytecolor(payload) - data[0] = tmp[0] - data[1] = tmp[1] - data[2] = tmp[2] - length = 3 - } else if convertMethod == "pixelbin2ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode ascii2pixelbin(reverse of %s)\n", convertMethod) - } - numAndColor := strings.Split(payload, " ") - binNum := ascii2uint8(numAndColor[0]) - tmp := colorcode2bytecolor(numAndColor[1]) - data[0] = binNum - data[1] = tmp[0] - data[2] = tmp[1] - data[3] = tmp[2] - length = 4 - } else { - if dbg { - fmt.Printf("convertfunctions: convertmode %s not found. using fallback none\n", convertMethod) - } - data, length = ascii2bytes(payload) - } - myFrame := can.Frame{ID: Id, Length: length, Data: data} - return myFrame -} - -// convert2MQTT does the following -// 1. receive ID and payload -// 2. lookup the correct convertmode -// 3. executing conversion -// 4. building a string -// 5. return -func convert2MQTT(id int, length int, payload [8]byte) string { - convertMethod := getConvModeFromId(id) - if convertMethod == "none" { - if dbg { - fmt.Printf("convertfunctions: using convertmode none\n") - } - return bytes2ascii(uint32(length), payload) - } else if convertMethod == "16bool2ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode 16bool2ascii\n") - } - return bool2ascii(payload[0:2]) - } else if convertMethod == "uint82ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode uint82ascii\n") - } - return uint82ascii(payload[0]) - } else if convertMethod == "uint162ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode uint162ascii\n") - } - return uint162ascii(payload[0:2]) - } else if convertMethod == "uint322ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode uint322ascii\n") - } - return uint322ascii(payload[0:4]) - } else if convertMethod == "uint642ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode uint642ascii\n") - } - return uint642ascii(payload[0:8]) - } else if convertMethod == "2uint322ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode 2uint322ascii\n") - } - return uint322ascii(payload[0:4]) + " " + uint322ascii(payload[4:8]) - } else if convertMethod == "4uint162ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode 4uint162ascii\n") - } - return uint162ascii(payload[0:2]) + " " + uint162ascii(payload[2:4]) + " " + uint162ascii(payload[4:6]) + " " + uint162ascii(payload[6:8]) - } else if convertMethod == "4int162ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode 4int162ascii\n") - } - return int162ascii(payload[0:2]) + " " + int162ascii(payload[2:4]) + " " + int162ascii(payload[4:6]) + " " + int162ascii(payload[6:8]) - } else if convertMethod == "4uint82ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode 4uint82ascii\n") - } - return uint82ascii(payload[0]) + " " + uint82ascii(payload[2]) + " " + uint82ascii(payload[4]) + " " + uint82ascii(payload[6]) - } else if convertMethod == "8uint82ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode 8uint82ascii\n") - } - return uint82ascii(payload[0]) + " " + uint82ascii(payload[1]) + " " + uint82ascii(payload[2]) + " " + uint82ascii(payload[3]) + " " + uint82ascii(payload[4]) + " " + uint82ascii(payload[5]) + " " + uint82ascii(payload[6]) + " " + uint82ascii(payload[7]) - } else if convertMethod == "pixelbin2ascii" { - if dbg { - fmt.Printf("convertfunctions: using convertmode pixelbin2ascii\n") - } - return uint82ascii(payload[0]) + " " + bytecolor2colorcode(payload[1:4]) - } else if convertMethod == "bytecolor2colorcode" { - if dbg { - fmt.Printf("convertfunctions: using convertmode bytecolor2colorcode\n") - } - return bytecolor2colorcode(payload[0:2]) - - } else { - if dbg { - fmt.Printf("convertfunctions: convertmode %s not found. using fallback none\n", convertMethod) - } - return bytes2ascii(uint32(length), payload) - } -} - -//###################################################################### -//# NONE # -//###################################################################### - -func bytes2ascii(length uint32, payload [8]byte) string { - return string(payload[:length]) -} - -func ascii2bytes(payload string) ([8]byte, uint8) { - var returner [8]byte - var i uint8 = 0 - for ; int(i) < len(payload) && i < 8; i++ { - returner[i] = payload[i] - } - return returner, i -} - -// ###################################################################### -// # BOOL2ASCII # -// ###################################################################### -// bool2ascii takes exactly two byte and returns a string with a -// boolean interpretation of the found data -func bool2ascii(payload []byte) string { - if len(payload) != 2 { - return "Err in CAN-Frame, data must be 2 bytes." - } - data := binary.LittleEndian.Uint16(payload) - bits := strconv.FormatUint(uint64(data), 2) - split := strings.Split(bits, "") - // fill the '0' bits - if len(split) < 16 { - for i := len(split); i < 16; i++ { - split = append([]string{"0"}, split...) - } - } - // get the two 'bytes' - lower := split[8:16] - upper := split[0:8] - // swap 'bytes', according to integer representation - lower[0], lower[1], lower[2], lower[3], lower[4], lower[5], lower[6], lower[7] = lower[7], lower[6], lower[5], lower[4], lower[3], lower[2], lower[1], lower[0] - upper[0], upper[1], upper[2], upper[3], upper[4], upper[5], upper[6], upper[7] = upper[7], upper[6], upper[5], upper[4], upper[3], upper[2], upper[1], upper[0] - return strings.Join(lower, " ") + " " + strings.Join(upper, " ") -} - -func ascii2bool(payload string) []byte { - // split the 16 '0' or '1' - split := strings.Split(payload, " ") - // get the two 'bytes' - lower := split[0:8] - upper := split[8:16] - // swap 'bytes', according to integer representation - lower[0], lower[1], lower[2], lower[3], lower[4], lower[5], lower[6], lower[7] = lower[7], lower[6], lower[5], lower[4], lower[3], lower[2], lower[1], lower[0] - upper[0], upper[1], upper[2], upper[3], upper[4], upper[5], upper[6], upper[7] = upper[7], upper[6], upper[5], upper[4], upper[3], upper[2], upper[1], upper[0] - // convert to string again - tmp := strings.Join(upper, "") + strings.Join(lower, "") - number, err := strconv.ParseUint(tmp, 2, 16) - a := make([]byte, 2) - if err != nil { - fmt.Printf("error converting %s\n", tmp) - } else { - binary.LittleEndian.PutUint16(a, uint16(number)) - } - return a -} - -// ###################################################################### -// # UINT82ASCII # -// ###################################################################### -// uint82ascii takes exactly one byte and returns a string with a -// numeric decimal interpretation of the found data -func uint82ascii(payload byte) string { - return strconv.FormatInt(int64(payload), 10) -} - -func ascii2uint8(payload string) byte { - return ascii2uint16(payload)[0] -} - -// ###################################################################### -// # UINT162ASCII # -// ###################################################################### -// uint162ascii takes 2 bytes and returns a string with a numeric -// decimal interpretation of the found data as ascii-string -func uint162ascii(payload []byte) string { - if len(payload) != 2 { - return "Err in CAN-Frame, data must be 2 bytes." - } - data := binary.LittleEndian.Uint16(payload) - return strconv.FormatUint(uint64(data), 10) -} - -func ascii2uint16(payload string) []byte { - tmp, _ := strconv.Atoi(payload) - number := uint16(tmp) - a := make([]byte, 2) - binary.LittleEndian.PutUint16(a, number) - return a -} - -// ###################################################################### -// # INT162ASCII # -// ###################################################################### -// int162ascii takes 2 bytes and returns a string with a numeric -// decimal interpretation of the found data as ascii-string -func int162ascii(payload []byte) string { - if len(payload) != 2 { - return "Err in CAN-Frame, data must be 2 bytes." - } - data := int16(binary.LittleEndian.Uint16(payload)) - return strconv.FormatInt(int64(data), 10) -} - -func ascii2int16(payload string) []byte { - tmp, _ := strconv.Atoi(payload) - number := uint16(tmp) - a := make([]byte, 2) - binary.LittleEndian.PutUint16(a, number) - return a -} - -// ######################################################################## -// ###################################################################### -// # UINT322ASCII # -// ###################################################################### -// uint322ascii takes 4 bytes and returns a string with a numeric -// decimal interpretation of the found data as ascii-string -func uint322ascii(payload []byte) string { - if len(payload) != 4 { - return "Err in CAN-Frame, data must be 4 bytes." - } - data := binary.LittleEndian.Uint32(payload) - return strconv.FormatUint(uint64(data), 10) -} - -func ascii2uint32(payload string) []byte { - tmp, _ := strconv.Atoi(payload) - number := uint32(tmp) - a := make([]byte, 4) - binary.LittleEndian.PutUint32(a, number) - return a -} - -// ######################################################################## -// ###################################################################### -// # UINT642ASCII # -// ###################################################################### -// uint642ascii takes 8 bytes and returns a string with a numeric -// decimal interpretation of the found data as ascii-string -func uint642ascii(payload []byte) string { - if len(payload) != 8 { - return "Err in CAN-Frame, data must be 8 bytes." - } - data := binary.LittleEndian.Uint64(payload) - return strconv.FormatUint(data, 10) -} - -func ascii2uint64(payload string) []byte { - tmp, _ := strconv.Atoi(payload) - number := uint64(tmp) - a := make([]byte, 8) - binary.LittleEndian.PutUint64(a, number) - return a -} - -// ######################################################################## -// ###################################################################### -// # bytecolor2colorcode -// ###################################################################### -// bytecolor2colorcode is a convertmode that converts between the binary -// 3 byte representation of a color and a string representation of a color -// as we know it (for example in html #00ff00 is green) -func bytecolor2colorcode(payload []byte) string { - colorstring := hex.EncodeToString(payload) - return "#" + colorstring -} - -func colorcode2bytecolor(payload string) []byte { - var a []byte - var err error - a, err = hex.DecodeString(strings.Replace(payload, "#", "", -1)) - if err != nil { - return []byte{0, 0, 0} - } - return a -} - -//######################################################################## diff --git a/src/internal/convertfunctions/bytecolor2colorcode.go b/src/internal/convertfunctions/bytecolor2colorcode.go new file mode 100644 index 0000000..80388d3 --- /dev/null +++ b/src/internal/convertfunctions/bytecolor2colorcode.go @@ -0,0 +1,31 @@ +package convertfunctions + +import ( + "encoding/hex" + "errors" + "fmt" + "github.com/brutella/can" + "strings" +) + +func ByteColor2ColorCodeToCan(input []byte) (can.Frame, error) { + colorBytes, _ := strings.CutPrefix(string(input), "#") + if len(colorBytes) != 6 { + return can.Frame{}, errors.New(fmt.Sprintf("input does not contain exactly 6 nibbles each represented by one character, got %d instead", len(colorBytes))) + } + res, err := hex.DecodeString(colorBytes) + if err != nil { + return can.Frame{}, errors.New(fmt.Sprintf("Error while converting: %s", err.Error())) + } + var returner can.Frame = can.Frame{Length: 3} + copy(res, returner.Data[0:3]) + return returner, nil +} + +func ByteColor2ColorCodeToMqtt(input can.Frame) ([]byte, error) { + if input.Length != 3 { + return []byte{}, errors.New(fmt.Sprintf("Input does not contain exactly 3 bytes, got %d instead", input.Length)) + } + colorstring := hex.EncodeToString(input.Data[0:3]) + return []byte("#" + colorstring), nil +} diff --git a/src/internal/convertfunctions/int2ascii.go b/src/internal/convertfunctions/int2ascii.go new file mode 100644 index 0000000..ede2446 --- /dev/null +++ b/src/internal/convertfunctions/int2ascii.go @@ -0,0 +1,152 @@ +package convertfunctions + +import ( + "encoding/binary" + "errors" + "fmt" + "github.com/brutella/can" + "strconv" + "strings" +) + +func Int82AsciiToCan(input []byte) (can.Frame, error) { + return NIntM2AsciiToCan(1, 8, input) +} + +func Int82AsciiToMqtt(input can.Frame) ([]byte, error) { + return NIntM2AsciiToMqtt(1, 8, input) +} + +func Int162AsciiToCan(input []byte) (can.Frame, error) { + return NIntM2AsciiToCan(1, 16, input) +} + +func Int162AsciiToMqtt(input can.Frame) ([]byte, error) { + return NIntM2AsciiToMqtt(1, 16, input) +} + +func Int322AsciiToCan(input []byte) (can.Frame, error) { + return NIntM2AsciiToCan(1, 32, input) +} + +func Int322AsciiToMqtt(input can.Frame) ([]byte, error) { + return NIntM2AsciiToMqtt(1, 32, input) +} + +func Int642AsciiToCan(input []byte) (can.Frame, error) { + return NIntM2AsciiToCan(1, 64, input) +} + +func Int642AsciiToMqtt(input can.Frame) ([]byte, error) { + return NIntM2AsciiToMqtt(1, 64, input) +} + +func TwoInt322AsciiToCan(input []byte) (can.Frame, error) { + return NIntM2AsciiToCan(2, 32, input) +} + +func TwoInt322AsciiToMqtt(input can.Frame) ([]byte, error) { + return NIntM2AsciiToMqtt(2, 32, input) +} + +func EightInt82AsciiToCan(input []byte) (can.Frame, error) { + return NIntM2AsciiToCan(8, 8, input) +} + +func EightInt82AsciiToMqtt(input can.Frame) ([]byte, error) { + return NIntM2AsciiToMqtt(8, 8, input) +} + +func FourInt82AsciiToCan(input []byte) (can.Frame, error) { + return NIntM2AsciiToCan(4, 8, input) +} + +func FourInt82AsciiToMqtt(input can.Frame) ([]byte, error) { + return NIntM2AsciiToMqtt(4, 8, input) +} + +func FourInt162AsciiToCan(input []byte) (can.Frame, error) { + return NIntM2AsciiToCan(4, 16, input) +} + +func FourInt162AsciiToMqtt(input can.Frame) ([]byte, error) { + return NIntM2AsciiToMqtt(4, 16, input) +} + +// NIntM2AsciiToCan is the generic approach to convert numberAmount occurrences of numbers with numberWidth bits size. +// Allowed values for numberAmount are 1-8. +// Allowed values for numberWidth are 8, 16, 32 or 64 +// numberAmount*numberWidth shall not be larger than 64 +// input has to contain the data that shall be converted. The input is split at whitespaces, the amount of fields has +// to match numberAmount. +// If the amount of fields matches, each field is converted to a uint of size numberWidth. The results are then added to the CAN-frame. +func NIntM2AsciiToCan(numberAmount, numberWidth uint, input []byte) (can.Frame, error) { + if !(numberWidth == 8 || numberWidth == 16 || numberWidth == 32 || numberWidth == 64) { + + return can.Frame{}, errors.New(fmt.Sprintf("numberWitdh %d uknown please choose one of 8, 16. 32 or 64\n", numberWidth)) + + } + if numberWidth*numberAmount > 64 { + return can.Frame{}, errors.New(fmt.Sprintf("%d number of %d bit width would not fit into a 8 byte CAN-Frame %d exceeds 64 bits.\n", numberAmount, numberWidth, numberAmount*numberWidth)) + } + splitInput := strings.Fields(string(input)) + if uint(len(splitInput)) != numberAmount { + return can.Frame{}, errors.New(fmt.Sprintf("input does not contain exactly %d numbers seperated by whitespace", numberAmount)) + } + var ret can.Frame + ret.Length = uint8((numberAmount * numberWidth) >> 3) + bytePerNumber := numberWidth >> 3 + for i := uint(0); i < numberAmount; i++ { + res, err := strconv.ParseInt(splitInput[i], 10, int(numberWidth)) + if err != nil { + return can.Frame{}, errors.New(fmt.Sprintf("Error while converting string %d: %s, %s", i, splitInput[i], err)) + } + switch numberWidth { + case 64: + binary.LittleEndian.PutUint64(ret.Data[i*bytePerNumber:(i+1)*bytePerNumber], uint64(res)) + case 32: + binary.LittleEndian.PutUint32(ret.Data[i*bytePerNumber:(i+1)*bytePerNumber], uint32(res)) + case 16: + binary.LittleEndian.PutUint16(ret.Data[i*bytePerNumber:(i+1)*bytePerNumber], uint16(res)) + case 8: + ret.Data[i] = uint8(res) + } + } + return ret, nil +} + +// NIntM2AsciiToMqtt is the generic approach to convert numberAmount occurrences of numbers with numberWidth bits size. +// Allowed values for numberAmount are 1-8. +// Allowed values for numberWidth are 8, 16, 32 or 64 +// numberAmount*numberWidth shall not be larger than 64 +// input has to Contain the Data that shall be converted. The Size of the CAN-Frame has to fit the expected size. +// If we have for example 1 amount of 32-Bits numbers the CAN-Frame size input.Length has to be 4 (bytes). +// If the size fits, the Data is split up in numberAmount pieces and are then processed to a string representation +// via strconv.FormatUint. +// The successful return value is a byte-slice that represents the converted strings joined with a space between them. +func NIntM2AsciiToMqtt(numberAmount, numberWidth uint, input can.Frame) ([]byte, error) { + if !(numberWidth == 8 || numberWidth == 16 || numberWidth == 32 || numberWidth == 64) { + return []byte{}, errors.New(fmt.Sprintf("numberWitdh %d uknown please choose one of 8, 16. 32 or 64\n", numberWidth)) + } + if numberWidth*numberAmount > 64 { + return []byte{}, errors.New(fmt.Sprintf("%d number of %d bit width would not fit into a 8 byte CAN-Frame %d exceeds 64 bits.\n", numberAmount, numberWidth, numberAmount*numberWidth)) + } + if input.Length != uint8((numberWidth*numberAmount)>>3) { + return []byte{}, errors.New(fmt.Sprintf("Input is of wrong length: %d, expected %d because of %d numbers of %d-bits.", input.Length, (numberAmount*numberWidth)>>3, numberAmount, numberWidth)) + } + var returnStrings []string + bytePerNumber := numberWidth >> 3 + for i := uint(0); i < numberAmount; i++ { + switch numberWidth { + case 64: + returnStrings = append(returnStrings, strconv.FormatInt(int64(binary.LittleEndian.Uint64(input.Data[i*bytePerNumber:(i+1)*bytePerNumber])), 10)) + case 32: + returnStrings = append(returnStrings, strconv.FormatInt(int64(binary.LittleEndian.Uint32(input.Data[i*bytePerNumber:(i+1)*bytePerNumber])), 10)) + case 16: + returnStrings = append(returnStrings, strconv.FormatInt(int64(binary.LittleEndian.Uint16(input.Data[i*bytePerNumber:(i+1)*bytePerNumber])), 10)) + case 8: + returnStrings = append(returnStrings, strconv.FormatInt(int64(input.Data[i]), 10)) + } + } + return []byte(strings.Join(returnStrings, " ")), nil +} diff --git a/src/internal/convertfunctions/none.go b/src/internal/convertfunctions/none.go new file mode 100644 index 0000000..01d07b0 --- /dev/null +++ b/src/internal/convertfunctions/none.go @@ -0,0 +1,16 @@ +package convertfunctions + +import "github.com/brutella/can" + +func NoneToCan(input []byte) (can.Frame, error) { + var returner [8]byte + var i uint8 = 0 + for ; int(i) < len(input) && i < 8; i++ { + returner[i] = input[i] + } + return can.Frame{Length: i, Data: returner}, nil +} + +func NoneToMqtt(input can.Frame) ([]byte, error) { + return input.Data[:input.Length], nil +} diff --git a/src/internal/convertfunctions/none_test.go b/src/internal/convertfunctions/none_test.go new file mode 100644 index 0000000..5e8a215 --- /dev/null +++ b/src/internal/convertfunctions/none_test.go @@ -0,0 +1,198 @@ +package convertfunctions + +import ( + "github.com/brutella/can" + "testing" +) + +func TestNoneToCan(t *testing.T) { + input := []byte("Gladys") + output, err := NoneToCan(input) + + // Check whether err is nil + if err != nil { + t.Fatalf(`NoneToCan failed, err not nil: %s`, err.Error()) + } + + // Check whether the output has the correct length + if output.Length != (uint8(len(input))) { + t.Fatalf(`NoneToCan failed, expected length %d, actual length %d`, len(input), output.Length) + } + + // Check if the output has the correct content + for i := 0; i < len(input); i++ { + if output.Data[i] != input[i] { + t.Fatalf(`NoneToCan failed, output wrong at byte %d`, i) + } + } + + // Check if back and forth conversion leads to original input + back, err := NoneToMqtt(output) + if err != nil { + t.Fatalf(`NoneToMqtt failed, err not nil: %s`, err.Error()) + } + + if string(input) != string(back) { + t.Fatalf(`NoneToCan failed, back and forth conversion did not lead to original input`) + } +} + +func TestNoneToMqtt(t *testing.T) { + input := can.Frame{ + ID: 0, + Length: 6, + Flags: 0, + Res0: 0, + Res1: 0, + Data: [8]uint8{'G', 'l', 'a', 'd', 'y', 's'}, + } + output, err := NoneToMqtt(input) + + // Check whether err is nil + if err != nil { + t.Fatalf(`NoneToMqtt failed, err not nil: %s`, err.Error()) + } + + // Check whether the output has the correct length + if uint8(len(output)) != input.Length { + t.Fatalf(`NoneToMqtt failed, expected length %d, actual length %d`, input.Length, uint8(len(output))) + } + + // Check if the output has the correct content + for i := uint8(0); i < input.Length; i++ { + if output[i] != input.Data[i] { + t.Fatalf(`NoneToCan failed, output wrong at byte %d`, i) + } + } + + // Check if back and forth conversion leads to original input + back, err := NoneToCan(output) + if err != nil { + t.Fatalf(`NoneToCan failed, err not nil: %s`, err.Error()) + } + + if input != back { + t.Fatalf(`NoneToCan failed, back and forth conversion did not lead to original input`) + } +} + +func FuzzNoneToCan(f *testing.F) { + f.Fuzz(func(t *testing.T, input []byte) { + output, err := NoneToCan(input) + if err != nil { + t.Fatalf("%v: decode: %v", input, err) + } + + if len(input) > 8 { + t.Logf("input (%s) is larger than 8 bytes (%d), only checking first 8 byte", input, len(input)) + // Check whether the output has the correct length + if output.Length != 8 { + t.Fatalf(`NoneToCan failed, expected length %d, actual length %d`, len(input), output.Length) + } + // Check if the output has the correct content + for i := 0; i < 8; i++ { + if output.Data[i] != input[i] { + t.Fatalf(`NoneToCan failed, output wrong at byte %d`, i) + } + } + // Check if back and forth conversion leads to original input + back, err := NoneToMqtt(output) + if err != nil { + t.Fatalf(`NoneToMqtt failed, err not nil: %s`, err.Error()) + } + + // only first 8 bytes are important + if string(input[:8]) != string(back) { + t.Fatalf(`NoneToCan failed, back and forth conversion did not lead to original input`) + } + } else { + // Check whether the output has the correct length + if output.Length != (uint8(len(input))) { + t.Fatalf(`NoneToCan failed, expected length %d, actual length %d`, len(input), output.Length) + } + // Check if the output has the correct content + for i := 0; i < len(input); i++ { + if output.Data[i] != input[i] { + t.Fatalf(`NoneToCan failed, output wrong at byte %d`, i) + } + } + + // Check if back and forth conversion leads to original input + back, err := NoneToMqtt(output) + if err != nil { + t.Fatalf(`NoneToMqtt failed, err not nil: %s`, err.Error()) + } + + if string(input) != string(back) { + t.Fatalf(`NoneToCan failed, back and forth conversion did not lead to original input`) + } + } + + }) +} + +func FuzzNoneToMqtt(f *testing.F) { + f.Fuzz(func(t *testing.T, inputString string) { + var input can.Frame + if len(inputString) > 8 { + input.Length = 8 + } else { + input.Length = uint8(len(inputString)) + } + for i := uint8(0); i < input.Length; i++ { + input.Data[i] = inputString[i] + } + output, err := NoneToMqtt(input) + if err != nil { + t.Fatalf("%v: decode: %v", input, err) + } + + if input.Length > 8 { + t.Logf("input.Length (%d) is larger than 8 bytes, only checking first 8 byte", input.Length) + // Check whether the output has the correct length + if len(output) != 8 { + t.Fatalf(`NoneToMqtt failed, expected length %d, actual length %d`, input.Length, len(output)) + } + // Check if the output has the correct content + for i := 0; i < 8; i++ { + if output[i] != input.Data[i] { + t.Fatalf(`NoneToMqtt failed, output wrong at byte %d`, i) + } + } + // Check if back and forth conversion leads to original input + back, err := NoneToCan(output) + if err != nil { + t.Fatalf(`NoneToMqtt failed, err not nil: %s`, err.Error()) + } + + // only first 8 bytes are important + for i := uint8(0); i < 8; i++ { + if input.Data[i] != back.Data[i] { + t.Fatalf(`NoneToCan failed, back and forth conversion did not lead to original input`) + } + } + } else { + // Check whether the output has the correct length + if uint8(len(output)) != input.Length { + t.Fatalf(`NoneToMqtt failed, expected length %d, actual length %d`, input.Length, len(output)) + } + // Check if the output has the correct content + for i := uint8(0); i < input.Length; i++ { + if output[i] != input.Data[i] { + t.Fatalf(`NoneToMqtt failed, output wrong at byte %d`, i) + } + } + + // Check if back and forth conversion leads to original input + back, err := NoneToCan(output) + if err != nil { + t.Fatalf(`NoneToMqtt failed, err not nil: %s`, err.Error()) + } + + if input != back { + t.Fatalf(`NoneToMqtt failed, back and forth conversion did not lead to original input`) + } + } + + }) +} diff --git a/src/internal/convertfunctions/pixelbin2ascii.go b/src/internal/convertfunctions/pixelbin2ascii.go new file mode 100644 index 0000000..852f029 --- /dev/null +++ b/src/internal/convertfunctions/pixelbin2ascii.go @@ -0,0 +1,42 @@ +package convertfunctions + +import ( + "encoding/hex" + "errors" + "fmt" + "github.com/brutella/can" + "strconv" + "strings" +) + +func PixelBin2AsciiToCan(input []byte) (can.Frame, error) { + colorBytesAndNumber := strings.Fields(string(input)) + if len(colorBytesAndNumber) != 2 { + return can.Frame{}, errors.New(fmt.Sprintf("input does not contain exactly two fields, one for the number and one for the color, got %d fields instead.", len(colorBytesAndNumber))) + } + colorBytes, _ := strings.CutPrefix(colorBytesAndNumber[1], "#") + if len(colorBytes) != 6 { + return can.Frame{}, errors.New(fmt.Sprintf("second field (color) does not contain exactly 6 nibbles each represented by one character, got %d instead", len(colorBytes))) + } + number, err := strconv.ParseUint(colorBytesAndNumber[0], 10, 8) + if err != nil { + return can.Frame{}, errors.New(fmt.Sprintf("Error while converting first field (pixel number): %s", err.Error())) + } + res, err := hex.DecodeString(colorBytes) + if err != nil { + return can.Frame{}, errors.New(fmt.Sprintf("Error while converting: %s", err.Error())) + } + var returner can.Frame = can.Frame{Length: 4} + returner.Data[0] = uint8(number) + copy(res, returner.Data[1:4]) + return returner, nil +} + +func PixelBin2AsciiToMqtt(input can.Frame) ([]byte, error) { + if input.Length != 4 { + return []byte{}, errors.New(fmt.Sprintf("Input does not contain exactly 4 bytes, got %d instead", input.Length)) + } + colorString := "#" + hex.EncodeToString(input.Data[0:3]) + numberString := strconv.FormatUint(uint64(input.Data[0]), 10) + return []byte(strings.Join([]string{numberString, colorString}, " ")), nil +} diff --git a/src/internal/convertfunctions/sixteenbool2ascii.go b/src/internal/convertfunctions/sixteenbool2ascii.go new file mode 100644 index 0000000..4ccf56d --- /dev/null +++ b/src/internal/convertfunctions/sixteenbool2ascii.go @@ -0,0 +1,41 @@ +package convertfunctions + +import ( + "errors" + "fmt" + "github.com/brutella/can" + "strconv" + "strings" +) + +func SixteenBool2AsciiToCan(input []byte) (can.Frame, error) { + splitInput := strings.Split(string(input), " ") // TODO use strings.Fields here + if len(splitInput) != 16 { + return can.Frame{}, errors.New("input does not contain exactly 16 numbers seperated by spaces") + } + var returnData [8]uint8 + for i := 0; i < len(splitInput); i++ { + res, err := strconv.ParseBool(splitInput[i]) + if err != nil { + return can.Frame{}, errors.New(fmt.Sprintf("input does not specify a boolean at index %d: %s:%s", i, splitInput[i], err)) + } + if res { + returnData[i>>3] |= 0x1 << (i % 8) + } else { + + returnData[i>>3] |= 0x0 << (i % 8) + } + } + return can.Frame{Length: 2, Data: returnData}, nil +} +func SixteenBool2AsciiToMqtt(input can.Frame) ([]byte, error) { + var returnStrings [16]string + for i := 0; i < 16; i++ { + if (input.Data[i>>3]>>(i%8))&0x1 == 1 { + returnStrings[i] = "1" + } else { + returnStrings[i] = "0" + } + } + return []byte(strings.Join(returnStrings[:], " ")), nil +} diff --git a/src/internal/convertfunctions/uint2ascii.go b/src/internal/convertfunctions/uint2ascii.go new file mode 100644 index 0000000..c0490e5 --- /dev/null +++ b/src/internal/convertfunctions/uint2ascii.go @@ -0,0 +1,152 @@ +package convertfunctions + +import ( + "encoding/binary" + "errors" + "fmt" + "github.com/brutella/can" + "strconv" + "strings" +) + +func Uint82AsciiToCan(input []byte) (can.Frame, error) { + return NUintM2AsciiToCan(1, 8, input) +} + +func Uint82AsciiToMqtt(input can.Frame) ([]byte, error) { + return NUintM2AsciiToMqtt(1, 8, input) +} + +func Uint162AsciiToCan(input []byte) (can.Frame, error) { + return NUintM2AsciiToCan(1, 16, input) +} + +func Uint162AsciiToMqtt(input can.Frame) ([]byte, error) { + return NUintM2AsciiToMqtt(1, 16, input) +} + +func Uint322AsciiToCan(input []byte) (can.Frame, error) { + return NUintM2AsciiToCan(1, 32, input) +} + +func Uint322AsciiToMqtt(input can.Frame) ([]byte, error) { + return NUintM2AsciiToMqtt(1, 32, input) +} + +func Uint642AsciiToCan(input []byte) (can.Frame, error) { + return NUintM2AsciiToCan(1, 64, input) +} + +func Uint642AsciiToMqtt(input can.Frame) ([]byte, error) { + return NUintM2AsciiToMqtt(1, 64, input) +} + +func TwoUint322AsciiToCan(input []byte) (can.Frame, error) { + return NUintM2AsciiToCan(2, 32, input) +} + +func TwoUint322AsciiToMqtt(input can.Frame) ([]byte, error) { + return NUintM2AsciiToMqtt(2, 32, input) +} + +func EightUint82AsciiToCan(input []byte) (can.Frame, error) { + return NUintM2AsciiToCan(8, 8, input) +} + +func EightUint82AsciiToMqtt(input can.Frame) ([]byte, error) { + return NUintM2AsciiToMqtt(8, 8, input) +} + +func FourUint82AsciiToCan(input []byte) (can.Frame, error) { + return NUintM2AsciiToCan(4, 8, input) +} + +func FourUint82AsciiToMqtt(input can.Frame) ([]byte, error) { + return NUintM2AsciiToMqtt(4, 8, input) +} + +func FourUint162AsciiToCan(input []byte) (can.Frame, error) { + return NUintM2AsciiToCan(4, 16, input) +} + +func FourUint162AsciiToMqtt(input can.Frame) ([]byte, error) { + return NUintM2AsciiToMqtt(4, 16, input) +} + +// NUintM2AsciiToCan is the generic approach to convert numberAmount occurrences of numbers with numberWidth bits size. +// Allowed values for numberAmount are 1-8. +// Allowed values for numberWidth are 8, 16, 32 or 64 +// numberAmount*numberWidth shall not be larger than 64 +// input has to contain the data that shall be converted. The input is split at whitespaces, the amount of fields has +// to match numberAmount. +// If the amount of fields matches, each field is converted to a uint of size numberWidth. The results are then added to the CAN-frame. +func NUintM2AsciiToCan(numberAmount, numberWidth uint, input []byte) (can.Frame, error) { + if !(numberWidth == 8 || numberWidth == 16 || numberWidth == 32 || numberWidth == 64) { + + return can.Frame{}, errors.New(fmt.Sprintf("numberWitdh %d uknown please choose one of 8, 16. 32 or 64\n", numberWidth)) + + } + if numberWidth*numberAmount > 64 { + return can.Frame{}, errors.New(fmt.Sprintf("%d number of %d bit width would not fit into a 8 byte CAN-Frame %d exceeds 64 bits.\n", numberAmount, numberWidth, numberAmount*numberWidth)) + } + splitInput := strings.Fields(string(input)) + if uint(len(splitInput)) != numberAmount { + return can.Frame{}, errors.New(fmt.Sprintf("input does not contain exactly %d numbers seperated by whitespace", numberAmount)) + } + var ret can.Frame + ret.Length = uint8((numberAmount * numberWidth) >> 3) + bytePerNumber := numberWidth >> 3 + for i := uint(0); i < numberAmount; i++ { + res, err := strconv.ParseUint(splitInput[i], 10, int(numberWidth)) + if err != nil { + return can.Frame{}, errors.New(fmt.Sprintf("Error while converting string %d: %s, %s", i, splitInput[i], err)) + } + switch numberWidth { + case 64: + binary.LittleEndian.PutUint64(ret.Data[i*bytePerNumber:(i+1)*bytePerNumber], res) + case 32: + binary.LittleEndian.PutUint32(ret.Data[i*bytePerNumber:(i+1)*bytePerNumber], uint32(res)) + case 16: + binary.LittleEndian.PutUint16(ret.Data[i*bytePerNumber:(i+1)*bytePerNumber], uint16(res)) + case 8: + ret.Data[i] = uint8(res) + } + } + return ret, nil +} + +// NUintM2AsciiToMqtt is the generic approach to convert numberAmount occurrences of numbers with numberWidth bits size. +// Allowed values for numberAmount are 1-8. +// Allowed values for numberWidth are 8, 16, 32 or 64 +// numberAmount*numberWidth shall not be larger than 64 +// input has to Contain the Data that shall be converted. The Size of the CAN-Frame has to fit the expected size. +// If we have for example 1 amount of 32-Bits numbers the CAN-Frame size input.Length has to be 4 (bytes). +// If the size fits, the Data is split up in numberAmount pieces and are then processed to a string representation +// via strconv.FormatUint. +// The successful return value is a byte-slice that represents the converted strings joined with a space between them. +func NUintM2AsciiToMqtt(numberAmount, numberWidth uint, input can.Frame) ([]byte, error) { + if !(numberWidth == 8 || numberWidth == 16 || numberWidth == 32 || numberWidth == 64) { + return []byte{}, errors.New(fmt.Sprintf("numberWitdh %d uknown please choose one of 8, 16. 32 or 64\n", numberWidth)) + } + if numberWidth*numberAmount > 64 { + return []byte{}, errors.New(fmt.Sprintf("%d number of %d bit width would not fit into a 8 byte CAN-Frame %d exceeds 64 bits.\n", numberAmount, numberWidth, numberAmount*numberWidth)) + } + if input.Length != uint8((numberWidth*numberAmount)>>3) { + return []byte{}, errors.New(fmt.Sprintf("Input is of wrong length: %d, expected %d because of %d numbers of %d-bits.", input.Length, (numberAmount*numberWidth)>>3, numberAmount, numberWidth)) + } + var returnStrings []string + bytePerNumber := numberWidth >> 3 + for i := uint(0); i < numberAmount; i++ { + switch numberWidth { + case 64: + returnStrings = append(returnStrings, strconv.FormatUint(uint64(binary.LittleEndian.Uint64(input.Data[i*bytePerNumber:(i+1)*bytePerNumber])), 10)) + case 32: + returnStrings = append(returnStrings, strconv.FormatUint(uint64(binary.LittleEndian.Uint32(input.Data[i*bytePerNumber:(i+1)*bytePerNumber])), 10)) + case 16: + returnStrings = append(returnStrings, strconv.FormatUint(uint64(binary.LittleEndian.Uint16(input.Data[i*bytePerNumber:(i+1)*bytePerNumber])), 10)) + case 8: + returnStrings = append(returnStrings, strconv.FormatUint(uint64(input.Data[i]), 10)) + } + } + return []byte(strings.Join(returnStrings, " ")), nil +} diff --git a/src/internal/main.go b/src/internal/main.go index 5d2d50a..74aaf39 100644 --- a/src/internal/main.go +++ b/src/internal/main.go @@ -4,24 +4,36 @@ import ( "bufio" // Reader "encoding/csv" // CSV Management "fmt" // print :) - "io" // EOF const - "log" // error management - "os" // open files - "strconv" // parse strings + "github.com/brutella/can" + "github.com/c3re/can2mqtt/internal/convertfunctions" + "io" // EOF const + "log" // error management + "os" // open files + "strconv" // parse strings "sync" ) +type convertToCan func(input []byte) (can.Frame, error) +type convertToMqtt func(input can.Frame) ([]byte, error) + +type ConvertMode interface { + convertToCan + convertToMqtt +} + // can2mqtt is a struct that represents the internal type of // one line of the can2mqtt.csv file. It has // the same three fields as the can2mqtt.csv file: CAN-ID, // conversion method and MQTT-Topic. type can2mqtt struct { - canId int + canId uint32 convMethod string + toCan convertToCan + toMqtt convertToMqtt mqttTopic string } -var pairFromID map[int]*can2mqtt // c2m pair (lookup from ID) +var pairFromID map[uint32]*can2mqtt // c2m pair (lookup from ID) var pairFromTopic map[string]*can2mqtt // c2m pair (lookup from Topic) var dbg = false // verbose on off [-v] var ci = "can0" // the CAN-Interface [-c] @@ -113,7 +125,7 @@ func readC2MPFromFile(filename string) { } r := csv.NewReader(bufio.NewReader(file)) - pairFromID = make(map[int]*can2mqtt) + pairFromID = make(map[uint32]*can2mqtt) pairFromTopic = make(map[string]*can2mqtt) for { record, err := r.Read() @@ -121,20 +133,183 @@ func readC2MPFromFile(filename string) { if err == io.EOF { break } - canID, err := strconv.Atoi(record[0]) + tmp, err := strconv.ParseUint(record[0], 10, 32) + if err != nil { + fmt.Printf("Error while converting can-ID: %s :%s\n", record[0], err.Error()) + continue + } + canID := uint32(tmp) convMode := record[1] topic := record[2] if isInSlice(canID, topic) { panic("main: each ID and each topic is only allowed once!") } - pairFromID[canID] = &can2mqtt{ - canId: canID, - convMethod: convMode, - mqttTopic: topic, + switch convMode { + case "16bool2ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.SixteenBool2AsciiToCan, + toMqtt: convertfunctions.SixteenBool2AsciiToMqtt, + } + case "uint82ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.Uint82AsciiToCan, + toMqtt: convertfunctions.Uint82AsciiToMqtt, + } + case "uint162ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.Uint162AsciiToCan, + toMqtt: convertfunctions.Uint162AsciiToMqtt, + } + case "uint322ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.Uint322AsciiToCan, + toMqtt: convertfunctions.Uint322AsciiToMqtt, + } + case "uint642ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.Uint642AsciiToCan, + toMqtt: convertfunctions.Uint642AsciiToMqtt, + } + case "2uint322ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.TwoUint322AsciiToCan, + toMqtt: convertfunctions.TwoUint322AsciiToMqtt, + } + case "4uint162ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.FourUint162AsciiToCan, + toMqtt: convertfunctions.FourUint162AsciiToMqtt, + } + case "4uint82ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.FourUint82AsciiToCan, + toMqtt: convertfunctions.FourUint82AsciiToMqtt, + } + case "8uint82ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.EightUint82AsciiToCan, + toMqtt: convertfunctions.EightUint82AsciiToMqtt, + } + // Int methodes come here now + case "int82ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.Int82AsciiToCan, + toMqtt: convertfunctions.Int82AsciiToMqtt, + } + case "int162ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.Int162AsciiToCan, + toMqtt: convertfunctions.Int162AsciiToMqtt, + } + case "int322ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.Int322AsciiToCan, + toMqtt: convertfunctions.Int322AsciiToMqtt, + } + case "int642ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.Int642AsciiToCan, + toMqtt: convertfunctions.Int642AsciiToMqtt, + } + case "2int322ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.TwoInt322AsciiToCan, + toMqtt: convertfunctions.TwoInt322AsciiToMqtt, + } + case "4int162ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.FourInt162AsciiToCan, + toMqtt: convertfunctions.FourInt162AsciiToMqtt, + } + case "4int82ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.FourInt82AsciiToCan, + toMqtt: convertfunctions.FourInt82AsciiToMqtt, + } + case "8int82ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.EightInt82AsciiToCan, + toMqtt: convertfunctions.EightInt82AsciiToMqtt, + } + case "bytecolor2colorcode": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.ByteColor2ColorCodeToCan, + toMqtt: convertfunctions.ByteColor2ColorCodeToMqtt, + } + case "pixelbin2ascii": + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.PixelBin2AsciiToCan, + toMqtt: convertfunctions.PixelBin2AsciiToMqtt, + } + default: + pairFromID[canID] = &can2mqtt{ + canId: canID, + convMethod: convMode, + mqttTopic: topic, + toCan: convertfunctions.NoneToCan, + toMqtt: convertfunctions.NoneToMqtt, + } } pairFromTopic[topic] = pairFromID[canID] - mqttSubscribe(topic) // TODO move to append function - canSubscribe(uint32(canID)) // TODO move to append function + mqttSubscribe(topic) // TODO move to append function + canSubscribe(canID) // TODO move to append function } if dbg { fmt.Printf("main: the following CAN-MQTT pairs have been extracted:\n") @@ -146,7 +321,7 @@ func readC2MPFromFile(filename string) { } // check function to check if a topic or an ID is in the slice -func isInSlice(canId int, mqttTopic string) bool { +func isInSlice(canId uint32, mqttTopic string) bool { if pairFromID[canId] != nil { if dbg { fmt.Printf("main: The ID %d or the Topic %s is already in the list!\n", canId, mqttTopic) @@ -163,7 +338,7 @@ func isInSlice(canId int, mqttTopic string) bool { } // get the corresponding ID for a given topic -func getIdFromTopic(topic string) int { +func getIdFromTopic(topic string) uint32 { return pairFromTopic[topic].canId } @@ -173,11 +348,11 @@ func getConvModeFromTopic(topic string) string { } // get the convertMode for a given ID -func getConvModeFromId(canId int) string { +func getConvModeFromId(canId uint32) string { return pairFromID[canId].convMethod } // get the corresponding topic for an ID -func getTopicFromId(canId int) string { +func getTopicFromId(canId uint32) string { return pairFromID[canId].mqttTopic } diff --git a/src/internal/mqtthandling.go b/src/internal/mqtthandling.go index 754be1f..e9fa04a 100644 --- a/src/internal/mqtthandling.go +++ b/src/internal/mqtthandling.go @@ -75,7 +75,7 @@ func mqttUnsubscribe(topic string) { } // publish a new message -func mqttPublish(topic string, payload string) { +func mqttPublish(topic string, payload []byte) { if dbg { fmt.Printf("mqtthandler: sending message: \"%s\" to topic: \"%s\"\n", payload, topic) } diff --git a/src/internal/receivehandling.go b/src/internal/receivehandling.go index 6692093..6a9ec87 100644 --- a/src/internal/receivehandling.go +++ b/src/internal/receivehandling.go @@ -14,12 +14,17 @@ func handleCAN(cf can.Frame) { if dbg { fmt.Printf("receivehandler: received CANFrame: ID: %d, len: %d, payload %s\n", cf.ID, cf.Length, cf.Data) } - mqttPayload := convert2MQTT(int(cf.ID), int(cf.Length), cf.Data) - if dbg { - fmt.Printf("receivehandler: converted String: %s\n", mqttPayload) - } - topic := getTopicFromId(int(cf.ID)) + // Only do conversions when necessary if dirMode != 2 { + mqttPayload, err := pairFromID[cf.ID].toMqtt(cf) + if err != nil { + fmt.Printf("Error while converting CAN Frame with ID %d and payload %s: %s\n", cf.ID, cf.Data, err.Error()) + return + } + if dbg { + fmt.Printf("receivehandler: converted String: %s\n", mqttPayload) + } + topic := getTopicFromId(cf.ID) mqttPublish(topic, mqttPayload) fmt.Printf("ID: %d len: %d data: %X -> topic: \"%s\" message: \"%s\"\n", cf.ID, cf.Length, cf.Data, topic, mqttPayload) } @@ -33,9 +38,18 @@ func handleMQTT(_ MQTT.Client, msg MQTT.Message) { if dbg { fmt.Printf("receivehandler: received message: topic: %s, msg: %s\n", msg.Topic(), msg.Payload()) } - cf := convert2CAN(msg.Topic(), string(msg.Payload())) if dirMode != 1 { + //cf := convert2CAN(msg.Topic(), string(msg.Payload())) + cf, err := pairFromTopic[msg.Topic()].toCan(msg.Payload()) + if err != nil { + fmt.Printf("Error while converting MQTT-Message with Topic %s payload %s: %s\n", msg.Topic(), msg.Payload(), err.Error()) + return + } + if dbg { + fmt.Printf("receivehandler: converted data: %s\n", cf.Data) + } + cf.ID = uint32(pairFromTopic[msg.Topic()].canId) canPublish(cf) fmt.Printf("ID: %d len: %d data: %X <- topic: \"%s\" message: \"%s\"\n", cf.ID, cf.Length, cf.Data, msg.Topic(), msg.Payload()) }