diff --git a/README.md b/README.md index 017bc09..1bb2c8d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Example: 123,uint322ascii,huette/all/000/airmonitor/sensors/pm10 ``` -Explanation for the 1st Line: For example our Doorstatus is published on the CAN-Bus every second with the CAN-ID 112 (decimal). can2mqtt will take everything thats published there and will push it through to mqtt-topic huette/all/a03/door/sensors/opened. +Explanation for the 1st Line: For example our Doorstatus is published on the CAN-Bus every second with the CAN-ID 112 (decimal). can2mqtt will take everything that is published there and will push it through to mqtt-topic huette/all/a03/door/sensors/opened. ## convert-modes Here they are: diff --git a/src/can2mqtt.go b/src/can2mqtt.go index cb3cc5d..c7709ee 100644 --- a/src/can2mqtt.go +++ b/src/can2mqtt.go @@ -39,10 +39,10 @@ func main() { // help function (obvious...) func printHelp() { - fmt.Printf("welcome to the CAN2MQTT bridge!\n\n") - fmt.Printf("Usage: can2mqtt [-f ] [-c ] [-m ] [-v] [-h] [-d ]\n") - fmt.Printf(": a can2mqtt.csv file\n") - fmt.Printf(": a CAN-Interface e.g. can0\n") - fmt.Printf(": connectstring for MQTT. e.g.: tcp://[user:pass@]localhost:1883\n") - fmt.Printf(": directional Mode 0 = bidirectional, 1 = can2mqtt only, 2 = mqtt2can only\n") + _, _ = fmt.Fprintf(os.Stderr, "welcome to the CAN2MQTT bridge!\n\n") + _, _ = fmt.Fprintf(os.Stderr, "Usage: can2mqtt [-f ] [-c ] [-m ] [-v] [-h] [-d ]\n") + _, _ = fmt.Fprintf(os.Stderr, ": a can2mqtt.csv file\n") + _, _ = fmt.Fprintf(os.Stderr, ": a CAN-Interface e.g. can0\n") + _, _ = fmt.Fprintf(os.Stderr, ": connectstring for MQTT. e.g.: tcp://[user:pass@]localhost:1883\n") + _, _ = fmt.Fprintf(os.Stderr, ": directional Mode 0 = bidirectional, 1 = can2mqtt only, 2 = mqtt2can only\n") } diff --git a/src/internal/canbushandling.go b/src/internal/canbus.go similarity index 56% rename from src/internal/canbushandling.go rename to src/internal/canbus.go index feb3e12..6bc9bb2 100644 --- a/src/internal/canbushandling.go +++ b/src/internal/canbus.go @@ -1,11 +1,11 @@ -// Package can2mqtt contains some tools for bridging a CAN-Interface +// Package internal of c3re/can2mqtt contains some tools for bridging a CAN-Interface // and a mqtt-network package internal import ( - "fmt" "github.com/brutella/can" - "log" + "log/slog" + "os" "sync" ) @@ -18,23 +18,21 @@ var bus *can.Bus // CAN-Bus pointer func canStart(canInterface string) { var err error - if dbg { - fmt.Printf("canbushandler: initializing CAN-Bus interface %s\n", canInterface) - } + slog.Debug("canbus: initializing CAN-Bus", "interface", canInterface) bus, err = can.NewBusForInterfaceWithName(canInterface) if err != nil { - if dbg { - fmt.Printf("canbushandler: error while activating CAN-Bus interface: %s\n", canInterface) - } - log.Fatal(err) + slog.Error("canbus: error while initializing CAN-Bus", "error", err) + os.Exit(1) } + slog.Info("canbus: connected to CAN") + slog.Debug("canbus: registering handler") bus.SubscribeFunc(handleCANFrame) + slog.Debug("canbus: starting receive loop") + // will not return if everything is fine err = bus.ConnectAndPublish() if err != nil { - if dbg { - fmt.Printf("canbushandler: error while activating CAN-Bus interface: %s\n", canInterface) - } - log.Fatal(err) + slog.Error("canbus: error while processing CAN-Bus", "error", err) + os.Exit(1) } } @@ -43,18 +41,14 @@ func handleCANFrame(frame can.Frame) { var idSub = false // indicates, whether the id was subscribed or not for _, i := range csi { if i == frame.ID { - if dbg { - fmt.Printf("canbushandler: ID %d is in subscribed list, calling receivehadler.\n", frame.ID) - } + slog.Debug("canbus: received subscribed frame", "id", frame.ID) go handleCAN(frame) idSub = true break } } if !idSub { - if dbg { - fmt.Printf("canbushandler: ID:%d was not subscribed. /dev/nulled that frame...\n", frame.ID) - } + slog.Debug("canbus: ignored unsubscribed frame", "id", frame.ID) } } @@ -63,16 +57,12 @@ func canSubscribe(id uint32) { csiLock.Lock() csi = append(csi, id) csiLock.Unlock() - if dbg { - fmt.Printf("canbushandler: mutex lock+unlock successful. subscribed to ID:%d\n", id) - } + slog.Debug("canbus: successfully subscribed CAN-ID", "id", id) } // expects a CANFrame and sends it func canPublish(frame can.Frame) { - if dbg { - fmt.Println("canbushandler: sending CAN-Frame: ", frame) - } + slog.Debug("canbus: sending CAN-Frame", "frame", frame) // Check if ID is using more than 11-Bits: if frame.ID >= 0x800 { // if so, enable extended frame format @@ -80,9 +70,6 @@ func canPublish(frame can.Frame) { } err := bus.Publish(frame) if err != nil { - if dbg { - fmt.Printf("canbushandler: error while transmitting the CAN-Frame.\n") - } - log.Fatal(err) + slog.Error("canbus: error while publishing CAN-Frame", "error", err) } } diff --git a/src/internal/convertfunctions/bytecolor2colorcode.go b/src/internal/convertfunctions/bytecolor2colorcode.go index 80388d3..14909cf 100644 --- a/src/internal/convertfunctions/bytecolor2colorcode.go +++ b/src/internal/convertfunctions/bytecolor2colorcode.go @@ -17,7 +17,7 @@ func ByteColor2ColorCodeToCan(input []byte) (can.Frame, error) { if err != nil { return can.Frame{}, errors.New(fmt.Sprintf("Error while converting: %s", err.Error())) } - var returner can.Frame = can.Frame{Length: 3} + var returner = can.Frame{Length: 3} copy(res, returner.Data[0:3]) return returner, nil } diff --git a/src/internal/convertfunctions/int2ascii.go b/src/internal/convertfunctions/int2ascii.go index ede2446..332a7d6 100644 --- a/src/internal/convertfunctions/int2ascii.go +++ b/src/internal/convertfunctions/int2ascii.go @@ -79,7 +79,7 @@ func FourInt162AsciiToMqtt(input can.Frame) ([]byte, error) { // 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. +// 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) { diff --git a/src/internal/convertfunctions/pixelbin2ascii.go b/src/internal/convertfunctions/pixelbin2ascii.go index 852f029..f2b6881 100644 --- a/src/internal/convertfunctions/pixelbin2ascii.go +++ b/src/internal/convertfunctions/pixelbin2ascii.go @@ -26,7 +26,7 @@ func PixelBin2AsciiToCan(input []byte) (can.Frame, error) { if err != nil { return can.Frame{}, errors.New(fmt.Sprintf("Error while converting: %s", err.Error())) } - var returner can.Frame = can.Frame{Length: 4} + var returner = can.Frame{Length: 4} returner.Data[0] = uint8(number) copy(res, returner.Data[1:4]) return returner, nil diff --git a/src/internal/convertfunctions/uint2ascii.go b/src/internal/convertfunctions/uint2ascii.go index c0490e5..82dc8ce 100644 --- a/src/internal/convertfunctions/uint2ascii.go +++ b/src/internal/convertfunctions/uint2ascii.go @@ -79,7 +79,7 @@ func FourUint162AsciiToMqtt(input can.Frame) ([]byte, error) { // 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. +// 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) { @@ -139,7 +139,7 @@ func NUintM2AsciiToMqtt(numberAmount, numberWidth uint, input can.Frame) ([]byte 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)) + 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: diff --git a/src/internal/main.go b/src/internal/main.go index 74aaf39..7b5a6f3 100644 --- a/src/internal/main.go +++ b/src/internal/main.go @@ -6,8 +6,9 @@ import ( "fmt" // print :) "github.com/brutella/can" "github.com/c3re/can2mqtt/internal/convertfunctions" - "io" // EOF const - "log" // error management + "io" // EOF const + "log" // error management + "log/slog" "os" // open files "strconv" // parse strings "sync" @@ -46,6 +47,7 @@ var wg sync.WaitGroup // just standard information output. Default is false. func SetDbg(v bool) { dbg = v + slog.SetLogLoggerLevel(slog.LevelDebug) } // SetCi sets the CAN-Interface to use for the CAN side @@ -86,28 +88,8 @@ func SetConfDirMode(s string) { // parses the can2mqtt.csv file and from there everything takes // its course... func Start() { - fmt.Println("Starting can2mqtt") - fmt.Println() - fmt.Println("MQTT-Config: ", cs) - fmt.Println("CAN-Config: ", ci) - fmt.Println("can2mqtt.csv: ", c2mf) - fmt.Print("dirMode: ", dirMode, " (") - if dirMode == 0 { - fmt.Println("bidirectional)") - } - if dirMode == 1 { - fmt.Println("can2mqtt only)") - } - if dirMode == 2 { - fmt.Println("mqtt2can only)") - } - fmt.Print("Debug-Mode: ") - if dbg { - fmt.Println("yes") - } else { - fmt.Println("no") - } - fmt.Println() + log.SetFlags(0) + slog.Info("Starting can2mqtt", "mqtt-config", cs, "can-interface", ci, "can2mqtt.csv", c2mf, "dir-mode", dirMode, "debug", dbg) wg.Add(1) go canStart(ci) // epic parallel shit ;-) mqttStart(cs) @@ -121,10 +103,12 @@ func readC2MPFromFile(filename string) { file, err := os.Open(filename) if err != nil { - log.Fatal(err) + slog.Error("can2mqtt.csv could not be opened", "filename", filename, "error", err) + os.Exit(1) } r := csv.NewReader(bufio.NewReader(file)) + r.FieldsPerRecord = 3 pairFromID = make(map[uint32]*can2mqtt) pairFromTopic = make(map[string]*can2mqtt) for { @@ -133,16 +117,26 @@ func readC2MPFromFile(filename string) { if err == io.EOF { break } + if err != nil { + slog.Warn("skipping line", "filename", filename, "error", err) + continue + } + line, _ := r.FieldPos(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()) + slog.Warn("skipping line, malformed can-ID", "error", err, "line", line) 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!") + if isIDInSlice(canID) { + slog.Warn("skipping line, duplicate ID", "id", canID, "line", line) + continue + } + if isTopicInSlice(topic) { + slog.Warn("skipping line duplicate topic", "topic", topic, "line", line) + continue } switch convMode { case "16bool2ascii": @@ -217,7 +211,7 @@ func readC2MPFromFile(filename string) { toCan: convertfunctions.EightUint82AsciiToCan, toMqtt: convertfunctions.EightUint82AsciiToMqtt, } - // Int methodes come here now + // Int methods come here now case "int82ascii": pairFromID[canID] = &can2mqtt{ canId: canID, @@ -311,45 +305,18 @@ func readC2MPFromFile(filename string) { 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") - fmt.Printf("main: CAN-ID\t\t conversion mode\t\tMQTT-topic\n") - for _, c2mp := range pairFromID { - fmt.Printf("main: %d\t\t%s\t\t%s\n", c2mp.canId, c2mp.convMethod, c2mp.mqttTopic) - } - } -} -// check function to check if a topic or an ID is in the slice -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) - } - return true + for _, c2mp := range pairFromID { + slog.Debug("extracted pair", "id", c2mp.canId, "convertmode", c2mp.convMethod, "topic", c2mp.mqttTopic) } - if pairFromTopic[mqttTopic] != nil { - if dbg { - fmt.Printf("main: The ID %d or the Topic %s is already in the list!\n", canId, mqttTopic) - } - return true - } - return false -} - -// get the corresponding ID for a given topic -func getIdFromTopic(topic string) uint32 { - return pairFromTopic[topic].canId } -// get the conversion mode for a given topic -func getConvModeFromTopic(topic string) string { - return pairFromTopic[topic].convMethod +func isIDInSlice(canId uint32) bool { + return pairFromID[canId] != nil } -// get the convertMode for a given ID -func getConvModeFromId(canId uint32) string { - return pairFromID[canId].convMethod +func isTopicInSlice(mqttTopic string) bool { + return pairFromTopic[mqttTopic] != nil } // get the corresponding topic for an ID diff --git a/src/internal/mqtthandling.go b/src/internal/mqtt.go similarity index 59% rename from src/internal/mqtthandling.go rename to src/internal/mqtt.go index e9fa04a..8cf9341 100644 --- a/src/internal/mqtthandling.go +++ b/src/internal/mqtt.go @@ -1,8 +1,9 @@ package internal import ( - "fmt" MQTT "github.com/eclipse/paho.mqtt.golang" + "log/slog" + "os" "strings" ) @@ -17,16 +18,10 @@ func mqttStart(suppliedString string) { // looks like authentication is required for this server userPasswordHost := strings.TrimPrefix(suppliedString, "tcp://") userPassword, host, found := strings.Cut(userPasswordHost, "@") - if !found { - fmt.Println("Whoops, there is an issue with your MQTT-connectString:") - fmt.Println("suppliedString: ", suppliedString) - fmt.Println("userPasswordHost: ", userPasswordHost) - } user, pw, found = strings.Cut(userPassword, ":") if !found { - fmt.Println("Whoops, there is an issue with your MQTT-connectString:") - fmt.Println("suppliedString: ", suppliedString) - fmt.Println("userPasswordHost: ", userPasswordHost) + slog.Error("mqtt: missing colon(:) between username and password", "connect string", suppliedString) + os.Exit(1) } connectString = "tcp://" + host } @@ -37,16 +32,12 @@ func mqttStart(suppliedString string) { clientSettings.SetCredentialsProvider(userPwCredProv) } client = MQTT.NewClient(clientSettings) - if dbg { - fmt.Printf("mqtthandler: starting connection to: %s\n", connectString) - } + slog.Debug("mqtt: starting connection", "connectString", connectString) if token := client.Connect(); token.Wait() && token.Error() != nil { - fmt.Println("mqttHandler: Oh no an error occurred...") - panic(token.Error()) - } - if dbg { - fmt.Printf("mqttHandler: connection established!\n") + slog.Error("mqtt: could not connect to mqtt", "error", token.Error()) + os.Exit(1) } + slog.Info("mqtt: connected to mqtt") } // credentialsProvider @@ -57,33 +48,25 @@ func userPwCredProv() (username, password string) { // subscribe to a new topic func mqttSubscribe(topic string) { if token := client.Subscribe(topic, 0, nil); token.Wait() && token.Error() != nil { - fmt.Printf("mqtthandler: error while subscribing: %s\n", topic) - } - if dbg { - fmt.Printf("mqtthandler: successfully subscribed: %s\n", topic) + slog.Error("mqtt: error subscribing", "error", token.Error()) } + slog.Debug("mqtt: subscribed", "topic", topic) } // unsubscribe a topic func mqttUnsubscribe(topic string) { if token := client.Unsubscribe(topic); token.Wait() && token.Error() != nil { - fmt.Printf("mqtthandler: Error while unsuscribing :%s\n", topic) - } - if dbg { - fmt.Printf("mqtthandler: successfully unsubscribed %s\n", topic) + slog.Error("mqtt: error unsubscribing", "error", token.Error()) } + slog.Debug("mqtt: unsubscribed", "topic", topic) } // publish a new message func mqttPublish(topic string, payload []byte) { - if dbg { - fmt.Printf("mqtthandler: sending message: \"%s\" to topic: \"%s\"\n", payload, topic) - } mqttUnsubscribe(topic) + slog.Debug("mqtt: publishing message", "payload", payload, "topic", topic) token := client.Publish(topic, 0, false, payload) token.Wait() - if dbg { - fmt.Printf("mqtthandler: message was transmitted successfully!.\n") - } + slog.Debug("mqtt: published message", "payload", payload, "topic", topic) mqttSubscribe(topic) } diff --git a/src/internal/receivehandling.go b/src/internal/receivehandling.go deleted file mode 100644 index 6a9ec87..0000000 --- a/src/internal/receivehandling.go +++ /dev/null @@ -1,56 +0,0 @@ -package internal - -import ( - "fmt" - "github.com/brutella/can" - MQTT "github.com/eclipse/paho.mqtt.golang" -) - -// handleCAN is the standard receive handler for CANFrames -// and does the following: -// 1. calling standard convert function: convert2MQTT -// 2. sending the message -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) - } - // 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) - } -} - -// handleMQTT is the standard receive handler for MQTT -// messages and does the following: -// 1. calling the standard convert function: convert2CAN -// 2. sending the message -func handleMQTT(_ MQTT.Client, msg MQTT.Message) { - if dbg { - fmt.Printf("receivehandler: received message: topic: %s, msg: %s\n", msg.Topic(), 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()) - } -} diff --git a/src/internal/receiving.go b/src/internal/receiving.go new file mode 100644 index 0000000..eee10e9 --- /dev/null +++ b/src/internal/receiving.go @@ -0,0 +1,46 @@ +package internal + +import ( + "github.com/brutella/can" + MQTT "github.com/eclipse/paho.mqtt.golang" + "log/slog" +) + +// handleCAN is the standard receive handler for CANFrames +// and does the following: +// 1. calling standard convert function: convert2MQTT +// 2. sending the message +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) + if err != nil { + slog.Warn("conversion to MQTT message unsuccessful", "convertmode", pairFromID[cf.ID].convMethod, "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) + } +} + +// handleMQTT is the standard receive handler for MQTT +// messages and does the following: +// 1. calling the standard convert function: convert2CAN +// 2. sending the message +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()) + if err != nil { + slog.Warn("conversion to CAN-Frame unsuccessful", "convertmode", pairFromTopic[msg.Topic()].convMethod, "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()) + } +}