forked from fmueller/esp8266-led-marquee-sign-controller
Compare commits
No commits in common. "add_mqtt" and "main" have entirely different histories.
@ -13,11 +13,10 @@ platform = espressif8266
|
|||||||
framework = arduino
|
framework = arduino
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 500000
|
upload_speed = 500000
|
||||||
upload_protocol = espota
|
|
||||||
upload_port = led-marquee-sign
|
|
||||||
|
|
||||||
# Serial1 (sign data) maps to D4 by default
|
# Serial1 (sign data) maps to D4 by default
|
||||||
[env:d1_mini]
|
[env:d1_mini]
|
||||||
board = d1_mini
|
board = d1_mini
|
||||||
lib_deps = adafruit/Adafruit MQTT Library@^2.5.8
|
|
||||||
lib_ignore = WiFi101
|
[env:nodemcuv2]
|
||||||
|
board = nodemcuv2
|
||||||
|
@ -5,9 +5,4 @@ namespace credentials {
|
|||||||
const String psk = "my PSK";
|
const String psk = "my PSK";
|
||||||
|
|
||||||
const String mdnsHostname = "led-marquee-sign";
|
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
70
src/main.cpp
@ -1,11 +1,6 @@
|
|||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <ESP8266WiFiMulti.h>
|
#include <ESP8266WiFiMulti.h>
|
||||||
#include <ESP8266mDNS.h>
|
#include <ESP8266mDNS.h>
|
||||||
#include <ESP8266WebServer.h>
|
#include <ESP8266WebServer.h>
|
||||||
#include <WiFiUdp.h>
|
|
||||||
#include <ArduinoOTA.h>
|
|
||||||
#include "Adafruit_MQTT.h"
|
|
||||||
#include "Adafruit_MQTT_Client.h"
|
|
||||||
|
|
||||||
#include "credentials.h"
|
#include "credentials.h"
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
@ -15,18 +10,6 @@
|
|||||||
static ESP8266WiFiMulti wifiMulti;
|
static ESP8266WiFiMulti wifiMulti;
|
||||||
static SocketServer socketServer;
|
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()
|
|
||||||
|
|
||||||
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() {
|
void setup() {
|
||||||
// serial console, for use via USB (also exposed to TXD0/RXD0 GPIOs)
|
// serial console, for use via USB (also exposed to TXD0/RXD0 GPIOs)
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
@ -37,10 +20,7 @@ void setup() {
|
|||||||
Serial1.begin(9600);
|
Serial1.begin(9600);
|
||||||
|
|
||||||
// configure WiFi and connect to the network
|
// 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());
|
wifiMulti.addAP(credentials::ssid.c_str(), credentials::psk.c_str());
|
||||||
|
|
||||||
Serial.println("Connecting to Wi-Fi network...");
|
Serial.println("Connecting to Wi-Fi network...");
|
||||||
while (wifiMulti.run() != WL_CONNECTED) {
|
while (wifiMulti.run() != WL_CONNECTED) {
|
||||||
delay(1000);
|
delay(1000);
|
||||||
@ -52,13 +32,16 @@ void setup() {
|
|||||||
Serial.print(", IP address: ");
|
Serial.print(", IP address: ");
|
||||||
Serial.println(WiFi.localIP());
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
ArduinoOTA.begin();
|
|
||||||
|
|
||||||
mqtt.subscribe(&mqtt_subscribe_topic);
|
|
||||||
|
|
||||||
// might be handy to have the IP written on the sign
|
// might be handy to have the IP written on the sign
|
||||||
sendTextToSign(WiFi.localIP().toString());
|
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
|
// configure HTTP endpoints
|
||||||
configureWebServer();
|
configureWebServer();
|
||||||
|
|
||||||
@ -70,43 +53,4 @@ void setup() {
|
|||||||
void loop() {
|
void loop() {
|
||||||
webServer.handleClient();
|
webServer.handleClient();
|
||||||
socketServer.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t retries = 6;
|
|
||||||
// connect will return 0 for connected
|
|
||||||
while ((ret = mqtt.connect()) != 0) {
|
|
||||||
mqtt.disconnect();
|
|
||||||
|
|
||||||
// wait 10s till next try
|
|
||||||
for (int i = 0; i < 10000; i += 5) {
|
|
||||||
ArduinoOTA.handle();
|
|
||||||
delay(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
retries--;
|
|
||||||
if (retries == 0) {
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
85
src/util.h
85
src/util.h
@ -1,9 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
// TODO: reverse engineer builtin and custom bitmaps
|
// TODO: reverse engineer builtin and custom bitmaps
|
||||||
|
|
||||||
// seems like our sign listens to messages on this address
|
// seems like our sign listens to messages on this address
|
||||||
@ -250,82 +246,6 @@ enum class ExtendedChar {
|
|||||||
GreekSmallLetterPhi = 0xed,
|
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 hexDump(String in) {
|
||||||
String out;
|
String out;
|
||||||
for (size_t i = 0; i < in.length(); ++i) {
|
for (size_t i = 0; i < in.length(); ++i) {
|
||||||
@ -367,6 +287,7 @@ String hexDump(String in) {
|
|||||||
* - stay time: 4
|
* - stay time: 4
|
||||||
* - brightness: bright
|
* - brightness: bright
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void sendTextToSign(
|
void sendTextToSign(
|
||||||
const String &text,
|
const String &text,
|
||||||
const Method method = Method::Cyclic,
|
const Method method = Method::Cyclic,
|
||||||
@ -386,9 +307,7 @@ void sendTextToSign(
|
|||||||
|
|
||||||
Serial.println("Sending text: \"" + text + "\"");
|
Serial.println("Sending text: \"" + text + "\"");
|
||||||
|
|
||||||
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 + text + "\r\r\r";
|
||||||
|
|
||||||
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);
|
Serial1.print(toSend);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user