From 6e9c67925b831633edb7509315fe7943565e0a25 Mon Sep 17 00:00:00 2001 From: "jonas@fullgera.se" <0Romsan0> Date: Wed, 6 Jul 2022 21:30:21 +0200 Subject: [PATCH 1/3] Added Makefile and arduino.json files for arduino-cli support. --- .vscode/arduino.json | 7 +++++++ Makefile | 43 +++++++++++++++++++++++++++++++++++++++++++ README.md | 36 +++++++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 .vscode/arduino.json create mode 100644 Makefile diff --git a/.vscode/arduino.json b/.vscode/arduino.json new file mode 100644 index 0000000..da30d2c --- /dev/null +++ b/.vscode/arduino.json @@ -0,0 +1,7 @@ +{ + "port": "/dev/tty.usbserial-AB0LYA07", + "configuration": "FlashFreq=80,UploadSpeed=921600,DebugLevel=none,PartitionScheme=default", + "board": "esp32:esp32:featheresp32", + "sketch": "AMConnect.ino", + "output": "../build" +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..883c295 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +TARGET ?= AMConnect + +# Arduino CLI executable name and directory location +ARDUINO_CLI = arduino-cli +ARDUINO_CLI_DIR = ~/bin/ + +# Arduino CLI Board type +BOARD_TYPE ?= esp32:esp32:featheresp32 + +# Default port/OTA address to upload to +SERIAL_PORT ?= /dev/tty.usbserial-AB0LYA07 +OTA_ADDR ?= 10.0.21.68 + +# Build path -- used to store built binary and object files +BUILD_PATH=../build + +.PHONY: help build load ota clean + +help: ## Show this help message. + @echo 'usage: make ' + @echo + @echo 'Available commands:' + @echo 'all - Build and load' + @echo 'build - Compile/verify source' + @echo 'clean - Clean/remove build source' + @echo 'help - Show this help' + @echo 'load - Load source using serial port' + @echo 'ota - Load source using ArduinoOTA' + @echo + +all: build load + +build: + $(ARDUINO_CLI_DIR)/$(ARDUINO_CLI) compile -v --build-path=$(BUILD_PATH) --build-cache-path=$(BUILD_PATH) -b $(BOARD_TYPE) $(PWD)/$(TARGET) + +load: + $(ARDUINO_CLI_DIR)/$(ARDUINO_CLI) upload -v --input-dir=$(BUILD_PATH) -p $(SERIAL_PORT) --fqbn $(BOARD_TYPE) $(PWD)/$(TARGET) + +ota: + $(ARDUINO_CLI_DIR)/$(ARDUINO_CLI) upload -v --input-dir=$(BUILD_PATH) -p $(OTA_ADDR) --fqbn $(BOARD_TYPE) $(PWD)/$(TARGET) + +clean: + @rm -rf $(BUILD_PATH) diff --git a/README.md b/README.md index 8d2c04a..9d9337c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,38 @@ -# AMConnect +# AMConnect -This is a basic Arduino sketch to connect to a Huqsvarna Automower generation 2 robot mower. +This is a basic Arduino sketch to connect to a Huqsvarna Automower generation 2 robot mower. In it's current state it connects to the Autoower and sends the status, location and debug data to a mqtt server. -The code should be seen as a Proof of Concept code and not a real product. +The code should be seen as a Proof of Concept code and not a real product. -Feel free to modify it as you see fit. +Feel free to modify it as you see fit. +## Software +To build the source using arduino-cli install dependency library's and update the following symbols in the Makefile: +``` +ARDUINO_CLI +ARDUINO_CLI_DIR +BOARD_TYPE +SERIAL_PORT +OTA_ADDR +``` + +Type `make help` to show available build/load commands. + + +### Library Dependency +* NeoGPS (4.2.9) +* WiFi (2.0.0) +* PubSubClient (2.8) +* Uptime_Library (1.0.0) + +To install library's using arduino-cli use the command: +``` +arduino-cli lib install “NeoGPS” +arduino-cli lib install "WiFi" +arduino-cli lib install “PubSubClient” +arduino-cli lib install "Uptime Library" +``` ## Hardware @@ -17,7 +43,7 @@ The example hardware used for this project is based around a LOLIN32 developer m [GPS Module on Amazon](https://www.amazon.com/NEO-8M-Module-APM2-56-GYGPSV3-NEOM8N-NEO-M8N-001/dp/B07YY85WJY) -As both the ESP32 and the NEO-8M module comes in alot of different forms and shapes, remember to match the settings in the configuration file with your hardware and wiring. +As both the ESP32 and the NEO-8M module comes in alot of different forms and shapes, remember to match the settings in the configuration file with your hardware and wiring. For my hardware, the wiring is as shown below. (Note: I have the GPS module upside down compared to the sketch, which makes all pins (except GND) line up with the pins on the LOLIN32 board.) From c2fc33819f0250f6303f671096bd4b9df1d18134 Mon Sep 17 00:00:00 2001 From: Jonas Kollberg Date: Thu, 7 Jul 2022 10:18:25 +0200 Subject: [PATCH 2/3] Added define in AMConnect_config.h to configure if GPS is attached or not. --- AMConnect.ino | 371 +++++++++++++++++++------------------- AMConnect_config.h.sample | 7 +- 2 files changed, 191 insertions(+), 187 deletions(-) diff --git a/AMConnect.ino b/AMConnect.ino index 01e57dd..73a390e 100644 --- a/AMConnect.ino +++ b/AMConnect.ino @@ -3,13 +3,13 @@ // // Description: Connects Husqvarna Automower Generaton 2 to MQTT. // It allows status to be sent to MQTT and commands -// to be sent to the Automower. +// to be sent to the Automower. // // // Requirements: Supported Automower (Generation 2) // ESP32 (LOLIN32 used for development) // Ublox Neo GPS (for positioning) -// +// // //====================================================================== #include @@ -47,7 +47,7 @@ unsigned int localGpsInterval; unsigned long pollMillis; unsigned int localPollInterval; -uint8_t lastCommand[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +uint8_t lastCommand[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; void setup() { @@ -56,21 +56,23 @@ void setup() ; DEBUG_PORT.print( F("AMConnect: started\n") ); - // Start wifi + // Start wifi setup_wifi(); // Connect MQTT client.setServer(mqtt_server, 1883); client.setCallback(callback); - + // Start the serialport to AutoMower Serial1.begin(9600, SERIAL_8N1, AMSerialRX, AMSerialTX); // Send status request to the automower // Start the GPS port - gpsPort.begin(9600); + if (true == GPS_ATTACHED) { + gpsPort.begin(9600); + } // Set gpsMillis gpsMillis = millis(); @@ -117,25 +119,26 @@ void setup() // Main loop void loop() { - // Check if MQTT is connected + // Check if MQTT is connected if (!client.connected()) { reconnect(); } // Do the MQTT Client Loop client.loop(); - - // Start with the GPS - while (gps.available( gpsPort )) { - fix = gps.read(); - if (fix.valid.location) { - if (millis() - gpsMillis >= localGpsInterval*1000) { - // Handle the GPS position - handle_gps(); - - // Set gpsMillis to millis(), so we know when gpsInterval has passed - gpsMillis = millis(); - } - } + + if (true == GPS_ATTACHED) { + // Start with the GPS + while (gps.available( gpsPort )) { + fix = gps.read(); + if (fix.valid.location) { + if (millis() - gpsMillis >= localGpsInterval*1000) { + // Handle the GPS position + handle_gps(); + // Set gpsMillis to millis(), so we know when gpsInterval has passed + gpsMillis = millis(); + } + } + } } // And then the Automower @@ -145,7 +148,7 @@ void loop() } // Poll mower for status - if (millis() - pollMillis >= localPollInterval*1000) + if (millis() - pollMillis >= localPollInterval*1000) { // do a poll for status handle_command("getStatus"); @@ -159,17 +162,17 @@ void loop() // Functions -void savePreferences() +void savePreferences() { preferences.begin("amPreferences", false); if(readStoredPreferences == 1) { handle_debug(false, (String)"Using stored values from preferences, skipping config values..."); - + //Poll-interval localPollInterval = preferences.getUInt("pollInterval"); handle_debug(true, "stored pollInterval is set to: " + preferences.getUInt("pollInterval")); - + // GPS-interval localGpsInterval = preferences.getUInt("gpsInterval"); handle_debug(true, "stored gpsInterval is set to: " + preferences.getUInt("gpsInterval")); @@ -181,7 +184,7 @@ void savePreferences() //Poll-interval preferences.putUInt("pollInterval", pollInterval); localPollInterval = pollInterval; - + // GPS-interval preferences.putUInt("gpsInterval", gpsInterval); localGpsInterval = gpsInterval; @@ -201,9 +204,9 @@ void setup_wifi() { delay(500); DEBUG_PORT.print("."); } - + handle_debug(false, ""); - handle_debug(false, (String)"WiFi connected. IP Address: " + (String)WiFi.localIP()); + handle_debug(false, (String)"WiFi connected. IP Address: " + (String)WiFi.localIP()); } @@ -260,7 +263,7 @@ void handle_gps() { dtostrf(fix.latitude(), 0, 6, latitudeString); char longitudeString[10]; dtostrf(fix.longitude(), 0, 6, longitudeString); - + const byte latitudeSize = sizeof latitudeString; const byte longitudeSize = sizeof longitudeString; @@ -279,7 +282,7 @@ void handle_debug(bool sendmqtt, String debugmsg) { // Handle the debug output DEBUG_PORT.println(debugmsg); - + // send to mqtt_command_topic if (sendmqtt && client.connected()) { @@ -292,7 +295,7 @@ void handle_debug(bool sendmqtt, String debugmsg) { void handle_status(int statusCode, String statusMsg) { // Send to debug handle_debug(true, statusMsg); - + // send to mqtt_status_topic if (client.connected()) { @@ -304,7 +307,7 @@ void handle_status(int statusCode, String statusMsg) { void handle_uptime() { String uptime = "Up " + (String)uptime_formatter::getUptime(); - + // send to mqtt_debug_topic if (client.connected()) { @@ -316,91 +319,91 @@ void handle_uptime() { void handle_am() { uint8_t statusAutomower[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - uint8_t empty[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - + uint8_t empty[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + Serial1.readBytes(statusAutomower,5); - - if(memcmp(statusAutomower, empty, 5) == 0) + + if(memcmp(statusAutomower, empty, 5) == 0) { handle_debug(true, "Unusable data received on serial"); - } - else + } + else { // values comes as DEC and not HEX handle_debug(true, "Byte1: " + (String)(statusAutomower[0]) + " Byte2: " + (String)(statusAutomower[1]) + " Byte3: " + (String)(statusAutomower[2]) + " Byte4: " + (String)(statusAutomower[3]) + " Byte5: " + (String)(statusAutomower[4])); - + } // Merge the last two bytes to status unsigned int statusInt = statusAutomower[4] << 8 | statusAutomower[3]; handle_debug(true, "Status Code: " + (String)statusInt); - if (statusAutomower[0] == 0x0F) + if (statusAutomower[0] == 0x0F) { // A command - if (statusAutomower[1] == 0x80) + if (statusAutomower[1] == 0x80) { // Keypresses - if (statusAutomower[2] == 0x5F) + if (statusAutomower[2] == 0x5F) { // Keypress switch (statusInt) { - case 0: - handle_debug(true, "Key 0 pressed"); + case 0: + handle_debug(true, "Key 0 pressed"); break; - case 1: - handle_debug(true, "Key 1 pressed"); + case 1: + handle_debug(true, "Key 1 pressed"); break; - case 2: - handle_debug(true, "Key 2 pressed"); + case 2: + handle_debug(true, "Key 2 pressed"); break; - case 3: - handle_debug(true, "Key 3 pressed"); + case 3: + handle_debug(true, "Key 3 pressed"); break; - case 4: - handle_debug(true, "Key 4 pressed"); + case 4: + handle_debug(true, "Key 4 pressed"); break; - case 5: - handle_debug(true, "Key 5 pressed"); + case 5: + handle_debug(true, "Key 5 pressed"); break; - case 6: - handle_debug(true, "Key 6 pressed"); + case 6: + handle_debug(true, "Key 6 pressed"); break; - case 7: - handle_debug(true, "Key 7 pressed"); + case 7: + handle_debug(true, "Key 7 pressed"); break; - case 8: - handle_debug(true, "Key 8 pressed"); + case 8: + handle_debug(true, "Key 8 pressed"); break; - case 9: - handle_debug(true, "Key 9 pressed"); + case 9: + handle_debug(true, "Key 9 pressed"); break; - case 10: - handle_debug(true, "Key Program A pressed"); + case 10: + handle_debug(true, "Key Program A pressed"); break; - case 11: - handle_debug(true, "Key Program B pressed"); + case 11: + handle_debug(true, "Key Program B pressed"); break; - case 12: - handle_debug(true, "Key Program C pressed"); + case 12: + handle_debug(true, "Key Program C pressed"); break; - case 13: + case 13: handle_debug(true, "Key Home pressed"); break; - case 14: - handle_debug(true, "Key Man/Auto pressed"); + case 14: + handle_debug(true, "Key Man/Auto pressed"); break; - case 15: + case 15: handle_debug(true, "Key C pressed"); break; - case 16: + case 16: handle_debug(true, "Key Up pressed"); break; - case 17: - handle_debug(true, "Key Down pressed"); + case 17: + handle_debug(true, "Key Down pressed"); break; - case 18: + case 18: handle_debug(true, "Key YES pressed"); break; default: //no valid parameter: send status @@ -409,24 +412,24 @@ void handle_am() { } } } - else if (statusAutomower[1] == 0x81) + else if (statusAutomower[1] == 0x81) { // Mode set - if (statusAutomower[2] == 0x2C) + if (statusAutomower[2] == 0x2C) { // Mode Set switch (statusInt) { - case 0: - handle_debug(true, "Manual Mode"); + case 0: + handle_debug(true, "Manual Mode"); break; - case 1: - handle_debug(true, "Auto Mode"); + case 1: + handle_debug(true, "Auto Mode"); break; - case 3: + case 3: handle_debug(true, "Home Mode"); break; - case 4: - handle_debug(true, "Demo Mode"); + case 4: + handle_debug(true, "Demo Mode"); break; default: //no valid parameter: send status handle_debug(true, "Unkown keypress. Code: " + (String)statusInt); @@ -434,17 +437,17 @@ void handle_am() { } } } - else if (statusAutomower[1] == 0xCA) + else if (statusAutomower[1] == 0xCA) { // Timer actions - if (statusAutomower[2] == 0x4E) + if (statusAutomower[2] == 0x4E) { // Timer actions switch (statusInt) { - case 0: + case 0: handle_debug(true, "Timer activated"); break; - case 1: + case 1: handle_debug(true, "Timer deactivated"); break; default: //no valid parameter: send status @@ -456,12 +459,12 @@ void handle_am() { else if (statusAutomower[1] == 0x00) { // Mowing time and strength - if (statusAutomower[2] == 0x38) + if (statusAutomower[2] == 0x38) { // Mowing time handle_debug(true, "Mowing time: " + (String)statusInt); } - else if (statusAutomower[2] == 0x4D) + else if (statusAutomower[2] == 0x4D) { // rpm handle_debug(true, "Mowing rpm: " + (String)statusInt); @@ -470,42 +473,42 @@ void handle_am() { else if (statusAutomower[1] == 0x01) { // Info, Battery and square mode - if (statusAutomower[2] == 0x34) + if (statusAutomower[2] == 0x34) { // Square mode procent handle_debug(true, "Square mode procent: " + (String)statusInt); } - else if (statusAutomower[2] == 0x37) + else if (statusAutomower[2] == 0x37) { // Square mode reference handle_debug(true, "Square mode reference: " + (String)statusInt); } - else if (statusAutomower[2] == 0x38) + else if (statusAutomower[2] == 0x38) { // Square mode status handle_debug(true, "Square mode status: " + (String)statusInt); } - else if (statusAutomower[2] == 0xEB) + else if (statusAutomower[2] == 0xEB) { // Battery capacity (mA) handle_debug(true, "Battery capacity (mA): " + (String)statusInt); } - else if (statusAutomower[2] == 0xEC) + else if (statusAutomower[2] == 0xEC) { // Charging time handle_debug(true, "Charging time: " + (String)statusInt); } - else if (statusAutomower[2] == 0xEF) + else if (statusAutomower[2] == 0xEF) { // Battery capacity mAh handle_debug(true, "Battery capacity mAh: " + (String)statusInt); } - else if (statusAutomower[2] == 0xF0) + else if (statusAutomower[2] == 0xF0) { // Battery seek start capacity handle_debug(true, "Battery seek start capacity: " + (String)statusInt); } - else if (statusAutomower[2] == 0xF1) + else if (statusAutomower[2] == 0xF1) { // Status switch (statusInt) { @@ -522,7 +525,7 @@ void handle_am() { handle_status(statusInt, "Low battery voltage"); // Niedrige Batteriespannung break; case 24: //Status - handle_status(statusInt, "Wheel spinning"); + handle_status(statusInt, "Wheel spinning"); break; case 28: //Status handle_status(statusInt, "Manual charging needed"); // Ladestation blockiert @@ -540,10 +543,10 @@ void handle_am() { handle_status(statusInt, "Pin expired"); // Pin abgelaufen break; case 56: //Status - handle_status(statusInt, "Left collision sensor defective"); + handle_status(statusInt, "Left collision sensor defective"); break; case 58: //Status - handle_status(statusInt, "Right collision sensor defective"); + handle_status(statusInt, "Right collision sensor defective"); break; case 1000: //Status handle_status(statusInt, "Leaves charging station"); // Verlässt Ladestation @@ -564,7 +567,7 @@ void handle_am() { handle_status(statusInt, "Charging"); // Laden break; case 1016: //Status - handle_status(statusInt, "Waiting in charging station"); // Wartet in Ladestation + handle_status(statusInt, "Waiting in charging station"); // Wartet in Ladestation break; case 1024: //Status handle_status(statusInt, "Drives to charging station"); // Fährt in Ladestation @@ -589,7 +592,7 @@ void handle_am() { break; case 1050: //Status handle_status(statusInt, "Leaving charging station"); // Verlässt Ladestation - break; + break; case 1052: //Status handle_status(statusInt, "Error"); // Fehler break; @@ -623,198 +626,198 @@ void handle_am() { else if (statusAutomower[1] == 0x02) { // Battery info - if (statusAutomower[2] == 0x33) + if (statusAutomower[2] == 0x33) { - // + // handle_debug(true, "Battery temperature: " + (String)statusInt); } - else if (statusAutomower[2] == 0x34) + else if (statusAutomower[2] == 0x34) { - // + // handle_debug(true, "Last charging time: " + (String)statusInt); } - else if (statusAutomower[2] == 0x35) + else if (statusAutomower[2] == 0x35) { - // + // handle_debug(true, "Battery charging temperature: " + (String)statusInt); } - else if (statusAutomower[2] == 0x36) + else if (statusAutomower[2] == 0x36) { - // + // handle_debug(true, "Battery next temperature reading: " + (String)statusInt); } } else if (statusAutomower[1] == 0x24) { // Wheel speeds - if (statusAutomower[2] == 0xBF) + if (statusAutomower[2] == 0xBF) { - // + // handle_debug(true, "Right wheel speed: " + (String)statusInt); } - else if (statusAutomower[2] == 0xC0) + else if (statusAutomower[2] == 0xC0) { - // + // handle_debug(true, "Left wheel speed: " + (String)statusInt); } } else if (statusAutomower[1] == 0x2E) { // Battery info - if (statusAutomower[2] == 0xE0) + if (statusAutomower[2] == 0xE0) { - // + // handle_debug(true, "Battery Capacity Used (mAh): " + (String)statusInt); } - else if (statusAutomower[2] == 0xEA) + else if (statusAutomower[2] == 0xEA) { - // + // handle_debug(true, "Speed of Knife engine: " + (String)statusInt); } - else if (statusAutomower[2] == 0xF4) + else if (statusAutomower[2] == 0xF4) { - // + // handle_debug(true, "Battery Voltage: " + (String)statusInt); } } - else if (statusAutomower[1] == 0x33) + else if (statusAutomower[1] == 0x33) { // Firmware version - if (statusAutomower[2] == 0x90) + if (statusAutomower[2] == 0x90) { - // + // handle_debug(true, "Firmware version: " + (String)statusInt); } } else if (statusAutomower[1] == 0x36) { // time and date - if (statusAutomower[2] == 0xB3) + if (statusAutomower[2] == 0xB3) { - // + // handle_debug(true, "Current minute: " + (String)statusInt); } - else if (statusAutomower[2] == 0xB5) + else if (statusAutomower[2] == 0xB5) { - // + // handle_debug(true, "Current hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0xB7) + else if (statusAutomower[2] == 0xB7) { - // + // handle_debug(true, "Current day: " + (String)statusInt); } - else if (statusAutomower[2] == 0xB9) + else if (statusAutomower[2] == 0xB9) { - // + // handle_debug(true, "Current month: " + (String)statusInt); } - else if (statusAutomower[2] == 0xBD) + else if (statusAutomower[2] == 0xBD) { - // + // handle_debug(true, "Current year: " + (String)statusInt); } } else if (statusAutomower[1] == 0x3A) { // Voice version - if (statusAutomower[2] == 0xC0) + if (statusAutomower[2] == 0xC0) { - // + // handle_debug(true, "Voice version: " + (String)statusInt); } } else if (statusAutomower[1] == 0x4A) { // Timers - if (statusAutomower[2] == 0x38) + if (statusAutomower[2] == 0x38) { - // + // handle_debug(true, "Week Timer1 Start Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x39) + else if (statusAutomower[2] == 0x39) { - // + // handle_debug(true, "Week Timer1 Start Minute: " + (String)statusInt); } - else if (statusAutomower[2] == 0x3A) + else if (statusAutomower[2] == 0x3A) { - // + // handle_debug(true, "Week Timer1 Stop Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x3B) + else if (statusAutomower[2] == 0x3B) { - // + // handle_debug(true, "Week Timer1 Stop Minute: " + (String)statusInt); } - else if (statusAutomower[2] == 0x3C) + else if (statusAutomower[2] == 0x3C) { - // + // handle_debug(true, "Weekend Timer1 Start Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x3D) + else if (statusAutomower[2] == 0x3D) { - // + // handle_debug(true, "Weekend Timer1 Start Minute: " + (String)statusInt); } - else if (statusAutomower[2] == 0x3E) + else if (statusAutomower[2] == 0x3E) { - // + // handle_debug(true, "Weekend Timer1 Stop Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x3F) + else if (statusAutomower[2] == 0x3F) { - // + // handle_debug(true, "Weekend Timer1 Stop Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x40) + else if (statusAutomower[2] == 0x40) { - // + // handle_debug(true, "Week Timer2 Start Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x41) + else if (statusAutomower[2] == 0x41) { - // + // handle_debug(true, "Week Timer2 Start Minute: " + (String)statusInt); } - else if (statusAutomower[2] == 0x42) + else if (statusAutomower[2] == 0x42) { - // + // handle_debug(true, "Week Timer2 Stop Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x43) + else if (statusAutomower[2] == 0x43) { - // + // handle_debug(true, "Week Timer2 Stop Minute: " + (String)statusInt); } - else if (statusAutomower[2] == 0x44) + else if (statusAutomower[2] == 0x44) { - // + // handle_debug(true, "Weekend Timer2 Start Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x45) + else if (statusAutomower[2] == 0x45) { - // + // handle_debug(true, "Weekend Timer2 Start Minute: " + (String)statusInt); } - else if (statusAutomower[2] == 0x46) + else if (statusAutomower[2] == 0x46) { - // + // handle_debug(true, "Weekend Timer2 Stop Hour: " + (String)statusInt); } - else if (statusAutomower[2] == 0x47) + else if (statusAutomower[2] == 0x47) { - // + // handle_debug(true, "Weekend Timer2 Stop Minute: " + (String)statusInt); } - else if (statusAutomower[2] == 0x4E) + else if (statusAutomower[2] == 0x4E) { - // + // handle_debug(true, "Timer Status: " + (String)statusInt); } - else if (statusAutomower[2] == 0x50) + else if (statusAutomower[2] == 0x50) { - // + // handle_debug(true, "Timer Day: " + (String)statusInt); } } @@ -824,38 +827,38 @@ void handle_am() { void handle_preferences(String preferencePayload) { preferences.begin("amPreferences", false); - if(preferencePayload.startsWith("gpsInterval:")) - { + if(preferencePayload.startsWith("gpsInterval:")) + { preferences.putUInt("gpsInterval", preferencePayload.substring(12).toInt()); localGpsInterval = preferencePayload.substring(12).toInt(); handle_debug(true, "gpsInterval is set to: " + String(preferences.getUInt("gpsInterval"))); } - + else if (preferencePayload.startsWith("pollInterval:")) - { + { preferences.putUInt("pollInterval", preferencePayload.substring(13).toInt()); localPollInterval = preferencePayload.substring(13).toInt(); handle_debug(true, "pollInterval is set to: " + String(preferences.getUInt("pollInterval"))); } - + else if( preferencePayload == "getGpsInterval") - { + { const byte gpsSize = sizeof localGpsInterval; char gpsChar[gpsSize]; itoa(localGpsInterval, gpsChar, 10); client.publish(mqtt_prefstatus_topic, gpsChar); handle_debug(true, "gpsInterval is set to: " + String(preferences.getUInt("gpsInterval"))); } - + else if( preferencePayload = "getPollInterval") { const byte pollSize = sizeof localPollInterval; char pollChar[pollSize]; itoa(localPollInterval, pollChar, 10); - client.publish(mqtt_prefstatus_topic, pollChar); + client.publish(mqtt_prefstatus_topic, pollChar); handle_debug(true, "pollInterval is set to: " + String(preferences.getUInt("pollInterval"))); } - - else + + else { handle_debug(true, "Unknown command recieved on automower/preferences topic: " + preferencePayload); } - + preferences.end(); } @@ -938,5 +941,5 @@ void handle_command(String command) { // send it to the automower Serial1.write(commandAutomower,sizeof(commandAutomower)); } - + } diff --git a/AMConnect_config.h.sample b/AMConnect_config.h.sample index 3961f98..f23c548 100644 --- a/AMConnect_config.h.sample +++ b/AMConnect_config.h.sample @@ -14,12 +14,13 @@ const char* mqtt_preferences_topic = "automower/preferences"; const char* mqtt_prefstatus_topic = "automower/prefstatus"; const char* mqtt_lwt_topic = "automower/lwt"; -// Define the port used for serial communication with the AutoMower. +// Define the port used for serial communication with the AutoMower. // Here we use Serial1, on pins 15 (RX) and 2 (TX) #define AMSerialRX 15 #define AMSerialTX 2 -// Define the port used for GPS. In this case, Serial2 on default pins (RX 16, TX 17) +// Define if GPS attached (true/false) and the port used for GPS. In this case, Serial2 on default pins (RX 16, TX 17) +#define GPS_ATTACHED true #define gpsPort Serial2 #define GPS_PORT_NAME "Serial2" @@ -36,4 +37,4 @@ const char* mqtt_lwt_topic = "automower/lwt"; // 1 = read internal values, 0 = overwrite internal values with config-readInternalValues // This can be used for setting the values over MQTT by sending pollInterval:xx on topic automower/preferences // and is stored in the device after a reboot. -#define readStoredPreferences 0 +#define readStoredPreferences 0 From 2937777d21ddb5dbc151264b42535ef5045ce971 Mon Sep 17 00:00:00 2001 From: Jonas Kollberg Date: Thu, 7 Jul 2022 16:18:51 +0200 Subject: [PATCH 3/3] Added getWifiRssi command. Increased WiFi stability by adding reconnect handling. --- AMConnect.ino | 119 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 40 deletions(-) diff --git a/AMConnect.ino b/AMConnect.ino index 73a390e..304943a 100644 --- a/AMConnect.ino +++ b/AMConnect.ino @@ -48,6 +48,7 @@ unsigned long pollMillis; unsigned int localPollInterval; uint8_t lastCommand[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +static bool AMPresent = false; void setup() { @@ -119,9 +120,14 @@ void setup() // Main loop void loop() { + // Chack if WiFi is connected + if (WiFi.status() != WL_CONNECTED) { + wifi_reconnect(); + } + // Check if MQTT is connected if (!client.connected()) { - reconnect(); + mqtt_reconnect(); } // Do the MQTT Client Loop client.loop(); @@ -153,6 +159,9 @@ void loop() // do a poll for status handle_command("getStatus"); + // do a poll for Wifi RSSI + handle_command("getWifiRssi"); + pollMillis = millis(); } @@ -192,13 +201,21 @@ void savePreferences() preferences.end(); } +String ip2Str(IPAddress ip){ + String ip_str=""; + for (int i=0; i<4; i++) { + ip_str += i ? "." + String(ip[i]) : String(ip[i]); + } + return ip_str; +} + void setup_wifi() { delay(10); // We start by connecting to a WiFi network handle_debug(false, ""); handle_debug(false, (String)"Connecting to: " + (String)ssid); - WiFi.begin(ssid, password); + WiFi.begin(ssid, password, 0); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -206,10 +223,9 @@ void setup_wifi() { } handle_debug(false, ""); - handle_debug(false, (String)"WiFi connected. IP Address: " + (String)WiFi.localIP()); + handle_debug(false, (String)"WiFi connected. IP Address: " + ip2Str(WiFi.localIP())); } - void callback(char* topic, byte* message, unsigned int length) { String messageTemp; @@ -236,9 +252,26 @@ void callback(char* topic, byte* message, unsigned int length) { } } -void reconnect() { +void wifi_reconnect() { + // Loop until we're reconnected + while ((WiFi.status() != WL_CONNECTED)) { + handle_debug(false, "Reconnecting to WiFi..."); + WiFi.disconnect(); + WiFi.reconnect(); + if (WiFi.status() != WL_CONNECTED) { + delay(5000); + } + } +} + +void mqtt_reconnect() { // Loop until we're reconnected while (!client.connected()) { + // Abort if no WiFi status not connected + if (WiFi.status() != WL_CONNECTED) { + return; + } + handle_debug(false, "Attempting MQTT connection..."); // Attempt to connect if (client.connect("AMClient", mqtt_username, mqtt_password, mqtt_lwt_topic, 1, true, "Offline")) { @@ -264,7 +297,6 @@ void handle_gps() { char longitudeString[10]; dtostrf(fix.longitude(), 0, 6, longitudeString); - const byte latitudeSize = sizeof latitudeString; const byte longitudeSize = sizeof longitudeString; const byte positionSize = latitudeSize + longitudeSize + 1; @@ -282,7 +314,6 @@ void handle_debug(bool sendmqtt, String debugmsg) { // Handle the debug output DEBUG_PORT.println(debugmsg); - // send to mqtt_command_topic if (sendmqtt && client.connected()) { @@ -292,6 +323,13 @@ void handle_debug(bool sendmqtt, String debugmsg) { } } +void handle_wifirssi() { + String wifirssi = "Wifi RSSI (dBm): " + (String)WiFi.RSSI(); + + // Send to debug + handle_debug(true, wifirssi); +} + void handle_status(int statusCode, String statusMsg) { // Send to debug handle_debug(true, statusMsg); @@ -318,21 +356,22 @@ void handle_uptime() { } void handle_am() { - uint8_t statusAutomower[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - uint8_t empty[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + uint8_t statusAutomower[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + uint8_t empty[5] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - Serial1.readBytes(statusAutomower,5); + Serial1.readBytes(statusAutomower,5); - if(memcmp(statusAutomower, empty, 5) == 0) - { + if(memcmp(statusAutomower, empty, 5) == 0) + { handle_debug(true, "Unusable data received on serial"); - } - else - { + AMPresent = false; + } + else + { // values comes as DEC and not HEX handle_debug(true, "Byte1: " + (String)(statusAutomower[0]) + " Byte2: " + (String)(statusAutomower[1]) + " Byte3: " + (String)(statusAutomower[2]) + " Byte4: " + (String)(statusAutomower[3]) + " Byte5: " + (String)(statusAutomower[4])); - - } + AMPresent = true; + } // Merge the last two bytes to status unsigned int statusInt = statusAutomower[4] << 8 | statusAutomower[3]; @@ -348,7 +387,7 @@ void handle_am() { if (statusAutomower[2] == 0x5F) { // Keypress - switch (statusInt) { + switch (statusInt) { case 0: handle_debug(true, "Key 0 pressed"); break; @@ -358,52 +397,52 @@ void handle_am() { case 2: handle_debug(true, "Key 2 pressed"); break; - case 3: + case 3: handle_debug(true, "Key 3 pressed"); break; - case 4: + case 4: handle_debug(true, "Key 4 pressed"); break; - case 5: + case 5: handle_debug(true, "Key 5 pressed"); break; - case 6: + case 6: handle_debug(true, "Key 6 pressed"); break; - case 7: + case 7: handle_debug(true, "Key 7 pressed"); break; - case 8: + case 8: handle_debug(true, "Key 8 pressed"); break; - case 9: + case 9: handle_debug(true, "Key 9 pressed"); break; - case 10: + case 10: handle_debug(true, "Key Program A pressed"); break; - case 11: + case 11: handle_debug(true, "Key Program B pressed"); break; - case 12: + case 12: handle_debug(true, "Key Program C pressed"); break; - case 13: + case 13: handle_debug(true, "Key Home pressed"); break; - case 14: + case 14: handle_debug(true, "Key Man/Auto pressed"); break; - case 15: + case 15: handle_debug(true, "Key C pressed"); break; - case 16: + case 16: handle_debug(true, "Key Up pressed"); break; - case 17: + case 17: handle_debug(true, "Key Down pressed"); break; - case 18: + case 18: handle_debug(true, "Key YES pressed"); break; default: //no valid parameter: send status @@ -428,7 +467,7 @@ void handle_am() { case 3: handle_debug(true, "Home Mode"); break; - case 4: + case 4: handle_debug(true, "Demo Mode"); break; default: //no valid parameter: send status @@ -443,7 +482,7 @@ void handle_am() { if (statusAutomower[2] == 0x4E) { // Timer actions - switch (statusInt) { + switch (statusInt) { case 0: handle_debug(true, "Timer activated"); break; @@ -863,7 +902,7 @@ void handle_preferences(String preferencePayload) { } void handle_command(String command) { - bool dowrite = true; + bool AMCmd = true; uint8_t commandAutomower[5] = { 0x0F, 0x01, 0xF1, 0x00, 0x00 }; if (command == "getMowingTime") { memcpy(commandAutomower, amcGetMowingTime, sizeof(commandAutomower)); } @@ -934,12 +973,12 @@ void handle_command(String command) { else if (command == "setModeDemo") { memcpy(commandAutomower, amcModeDemo, sizeof(commandAutomower)); } else if (command == "setTimerActivate") { memcpy(commandAutomower, amcTimerActivate, sizeof(commandAutomower)); } else if (command == "setTimerDeactivate") { memcpy(commandAutomower, amcTimerDeactivate, sizeof(commandAutomower)); } - else if (command == "getUptime") { handle_uptime(); dowrite = false; } + else if (command == "getUptime") { handle_uptime(); AMCmd = false; } + else if (command == "getWifiRssi") { handle_wifirssi(); AMCmd = false; } - if (dowrite) + if ((true == AMPresent) && (true == AMCmd)) { // send it to the automower Serial1.write(commandAutomower,sizeof(commandAutomower)); } - }