Compare commits
	
		
			5 Commits
		
	
	
		
			main
			...
			165540c120
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 165540c120 | |||
| ad4ee16f19 | |||
| 610f86750b | |||
| c78fde5d9f | |||
| 34bd9746d1 | 
| @@ -13,10 +13,11 @@ platform = espressif8266 | ||||
| framework = arduino | ||||
| monitor_speed = 115200 | ||||
| upload_speed = 500000 | ||||
| upload_protocol = espota | ||||
| upload_port = led-marquee-sign | ||||
|  | ||||
| # 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"; | ||||
| } | ||||
|   | ||||
							
								
								
									
										67
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								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,17 @@ | ||||
| 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() | ||||
|  | ||||
| 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() { | ||||
|     // serial console, for use via USB (also exposed to TXD0/RXD0 GPIOs) | ||||
|     Serial.begin(115200); | ||||
| @@ -20,7 +36,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 +51,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 +69,41 @@ 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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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 | ||||
|  | ||||
| #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); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user