diff --git a/firmware/src/.gitignore b/firmware/src/.gitignore new file mode 100644 index 00000000..03b2b466 --- /dev/null +++ b/firmware/src/.gitignore @@ -0,0 +1 @@ +secrets.h diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 341868bd..6ea41839 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -3,6 +3,7 @@ #include "display_task.h" #include "interface_task.h" #include "motor_task.h" +#include "mqtt_task.h" #if SK_DISPLAY static DisplayTask display_task(0); @@ -15,6 +16,10 @@ static MotorTask motor_task(1); InterfaceTask interface_task(0, motor_task, display_task_p); +#if SK_MQTT +static MQTTTask mqtt_task(0, motor_task, interface_task); +#endif + void setup() { #if SK_DISPLAY display_task.setLogger(&interface_task); @@ -27,6 +32,7 @@ void setup() { motor_task.setLogger(&interface_task); motor_task.begin(); interface_task.begin(); + mqtt_task.begin(); // Free up the Arduino loop task vTaskDelete(NULL); diff --git a/firmware/src/mqtt_task.cpp b/firmware/src/mqtt_task.cpp new file mode 100644 index 00000000..de055bba --- /dev/null +++ b/firmware/src/mqtt_task.cpp @@ -0,0 +1,65 @@ +#if SK_MQTT +#include "mqtt_task.h" + +#include "motor_task.h" +#include "secrets.h" + + +MQTTTask::MQTTTask(const uint8_t task_core, MotorTask& motor_task, Logger& logger) : + Task("MQTT", 4096, 1, task_core), + motor_task_(motor_task), + logger_(logger), + wifi_client_(), + mqtt_client_(wifi_client_) { + auto callback = [this](char *topic, byte *payload, unsigned int length) { mqttCallback(topic, payload, length); }; + mqtt_client_.setCallback(callback); +} + +void MQTTTask::connectWifi() { + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + logger_.log("Establishing connection to WiFi.."); + } + + char buf[256]; + snprintf(buf, sizeof(buf), "Connected to network %s", WIFI_SSID); + logger_.log(buf); +} + +void MQTTTask::mqttCallback(char *topic, byte *payload, unsigned int length) { + char buf[256]; + snprintf(buf, sizeof(buf), "Received mqtt callback for topic %s, length %u", topic, length); + logger_.log(buf); +} + +void MQTTTask::connectMQTT() { + char buf[256]; + mqtt_client_.setServer(MQTT_SERVER, 1883); + logger_.log("Attempting MQTT connection..."); + if (mqtt_client_.connect(HOSTNAME "-" MQTT_USER, MQTT_USER, MQTT_PASSWORD)) { + logger_.log("MQTT connected"); + mqtt_client_.subscribe(MQTT_COMMAND_TOPIC); + } else { + snprintf(buf, sizeof(buf), "MQTT failed rc=%d will try again in 5 seconds", mqtt_client_.state()); + logger_.log(buf); + } +} + +void MQTTTask::run() { + connectWifi(); + connectMQTT(); + + while(1) { + long now = millis(); + if (!mqtt_client_.connected() && (now - mqtt_last_connect_time_) > 5000) { + logger_.log("Reconnecting MQTT"); + mqtt_last_connect_time_ = now; + connectMQTT(); + } + mqtt_client_.loop(); + delay(1); + } +} +#endif diff --git a/firmware/src/mqtt_task.h b/firmware/src/mqtt_task.h new file mode 100644 index 00000000..ff9e5321 --- /dev/null +++ b/firmware/src/mqtt_task.h @@ -0,0 +1,35 @@ +#pragma once + +#if SK_MQTT + +#include +#include +#include + +#include "logger.h" +#include "motor_task.h" +#include "task.h" + + +class MQTTTask : public Task { + friend class Task; // Allow base Task to invoke protected run() + + public: + MQTTTask(const uint8_t task_core, MotorTask& motor_task, Logger& logger); + + protected: + void run(); + + private: + MotorTask& motor_task_; + Logger& logger_; + WiFiClient wifi_client_; + PubSubClient mqtt_client_; + int mqtt_last_connect_time_ = 0; + + void connectWifi(); + void connectMQTT(); + void mqttCallback(char *topic, byte *payload, unsigned int length); +}; + +#endif diff --git a/firmware/src/secrets.h.example b/firmware/src/secrets.h.example new file mode 100644 index 00000000..bb295ba6 --- /dev/null +++ b/firmware/src/secrets.h.example @@ -0,0 +1,12 @@ +// Rename this file to secrets.h and add your secret details. + +// Network setup +#define WIFI_SSID "myssid" +#define WIFI_PASSWORD "supersecretpassword" + +#define HOSTNAME "smartknob" // e.g. smartknob.local + +#define MQTT_SERVER "10.0.0.2" +#define MQTT_USER "mqttuser" +#define MQTT_PASSWORD "megasecretpassword" +#define MQTT_COMMAND_TOPIC "smartknob" diff --git a/platformio.ini b/platformio.ini index 5f8d6cc3..fc9dec28 100644 --- a/platformio.ini +++ b/platformio.ini @@ -42,11 +42,12 @@ lib_deps = bogde/HX711 @ 0.7.5 adafruit/Adafruit VEML7700 Library @ 1.1.1 bakercp/PacketSerial @ 1.4.0 - nanopb/Nanopb @ 0.4.6 ; Ideally this would reference the nanopb submodule, but that would require + nanopb/Nanopb @ 0.4.7 ; Ideally this would reference the nanopb submodule, but that would require ; everyone to check out submodules to just compile, so we use the library ; registry for the runtime. The submodule is available for manually updating ; the pre-compiled (checked in) .pb.h/c files when proto files change, but is ; otherwise not used during application firmware compilation. + knolleary/PubSubClient @ 2.8 build_flags = ${base_config.build_flags} @@ -66,6 +67,9 @@ build_flags = ; Ambient light sensor (VEML7700) enabled: 1=enable (display/LEDs match ambient brightness), 0=disable (100% brightness all the time) -DSK_ALS=1 + ; Enable MQTT (wifi must be configured in secrets.h first) + -DSK_MQTT=1 + ; Pin configurations -DPIN_UH=26 -DPIN_UL=25