Compare commits
	
		
			5 Commits
		
	
	
		
			main
			...
			165540c120
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 165540c120 | |||
| ad4ee16f19 | |||
| 610f86750b | |||
| c78fde5d9f | |||
| 34bd9746d1 | 
@@ -13,10 +13,11 @@ 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
 | 
				
			||||||
[env:nodemcuv2]
 | 
					lib_ignore = WiFi101
 | 
				
			||||||
board = nodemcuv2
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,4 +5,9 @@ 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";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										67
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								src/main.cpp
									
									
									
									
									
								
							@@ -1,6 +1,11 @@
 | 
				
			|||||||
 | 
					#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"
 | 
				
			||||||
@@ -10,6 +15,17 @@
 | 
				
			|||||||
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");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
@@ -20,7 +36,10 @@ 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);
 | 
				
			||||||
@@ -32,16 +51,13 @@ 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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,4 +69,41 @@ 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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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,5 +1,9 @@
 | 
				
			|||||||
#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
 | 
				
			||||||
@@ -246,6 +250,82 @@ 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) {
 | 
				
			||||||
@@ -287,7 +367,6 @@ 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,
 | 
				
			||||||
@@ -307,7 +386,9 @@ void sendTextToSign(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Serial.println("Sending text: \"" + text + "\"");
 | 
					    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);
 | 
					    Serial1.print(toSend);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user