diff --git a/README.md b/README.md index 2967a56..abc49f5 100644 --- a/README.md +++ b/README.md @@ -54,30 +54,14 @@ Explanation for the 1st Line: For example our Doorstatus is published on the CAN ## convert-modes Here they are: -| 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 address colorized pixels. MQTT-wise you can insert a string like "<0-255> #RRGGBB" which 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 | +| 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 address colorized pixels. MQTT-wise you can insert a string like "<0-255> #RRGGBB" which 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 | +| `{i}[u]int{b}2ascii` | `i` is the amount of instances of numbers in the CAN-Frame/the MQTT-message. Valid instance amounts are currently 1,2,4 and 8. Although other combinations are possible. Might be added in the future, if the need arises. `b` is the size of each number in bits. Supported values are 8,16,32 and 64. You can use either one unsigned or signed integers. This are all possible combinations: `int82ascii`, `2int82ascii`, `4int82ascii`, `8int82ascii`, `int162ascii`, `2int162ascii`, `4int162ascii`, `int322ascii`, `2int322ascii`, `int642ascii`, `uint82ascii`, `2uint82ascii`, `4uint82ascii`, `8uint82ascii`, `uint162ascii`, `2uint162ascii`, `4uint162ascii`, `uint322ascii`, `2uint322ascii`, `uint642ascii` | +| ## Unidirectional Mode @@ -101,14 +85,20 @@ sudo ip link set vcan0 up Now you can use your new interface `vcan0` as socket-can interface with can2mqtt. ## Debugging -To debug can2mqtts behaviour you need to be able to send and receive CAN frames and MQTT messages. For MQTT I recommend [mosquitto](https://mosquitto.org/) with its `mosquitto_pub` and `mosquitto_sub` commands. For CAN i recommend [can-utils]() with its tools `cansend` and `candump`. -## Add a convert-Mode +To debug the behaviour of can2mqtt you need to be able to send and receive CAN frames and MQTT messages. For MQTT I recommend [mosquitto](https://mosquitto.org/) with its `mosquitto_pub` and `mosquitto_sub` commands. For CAN i recommend [can-utils]() with its tools `cansend` and `candump`. +## Add a convert-Mode If you want to add a convert-Mode think about a name. This is the name that you can later refer to when you want to -use your convert-Mode in the `can2mqtt.csv` config-file. In this example the name of the new convert-Mode is `"mymode"`. -Next, add the new convert-Mode in the [switch-case block in `src/main.go`](./src/main.go#L247). In the new case -statement you refer to two functions. One is called when a MQTT-message is received and the other one when a CAN-frame is -received. Change the contents of those functions to the behaviour that you seek for. Mockup code for `MyModeToCan` and -`MyModeToMqtt` can be found in [mymode.go](./src/mymode.go). +use your convert-Mode in the `can2mqtt.csv` config-file. Now, use the file `src/convertmode/mymode.go` as a template for your own convertmode. Copy that file to `src/convertmode/.go`. Now change all occurrences of "MyMode" with your preferred Name (Lets say `YourNewMode` in this example). Note that it has to start with an upper-case letter, so that it is usable outside of this package. Next you have to write three functions (implement the `ConvertMode` interface): +1. A conversion method from CAN -> MQTT: `ToMqtt(input can.Frame) ([]byte, error)` +2. A conversion method from MQTT -> CAN: `ToCan(input []byte) (can.Frame, error)` +3. A `String() string` method that reports the name of that convertmode. This method is used in some log-messages + +Your almost done, the last step is to "register" your new convertmode. To do so add the following line to [`src/main.go#L72`](./src/main.go#L72) +```go +convertModeFromString[convertmode.{}.String()] = convertmode.{} +``` + +Now you can use your new convertmode in your `can2mqtt.csv` config File. Use the string that your return in the `String()` function as the name of the convertmode. In the `mymode.go` code this is `"mymode"`. Good luck & happy hacking ✌ \ No newline at end of file diff --git a/src/canbus.go b/src/can.go similarity index 100% rename from src/canbus.go rename to src/can.go diff --git a/src/convertfunctions/int2ascii.go b/src/convertfunctions/int2ascii.go deleted file mode 100644 index 332a7d6..0000000 --- a/src/convertfunctions/int2ascii.go +++ /dev/null @@ -1,152 +0,0 @@ -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 an 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/convertfunctions/none.go b/src/convertfunctions/none.go deleted file mode 100644 index 01d07b0..0000000 --- a/src/convertfunctions/none.go +++ /dev/null @@ -1,16 +0,0 @@ -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/convertfunctions/uint2ascii.go b/src/convertfunctions/uint2ascii.go deleted file mode 100644 index 82dc8ce..0000000 --- a/src/convertfunctions/uint2ascii.go +++ /dev/null @@ -1,152 +0,0 @@ -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 an 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(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/convertfunctions/bytecolor2colorcode.go b/src/convertmode/bytecolor2colorcode.go similarity index 74% rename from src/convertfunctions/bytecolor2colorcode.go rename to src/convertmode/bytecolor2colorcode.go index 14909cf..f2b44d9 100644 --- a/src/convertfunctions/bytecolor2colorcode.go +++ b/src/convertmode/bytecolor2colorcode.go @@ -1,4 +1,4 @@ -package convertfunctions +package convertmode import ( "encoding/hex" @@ -8,7 +8,13 @@ import ( "strings" ) -func ByteColor2ColorCodeToCan(input []byte) (can.Frame, error) { +type ByteColor2ColorCode struct{} + +func (_ ByteColor2ColorCode) String() string { + return "bytecolor2colorcode" +} + +func (_ ByteColor2ColorCode) ToCan(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))) @@ -22,10 +28,10 @@ func ByteColor2ColorCodeToCan(input []byte) (can.Frame, error) { return returner, nil } -func ByteColor2ColorCodeToMqtt(input can.Frame) ([]byte, error) { +func (_ ByteColor2ColorCode) ToMqtt(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 -} +} \ No newline at end of file diff --git a/src/convertmode/int2ascii.go b/src/convertmode/int2ascii.go new file mode 100644 index 0000000..8f2d409 --- /dev/null +++ b/src/convertmode/int2ascii.go @@ -0,0 +1,99 @@ +package convertmode + +import ( + "encoding/binary" + "errors" + "fmt" + "github.com/brutella/can" + "strconv" + "strings" +) + +// Int2Ascii is a convertMode that can take multiple signed integers of one size. +// instances describe the amount of numbers that should be converted, bits is the size of each number +// instances * bits must fit into 64 bits. +type Int2Ascii struct { + instances, bits uint +} + +func (i2a Int2Ascii) String() string { + instanceString := "" + if i2a.instances > 1 { + instanceString = fmt.Sprintf("%d", i2a.instances) + } + return fmt.Sprintf("%sint%d2ascii", instanceString, i2a.bits) +} + +func NewInt2Ascii(instances, bits uint) (Int2Ascii, error) { + if !(bits == 8 || bits == 16 || bits == 32 || bits == 64) { + return Int2Ascii{}, errors.New(fmt.Sprintf("bitsize %d not supported, please choose one of 8, 16. 32 or 64\n", bits)) + } + if bits*instances > 64 { + return Int2Ascii{}, errors.New(fmt.Sprintf("%d instances of %d bit size would not fit into a 8 byte CAN-Frame. %d exceeds 64 bits.\n", instances, bits, instances*bits)) + } + return Int2Ascii{instances, bits}, nil +} + +// ToCan is the generic approach to convert instances instances of numbers with bits bits size. +// Allowed values for instances are 1-8. +// Allowed values for bits are 8, 16, 32 or 64 +// instances*bits must 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 instances. +// If the amount of fields matches, each field is converted to an uint of size bits. The results are then added to the CAN-frame. +func (i2a Int2Ascii) ToCan(input []byte) (can.Frame, error) { + splitInput := strings.Fields(string(input)) + if uint(len(splitInput)) != i2a.instances { + return can.Frame{}, errors.New(fmt.Sprintf("input does not contain exactly %d numbers seperated by whitespace", i2a.instances)) + } + var ret can.Frame + ret.Length = uint8((i2a.instances * i2a.bits) >> 3) + bytePerNumber := i2a.bits >> 3 + for i := uint(0); i < i2a.instances; i++ { + res, err := strconv.ParseInt(splitInput[i], 10, int(i2a.bits)) + if err != nil { + return can.Frame{}, errors.New(fmt.Sprintf("Error while converting string %d: %s, %s", i, splitInput[i], err)) + } + switch i2a.bits { + 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 +} + +// ToMqtt is the generic approach to convert instances instances of numbers with bits bits size. +// Allowed values for instances are 1-8. +// Allowed values for bits are 8, 16, 32 or 64 +// instances*bits must 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 instances 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 (i2a Int2Ascii) ToMqtt(input can.Frame) ([]byte, error) { + if input.Length != uint8((i2a.bits*i2a.instances)>>3) { + return []byte{}, errors.New(fmt.Sprintf("Input is of wrong length: %d, expected %d because of %d numbers of %d-bits.", input.Length, (i2a.instances*i2a.bits)>>3, i2a.instances, i2a.bits)) + } + var returnStrings []string + bytePerNumber := i2a.bits >> 3 + for i := uint(0); i < i2a.instances; i++ { + switch i2a.bits { + 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 +} \ No newline at end of file diff --git a/src/convertfunctions/mymode.go b/src/convertmode/mymode.go similarity index 85% rename from src/convertfunctions/mymode.go rename to src/convertmode/mymode.go index f72572f..4ead25c 100644 --- a/src/convertfunctions/mymode.go +++ b/src/convertmode/mymode.go @@ -1,4 +1,4 @@ -package convertfunctions +package convertmode import ( "errors" @@ -7,7 +7,13 @@ import ( const mockErr string = "I am just mockup-code and not supposed to be actually used, implement something useful here" -func MyModeToCan(input []byte) (can.Frame, error) { +type MyMode struct{} + +func (_ MyMode) String() string { + return "mymode" +} + +func (_ MyMode) ToCan(input []byte) (can.Frame, error) { /* This is your area to create your convertMode (Receive MQTT, convert to CAN). You can find the payload of the received MQTT-Message @@ -26,7 +32,7 @@ func MyModeToCan(input []byte) (can.Frame, error) { return can.Frame{}, errors.New(mockErr) } -func MyModeToMqtt(input can.Frame) ([]byte, error) { +func (_ MyMode) ToMqtt(input can.Frame) ([]byte, error) { /* This is your area to create your convertMode (Receive CAN, convert to MQTT). You can find the received CAN-Frame in the can.Frame input. You can craft your returning MQTT-Payload here. @@ -37,4 +43,4 @@ func MyModeToMqtt(input can.Frame) ([]byte, error) { return input.Data[:input.Length], nil */ return []byte{}, errors.New(mockErr) -} +} \ No newline at end of file diff --git a/src/convertmode/none.go b/src/convertmode/none.go new file mode 100644 index 0000000..a5ca032 --- /dev/null +++ b/src/convertmode/none.go @@ -0,0 +1,35 @@ +package convertmode + +import "github.com/brutella/can" + +type None struct{} + +func (_ None) String() string { + return "none" +} + +func (_ None) ToCan(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 (_ None) ToMqtt(input can.Frame) ([]byte, error) { + return input.Data[:input.Length], nil +} + +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 +} \ No newline at end of file diff --git a/src/convertfunctions/none_test.go b/src/convertmode/none_test.go similarity index 99% rename from src/convertfunctions/none_test.go rename to src/convertmode/none_test.go index 5e8a215..c6d91a3 100644 --- a/src/convertfunctions/none_test.go +++ b/src/convertmode/none_test.go @@ -1,4 +1,4 @@ -package convertfunctions +package convertmode import ( "github.com/brutella/can" @@ -195,4 +195,4 @@ func FuzzNoneToMqtt(f *testing.F) { } }) -} +} \ No newline at end of file diff --git a/src/convertfunctions/pixelbin2ascii.go b/src/convertmode/pixelbin2ascii.go similarity index 85% rename from src/convertfunctions/pixelbin2ascii.go rename to src/convertmode/pixelbin2ascii.go index f2b6881..0190fb3 100644 --- a/src/convertfunctions/pixelbin2ascii.go +++ b/src/convertmode/pixelbin2ascii.go @@ -1,4 +1,4 @@ -package convertfunctions +package convertmode import ( "encoding/hex" @@ -9,7 +9,13 @@ import ( "strings" ) -func PixelBin2AsciiToCan(input []byte) (can.Frame, error) { +type PixelBin2Ascii struct{} + +func (_ PixelBin2Ascii) String() string { + return "pixelbin2ascii" +} + +func (_ PixelBin2Ascii) ToCan(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))) @@ -32,11 +38,11 @@ func PixelBin2AsciiToCan(input []byte) (can.Frame, error) { return returner, nil } -func PixelBin2AsciiToMqtt(input can.Frame) ([]byte, error) { +func (_ PixelBin2Ascii) ToMqtt(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 -} +} \ No newline at end of file diff --git a/src/convertfunctions/sixteenbool2ascii.go b/src/convertmode/sixteenbool2ascii.go similarity index 78% rename from src/convertfunctions/sixteenbool2ascii.go rename to src/convertmode/sixteenbool2ascii.go index 4ccf56d..8322f80 100644 --- a/src/convertfunctions/sixteenbool2ascii.go +++ b/src/convertmode/sixteenbool2ascii.go @@ -1,4 +1,4 @@ -package convertfunctions +package convertmode import ( "errors" @@ -8,7 +8,13 @@ import ( "strings" ) -func SixteenBool2AsciiToCan(input []byte) (can.Frame, error) { +type SixteenBool2Ascii struct{} + +func (_ SixteenBool2Ascii) String() string { + return "16bool2ascii" +} + +func (_ SixteenBool2Ascii) ToCan(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") @@ -28,7 +34,7 @@ func SixteenBool2AsciiToCan(input []byte) (can.Frame, error) { } return can.Frame{Length: 2, Data: returnData}, nil } -func SixteenBool2AsciiToMqtt(input can.Frame) ([]byte, error) { +func (_ SixteenBool2Ascii) ToMqtt(input can.Frame) ([]byte, error) { var returnStrings [16]string for i := 0; i < 16; i++ { if (input.Data[i>>3]>>(i%8))&0x1 == 1 { @@ -38,4 +44,4 @@ func SixteenBool2AsciiToMqtt(input can.Frame) ([]byte, error) { } } return []byte(strings.Join(returnStrings[:], " ")), nil -} +} \ No newline at end of file diff --git a/src/convertmode/uint2ascii.go b/src/convertmode/uint2ascii.go new file mode 100644 index 0000000..80ff872 --- /dev/null +++ b/src/convertmode/uint2ascii.go @@ -0,0 +1,99 @@ +package convertmode + +import ( + "encoding/binary" + "errors" + "fmt" + "github.com/brutella/can" + "strconv" + "strings" +) + +// Uint2Ascii is a convertMode that can take multiple signed integers of one size. +// instances describe the amount of numbers that should be converted, bits is the size of each number +// instances * bits must fit into 64 bits. +type Uint2Ascii struct { + instances, bits uint +} + +func (u2a Uint2Ascii) String() string { + instanceString := "" + if u2a.instances > 1 { + instanceString = fmt.Sprintf("%d", u2a.instances) + } + return fmt.Sprintf("%suint%d2ascii", instanceString, u2a.bits) +} + +func NewUint2Ascii(instances, bits uint) (Uint2Ascii, error) { + if !(bits == 8 || bits == 16 || bits == 32 || bits == 64) { + return Uint2Ascii{}, errors.New(fmt.Sprintf("bitsize %d not supported, please choose one of 8, 16. 32 or 64\n", bits)) + } + if bits*instances > 64 { + return Uint2Ascii{}, errors.New(fmt.Sprintf("%d instances of %d bit size would not fit into a 8 byte CAN-Frame. %d exceeds 64 bits.\n", instances, bits, instances*bits)) + } + return Uint2Ascii{instances, bits}, nil +} + +// ToCan is the generic approach to convert instances occurrences of numbers with bits bits size. +// Allowed values for instances are 1-8. +// Allowed values for bits are 8, 16, 32 or 64 +// instances*bits must 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 instances. +// If the amount of fields matches, each field is converted to an uint of size bits. The results are then added to the CAN-frame. +func (u2a Uint2Ascii) ToCan(input []byte) (can.Frame, error) { + splitInput := strings.Fields(string(input)) + if uint(len(splitInput)) != u2a.instances { + return can.Frame{}, errors.New(fmt.Sprintf("input does not contain exactly %d numbers seperated by whitespace", u2a.instances)) + } + var ret can.Frame + ret.Length = uint8((u2a.instances * u2a.bits) >> 3) + bytePerNumber := u2a.bits >> 3 + for i := uint(0); i < u2a.instances; i++ { + res, err := strconv.ParseUint(splitInput[i], 10, int(u2a.bits)) + if err != nil { + return can.Frame{}, errors.New(fmt.Sprintf("Error while converting string %d: %s, %s", i, splitInput[i], err)) + } + switch u2a.bits { + 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 +} + +// ToMqtt is the generic approach to convert instances occurrences of numbers with bits bits size. +// Allowed values for instances are 1-8. +// Allowed values for bits are 8, 16, 32 or 64 +// instances*bits 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 instances 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 (u2a Uint2Ascii) ToMqtt(input can.Frame) ([]byte, error) { + if input.Length != uint8((u2a.bits*u2a.instances)>>3) { + return []byte{}, errors.New(fmt.Sprintf("Input is of wrong length: %d, expected %d because of %d numbers of %d-bits.", input.Length, (u2a.instances*u2a.bits)>>3, u2a.instances, u2a.bits)) + } + var returnStrings []string + bytePerNumber := u2a.bits >> 3 + for i := uint(0); i < u2a.instances; i++ { + switch u2a.bits { + case 64: + returnStrings = append(returnStrings, strconv.FormatUint(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 +} \ No newline at end of file diff --git a/src/main.go b/src/main.go index 8d93486..0fa99aa 100644 --- a/src/main.go +++ b/src/main.go @@ -4,7 +4,7 @@ import ( "bufio" // Reader "encoding/csv" // CSV Management "flag" - "github.com/c3re/can2mqtt/convertfunctions" + "github.com/c3re/can2mqtt/convertmode" "io" // EOF const "log" // error management "log/slog" @@ -16,6 +16,7 @@ import ( var ( pairFromID map[uint32]*can2mqtt // c2m pair (lookup from ID) pairFromTopic map[string]*can2mqtt // c2m pair (lookup from Topic) + convertModeFromString map[string]ConvertMode debugLog bool canInterface, mqttConnection, configFile string version = "dev" @@ -63,6 +64,32 @@ func readC2MPFromFile(filename string) { r.FieldsPerRecord = 3 pairFromID = make(map[uint32]*can2mqtt) pairFromTopic = make(map[string]*can2mqtt) + convertModeFromString = make(map[string]ConvertMode) + + // initialize all convertModes + convertModeFromString[convertmode.None{}.String()] = convertmode.None{} + convertModeFromString[convertmode.SixteenBool2Ascii{}.String()] = convertmode.SixteenBool2Ascii{} + convertModeFromString[convertmode.PixelBin2Ascii{}.String()] = convertmode.PixelBin2Ascii{} + convertModeFromString[convertmode.ByteColor2ColorCode{}.String()] = convertmode.ByteColor2ColorCode{} + convertModeFromString[convertmode.MyMode{}.String()] = convertmode.MyMode{} + // Dynamically create int and uint convertmodes + for _, bits := range []uint{8, 16, 32, 64} { + for _, instances := range []uint{1, 2, 4, 8} { + if bits*instances <= 64 { + // int + cmi, _ := convertmode.NewInt2Ascii(instances, bits) + convertModeFromString[cmi.String()] = cmi + // uint + cmu, _ := convertmode.NewUint2Ascii(instances, bits) + convertModeFromString[cmu.String()] = cmu + } + } + } + if debugLog { + for _, cm := range convertModeFromString { + slog.Debug("convertmode initialized", "convertmode", cm) + } + } for { record, err := r.Read() // Stop at EOF. @@ -90,176 +117,16 @@ func readC2MPFromFile(filename string) { slog.Warn("skipping line duplicate topic", "topic", topic, "line", line) continue } - 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 methods 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, - } - case "mymode": - pairFromID[canID] = &can2mqtt{ - canId: canID, - convMethod: convMode, - mqttTopic: topic, - toCan: convertfunctions.MyModeToCan, - toMqtt: convertfunctions.MyModeToMqtt, - } - default: - pairFromID[canID] = &can2mqtt{ - canId: canID, - convMethod: convMode, - mqttTopic: topic, - toCan: convertfunctions.NoneToCan, - toMqtt: convertfunctions.NoneToMqtt, - } + + if convertModeFromString[convMode] == nil { + slog.Warn("skipping line, unsupported convertMode ", "convertMode", convMode, "line", line) + continue + } + + pairFromID[canID] = &can2mqtt{ + canId: canID, + convertMode: convertModeFromString[convMode], + mqttTopic: topic, } pairFromTopic[topic] = pairFromID[canID] mqttSubscribe(topic) // TODO move to append function @@ -267,7 +134,7 @@ func readC2MPFromFile(filename string) { } for _, c2mp := range pairFromID { - slog.Debug("extracted pair", "id", c2mp.canId, "convertmode", c2mp.convMethod, "topic", c2mp.mqttTopic) + slog.Debug("extracted pair", "id", c2mp.canId, "convertmode", c2mp.convertMode, "topic", c2mp.mqttTopic) } } @@ -282,4 +149,4 @@ func isTopicInSlice(mqttTopic string) bool { // get the corresponding topic for an ID func getTopicFromId(canId uint32) string { return pairFromID[canId].mqttTopic -} +} \ No newline at end of file diff --git a/src/receiving.go b/src/receive.go similarity index 73% rename from src/receiving.go rename to src/receive.go index ae94b3e..97d4cb4 100644 --- a/src/receiving.go +++ b/src/receive.go @@ -14,15 +14,15 @@ func handleCAN(cf can.Frame) { slog.Debug("received CANFrame", "id", cf.ID, "len", cf.Length, "data", cf.Data) // Only do conversions when necessary if dirMode != 2 { - mqttPayload, err := pairFromID[cf.ID].toMqtt(cf) + mqttPayload, err := pairFromID[cf.ID].convertMode.ToMqtt(cf) if err != nil { - slog.Warn("conversion to MQTT message unsuccessful", "convertmode", pairFromID[cf.ID].convMethod, "error", err) + slog.Warn("conversion to MQTT message unsuccessful", "convertmode", pairFromID[cf.ID].convertMode, "error", err) return } topic := getTopicFromId(cf.ID) mqttPublish(topic, mqttPayload) // this is the most common log-message, craft with care... - slog.Info("CAN -> MQTT", "ID", cf.ID, "len", cf.Length, "data", cf.Data, "convertmode", pairFromID[cf.ID].convMethod, "topic", topic, "message", mqttPayload) + slog.Info("CAN -> MQTT", "ID", cf.ID, "len", cf.Length, "data", cf.Data, "convertmode", pairFromID[cf.ID].convertMode, "topic", topic, "message", mqttPayload) } } @@ -34,13 +34,13 @@ func handleMQTT(_ MQTT.Client, msg MQTT.Message) { slog.Debug("received message", "topic", msg.Topic(), "payload", msg.Payload()) if dirMode != 1 { - cf, err := pairFromTopic[msg.Topic()].toCan(msg.Payload()) + cf, err := pairFromTopic[msg.Topic()].convertMode.ToCan(msg.Payload()) if err != nil { - slog.Warn("conversion to CAN-Frame unsuccessful", "convertmode", pairFromTopic[msg.Topic()].convMethod, "error", err) + slog.Warn("conversion to CAN-Frame unsuccessful", "convertmode", pairFromTopic[msg.Topic()].convertMode, "error", err) return } cf.ID = pairFromTopic[msg.Topic()].canId canPublish(cf) - slog.Info("CAN <- MQTT", "ID", cf.ID, "len", cf.Length, "data", cf.Data, "convertmode", pairFromTopic[msg.Topic()].convMethod, "topic", msg.Topic(), "message", msg.Payload()) + slog.Info("CAN <- MQTT", "ID", cf.ID, "len", cf.Length, "data", cf.Data, "convertmode", pairFromTopic[msg.Topic()].convertMode, "topic", msg.Topic(), "message", msg.Payload()) } -} +} \ No newline at end of file diff --git a/src/types.go b/src/types.go index cb87f5f..32078a4 100644 --- a/src/types.go +++ b/src/types.go @@ -1,18 +1,26 @@ package main -import "github.com/brutella/can" +import ( + "fmt" + "github.com/brutella/can" +) -type convertToCan func(input []byte) (can.Frame, error) -type convertToMqtt func(input can.Frame) ([]byte, error) +// ConvertMode is the interface that defines the two methods necessary +// to handle MQTT-Messages (ToMqtt) as well as CAN-Frames(ToCan). It also includes fmt.Stringer +// to make types that implement it print their human-readable convertmode, as it +// appears in the can2mqtt file. +type ConvertMode interface { + ToCan(input []byte) (can.Frame, error) + ToMqtt(input can.Frame) ([]byte, error) + fmt.Stringer +} // 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 uint32 - convMethod string - toCan convertToCan - toMqtt convertToMqtt - mqttTopic string -} + canId uint32 + convertMode ConvertMode + mqttTopic string +} \ No newline at end of file