add MQTT and OTA update support #1
@ -13,10 +13,11 @@ platform = espressif8266
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
upload_speed = 500000
|
||||
upload_protocol = espota
|
||||
|
||||
upload_port = led-marquee-sign
|
||||
fmueller
commented
Would it be sufficient just to target the mDNS hostname that by default should be Would it be sufficient just to target the mDNS hostname that by default should be `led-marquee-sign.local`?
burned42
commented
IIRC that didn't work when we tried that. IIRC that didn't work when we tried that.
burned42
commented
Works when using just Works when using just `led-marquee-sign` :)
fmueller
commented
That is some odd behavior, but then again, if it works... mDNS is pretty standard with these IoT devices nowadays. For example, WLED also advertises a (user-configurable) hostname. Perhaps PlatformIO applies some magic here. That is some odd behavior, but then again, if it works... mDNS is pretty standard with these IoT devices nowadays. For example, [WLED](https://kno.wled.ge/) also advertises a (user-configurable) hostname. Perhaps PlatformIO applies some magic here.
|
||||
|
||||
# Serial1 (sign data) maps to D4 by default
|
||||
[env:d1_mini]
|
||||
board = d1_mini
|
||||
|
||||
[env:nodemcuv2]
|
||||
board = nodemcuv2
|
||||
lib_deps = adafruit/Adafruit MQTT Library@^2.5.8
|
||||
lib_ignore = WiFi101
|
||||
|
@ -5,4 +5,9 @@ namespace credentials {
|
||||
const String psk = "my PSK";
|
||||
|
||||
const String mdnsHostname = "led-marquee-sign";
|
||||
|
||||
const String mqtt_hostname = "mqtt-broker";
|
||||
const uint16_t mqtt_port = 1883;
|
||||
const String mqtt_username = "user";
|
||||
const String mqtt_password = "pass";
|
||||
}
|
||||
|
70
src/main.cpp
@ -1,6 +1,11 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WiFiMulti.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include "Adafruit_MQTT.h"
|
||||
#include "Adafruit_MQTT_Client.h"
|
||||
|
||||
#include "credentials.h"
|
||||
#include "webserver.h"
|
||||
@ -10,6 +15,18 @@
|
||||
static ESP8266WiFiMulti wifiMulti;
|
||||
static SocketServer socketServer;
|
||||
|
||||
#define AIO_SERVER credentials::mqtt_hostname.c_str()
|
||||
#define AIO_SERVERPORT credentials::mqtt_port
|
||||
#define AIO_USERNAME credentials::mqtt_username.c_str()
|
||||
#define AIO_KEY credentials::mqtt_password.c_str()
|
||||
|
||||
fmueller marked this conversation as resolved
Outdated
fmueller
commented
Please add a blank line above. Please add a blank line above.
burned42
commented
done 👍 done 👍
|
||||
WiFiClient client;
|
||||
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);
|
||||
Adafruit_MQTT_Subscribe mqtt_subscribe_topic = Adafruit_MQTT_Subscribe(&mqtt, "homeassistant/marquee_sign/text/set");
|
||||
Adafruit_MQTT_Publish mqtt_publish_topic = Adafruit_MQTT_Publish(&mqtt, "homeassistant/marquee_sign/text/status");
|
||||
|
||||
void MQTT_connect();
|
||||
|
||||
void setup() {
|
||||
// serial console, for use via USB (also exposed to TXD0/RXD0 GPIOs)
|
||||
Serial.begin(115200);
|
||||
@ -20,7 +37,10 @@ void setup() {
|
||||
Serial1.begin(9600);
|
||||
|
||||
// configure WiFi and connect to the network
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.hostname(credentials::mdnsHostname);
|
||||
wifiMulti.addAP(credentials::ssid.c_str(), credentials::psk.c_str());
|
||||
|
||||
Serial.println("Connecting to Wi-Fi network...");
|
||||
while (wifiMulti.run() != WL_CONNECTED) {
|
||||
delay(1000);
|
||||
@ -32,16 +52,13 @@ void setup() {
|
||||
Serial.print(", IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
ArduinoOTA.begin();
|
||||
|
||||
mqtt.subscribe(&mqtt_subscribe_topic);
|
||||
|
||||
// might be handy to have the IP written on the sign
|
||||
sendTextToSign(WiFi.localIP().toString());
|
||||
|
||||
// publish this service using mDNS so that it's easy to find it in the LAN
|
||||
if (MDNS.begin(credentials::mdnsHostname)) {
|
||||
Serial.println("mDNS started up successfully");
|
||||
} else {
|
||||
Serial.println("Error: failed to start mDNS");
|
||||
}
|
||||
|
||||
// configure HTTP endpoints
|
||||
configureWebServer();
|
||||
|
||||
@ -53,4 +70,43 @@ void setup() {
|
||||
void loop() {
|
||||
webServer.handleClient();
|
||||
socketServer.handleClient();
|
||||
|
||||
ArduinoOTA.handle();
|
||||
|
||||
MQTT_connect();
|
||||
|
||||
Adafruit_MQTT_Subscribe *subscription;
|
||||
while ((subscription = mqtt.readSubscription(5000))) {
|
||||
const char* signText = (char *)mqtt_subscribe_topic.lastread;
|
||||
|
||||
sendTextToSign(signText);
|
||||
|
||||
mqtt_publish_topic.publish(signText);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTT_connect() {
|
||||
int8_t ret;
|
||||
|
||||
// Stop if already connected.
|
||||
if (mqtt.connected()) {
|
||||
return;
|
||||
fmueller
commented
I'd highly appreciate if you'd avoid the use of inline comments. I'd highly appreciate if you'd avoid the use of inline comments.
burned42
commented
I think I copied the whole function from some code in an example of one of the libraries. Feel free to adjust to your prefered code style. I think I copied the whole function from some code in an example of one of the libraries. Feel free to adjust to your prefered code style.
fmueller
commented
Will do. I'll also test the USB flashing locally. Will do. I'll also test the USB flashing locally.
|
||||
}
|
||||
|
||||
uint8_t retries = 6;
|
||||
// connect will return 0 for connected
|
||||
while ((ret = mqtt.connect()) != 0) {
|
||||
fmueller marked this conversation as resolved
Outdated
fmueller
commented
A chance to flash it while it's in the back-off? I'd appreciate a comment explaining this line. A chance to flash it while it's in the back-off? I'd appreciate a comment explaining this line.
burned42
commented
see above, I copied this from an example from one of the used libraries see above, I copied this from an example from one of the used libraries
|
||||
mqtt.disconnect();
|
||||
|
||||
// wait 10s till next try
|
||||
for (int i = 0; i < 10000; i += 5) {
|
||||
ArduinoOTA.handle();
|
||||
delay(5);
|
||||
fmueller marked this conversation as resolved
fmueller
commented
Why not restart right away? I mean, sure, it's not quite necessary to do so, but it wouldn't hurt a lot either. Is there some rate limit? Why not restart right away? I mean, sure, it's not quite necessary to do so, but it wouldn't hurt a lot either. Is there some rate limit?
burned42
commented
see above, I copied this from an example from one of the used libraries see above, I copied this from an example from one of the used libraries
|
||||
}
|
||||
|
||||
retries--;
|
||||
if (retries == 0) {
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
85
src/util.h
@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
// TODO: reverse engineer builtin and custom bitmaps
|
||||
|
||||
// seems like our sign listens to messages on this address
|
||||
@ -246,6 +250,82 @@ enum class ExtendedChar {
|
||||
GreekSmallLetterPhi = 0xed,
|
||||
};
|
||||
|
||||
std::unordered_map<uint16_t, uint8_t> createUnicodeToCP437Map() {
|
||||
std::unordered_map<uint16_t, uint8_t> cp437_map;
|
||||
|
||||
cp437_map[0x00E4] = 0x84; // ä in UTF-16
|
||||
cp437_map[0xC3A4] = 0x84; // ä in UTF-8
|
||||
cp437_map[0x00F6] = 0x94; // ö in UTF-16
|
||||
cp437_map[0xC3B6] = 0x94; // ö in UTF-8
|
||||
cp437_map[0x00FC] = 0x81; // ü in UTF-16
|
||||
cp437_map[0xC3BC] = 0x81; // ü in UTF-8
|
||||
cp437_map[0x00C4] = 0x8E; // Ä in UTF-16
|
||||
cp437_map[0xC384] = 0x8E; // Ä in UTF-8
|
||||
cp437_map[0x00D6] = 0x99; // Ö in UTF-16
|
||||
cp437_map[0xC396] = 0x99; // Ö in UTF-8
|
||||
cp437_map[0x00DC] = 0x9A; // Ü in UTF-16
|
||||
cp437_map[0xC39C] = 0x9A; // Ü in UTF-8
|
||||
cp437_map[0x00DF] = 0xE1; // ß in UTF-16
|
||||
cp437_map[0xC39F] = 0xE1; // ß in UTF-8
|
||||
cp437_map[0x20AC] = 0xB1; // € in UTF-16
|
||||
cp437_map[0xE282AC] = 0xB1; // € in UTF-8 (3 Bytes)
|
||||
cp437_map[0x00B5] = 0xE6; // µ in UTF-16
|
||||
cp437_map[0xC2B5] = 0xE6; // µ in UTF-8
|
||||
|
||||
return cp437_map;
|
||||
}
|
||||
|
||||
String convertToCP437(const String& input) {
|
||||
std::unordered_map<uint16_t, uint8_t> cp437_map = createUnicodeToCP437Map();
|
||||
String output = "";
|
||||
size_t len = input.length();
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char ch = input[i];
|
||||
|
||||
// UTF-8 detection: check for 3 byte UTF-8
|
||||
if ((ch & 0xF0) == 0xE0) {
|
||||
if (i + 2 < len) {
|
||||
uint32_t utf8_char = ((uint8_t)input[i] << 16) | ((uint8_t)input[i + 1] << 8) | (uint8_t)input[i + 2];
|
||||
// skip 2 additional bytes
|
||||
i += 2;
|
||||
if (cp437_map.find(utf8_char) != cp437_map.end()) {
|
||||
output += static_cast<char>(cp437_map[utf8_char]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// UTF-8 detection: check for 2 byte UTF-8
|
||||
else if ((ch & 0xE0) == 0xC0) {
|
||||
if (i + 1 < len) {
|
||||
uint16_t utf8_char = ((uint8_t)input[i] << 8) | (uint8_t)input[i + 1];
|
||||
// skip second byte
|
||||
i++;
|
||||
if (cp437_map.find(utf8_char) != cp437_map.end()) {
|
||||
output += static_cast<char>(cp437_map[utf8_char]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// single UTF-16 character (or plain ANSI character)
|
||||
else if ((uint8_t)ch < 0x80) {
|
||||
// keep ASCII as is
|
||||
output += ch;
|
||||
}
|
||||
else {
|
||||
uint16_t utf16_char = (uint8_t)ch;
|
||||
// check if character is in UTF-16 range, fallback to ?
|
||||
if (cp437_map.find(utf16_char) != cp437_map.end()) {
|
||||
output += static_cast<char>(cp437_map[utf16_char]);
|
||||
} else {
|
||||
output += '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
String hexDump(String in) {
|
||||
String out;
|
||||
for (size_t i = 0; i < in.length(); ++i) {
|
||||
@ -287,7 +367,6 @@ String hexDump(String in) {
|
||||
* - stay time: 4
|
||||
* - brightness: bright
|
||||
*/
|
||||
|
||||
void sendTextToSign(
|
||||
const String &text,
|
||||
const Method method = Method::Cyclic,
|
||||
@ -307,7 +386,9 @@ void sendTextToSign(
|
||||
|
||||
Serial.println("Sending text: \"" + text + "\"");
|
||||
|
||||
String toSend = "~" + String(SIGN_ADDRESS, DEC) + "~f01" + (char) method + "\\" + (char) brightness + "\\Y" + String(speed, DEC) + "\\Z" + String(stayTime, DEC) + "\\" + (char) fontType + text + "\r\r\r";
|
||||
String translatedText = convertToCP437(text);
|
||||
|
||||
String toSend = "~" + String(SIGN_ADDRESS, DEC) + "~f01" + (char) method + "\\" + (char) brightness + "\\Y" + String(speed, DEC) + "\\Z" + String(stayTime, DEC) + "\\" + (char) fontType + translatedText + "\r\r\r";
|
||||
|
||||
Serial1.print(toSend);
|
||||
|
||||
|
I presume with this change, flashing initially with USB is no longer an option? I.e., this needs to be commented? If so, I'd appreciate a short comment. It's been a really long time since I've used ArduinoOTA.
I honestly have no idea. I was happy when I got this working with the platform io thingy.