From fdc6e8ae48c6f867fe079ec4067bffc5d761ffba Mon Sep 17 00:00:00 2001 From: Kater_S Date: Mon, 28 Dec 2020 14:16:07 +0100 Subject: [PATCH 01/12] minor improvements outsourced font stuff into font.h; introduced TOPICROOT (default: "ledMatrix"); device now has a unique name derived from serial no. part of MAC address, used for individual subscriptions and for publishing status messages; more robust text display (skip undefined glyphs) --- MarQueTT.ino | 335 +++++++------------------------------------- Readme.md | 55 +++++--- font.h | 264 ++++++++++++++++++++++++++++++++++ local_config.dist.h | 6 + 4 files changed, 359 insertions(+), 301 deletions(-) create mode 100644 font.h diff --git a/MarQueTT.ino b/MarQueTT.ino index dab984f..9539c6e 100644 --- a/MarQueTT.ino +++ b/MarQueTT.ino @@ -1,8 +1,13 @@ #include #include #include +#include "font.h" #include "local_config.h" +#ifndef TOPICROOT +#define TOPICROOT "ledMatrix" +#endif + uint8_t text[MAX_TEXT_LENGTH]; LEDMatrixDriver led(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN, 0); uint16_t textIndex = 0; @@ -11,6 +16,7 @@ uint16_t scrollWhitespace = 0; uint64_t marqueeDelayTimestamp = 0; uint64_t marqueeBlinkTimestamp; uint16_t blinkDelay = 0; +char devname[20]; WiFiClient espClient; PubSubClient client(espClient); @@ -27,7 +33,7 @@ void setup() { led.setIntensity(0); led.setEnabled(true); calculate_font_index(); - client.publish("ledMatrix/status", "startup"); + client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "startup"); } void loop() @@ -48,11 +54,12 @@ void loop() loop_matrix(); if (!client.connected()) { reconnect(); - client.publish("ledMatrix/status", "reconnect"); + client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "reconnect"); } client.loop(); } + void setup_wifi() { delay(10); Serial.println(); @@ -66,8 +73,15 @@ void setup_wifi() { } randomSeed(micros()); Serial.println(""); - Serial.print("WiFi connected - IP address: "); - Serial.println(WiFi.localIP()); + Serial.print("WiFi connected"); + Serial.print(" - IP address: "); + Serial.print(WiFi.localIP()); + Serial.print(" - MAC address: "); + Serial.println(WiFi.macAddress()); + byte mac[6]; + WiFi.macAddress(mac); + snprintf(devname, sizeof(devname), "MarQueTTino/%02X%02X%02X", mac[3], mac[4], mac[5]); + Serial.println((String)"This device is called '" + devname + "'."); } void printHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with leading zeroes @@ -81,11 +95,13 @@ void printHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with l void callback(char* topic, byte* payload, unsigned int length) { - Serial.print((String)"MQTT in: " + topic + "\t = "); + Serial.print((String)"MQTT in: " + topic + "\t = ["); for (int i = 0; i < length; i++) Serial.print((char)(payload[i])); - Serial.println(); + Serial.println("]"); + + char* command = topic + String(topic).lastIndexOf("/") + 1; - if (!strcmp(topic, "ledMatrix/intensity")) { + if (!strcmp(command, "intensity")) { int intensity = 0; for (int i = 0 ; i < length; i++) { intensity *= 10; @@ -96,7 +112,7 @@ void callback(char* topic, byte* payload, unsigned int length) { return; } - if (!strcmp(topic, "ledMatrix/delay")) { + if (!strcmp(command, "delay")) { scrollDelay = 0; for (int i = 0 ; i < length; i++) { scrollDelay *= 10; @@ -107,14 +123,14 @@ void callback(char* topic, byte* payload, unsigned int length) { } else if (scrollDelay < 1) { scrollDelay = 1; } - if (scrollDelay > 1000) { - scrollDelay = 1000; + if (scrollDelay > 10000) { + scrollDelay = 10000; } return; } - if (!strcmp(topic, "ledMatrix/blink")) { + if (!strcmp(command, "blink")) { blinkDelay = 0; for (int i = 0 ; i < length; i++) { blinkDelay *= 10; @@ -136,13 +152,13 @@ void callback(char* topic, byte* payload, unsigned int length) { } - if (!strcmp(topic, "ledMatrix/enable")) { + if (!strcmp(command, "enable")) { led.setEnabled(payload[0] == '1'); } - if (!strcmp(topic, "ledMatrix/text")) { - const bool pr = 0; // set to 1 for debug prints + if (!strcmp(command, "text")) { + const bool pr = 1; // set to 1 for debug prints if (pr) printHex8(payload, length); text[0] = ' '; for (int i = 0 ; i < 4096; i++) { @@ -218,6 +234,15 @@ void callback(char* topic, byte* payload, unsigned int length) { if (pr) Serial.println(); } } + for (int i = 0; i < j; i++) { + uint8_t asc = text[i] - 32; + uint16_t idx = pgm_read_word(&(font_index[asc])); + uint8_t w = pgm_read_byte(&(font[idx])); + if (w == 0) { + // character is NOT defined, replace with 0x7f = checkerboard pattern + text[i] = 0x7f; + } + } if (pr) Serial.print((String)"=> Text (" + j + " bytes): "); if (pr) printHex8(text, j + 1); @@ -234,13 +259,18 @@ void reconnect() { Serial.print("Attempting MQTT connection..."); String clientId = "ESP8266Client-"; clientId += String(random(0xffff), HEX); - if (client.connect(clientId.c_str(), mqtt_username, mqtt_password, "ledMatrix/status", 1, true, "Offline")) { + if (client.connect(clientId.c_str(), mqtt_username, mqtt_password, TOPICROOT "/status", 1, true, "Offline")) { Serial.println("connected"); - client.subscribe("ledMatrix/enable"); - client.subscribe("ledMatrix/intensity"); - client.subscribe("ledMatrix/delay"); - client.subscribe("ledMatrix/text"); - client.subscribe("ledMatrix/blink"); + client.subscribe(TOPICROOT "/enable"); + client.subscribe(((String)TOPICROOT "/" + devname + "/enable").c_str()); + client.subscribe(TOPICROOT "/intensity"); + client.subscribe(((String)TOPICROOT "/" + devname + "/intensity").c_str()); + client.subscribe(TOPICROOT "/delay"); + client.subscribe(((String)TOPICROOT "/" + devname + "/delay").c_str()); + client.subscribe(TOPICROOT "/text"); + client.subscribe(((String)TOPICROOT "/" + devname + "/text").c_str()); + client.subscribe(TOPICROOT "/blink"); + client.subscribe(((String)TOPICROOT "/" + devname + "/blink").c_str()); } else { Serial.print("failed, rc="); Serial.print(client.state()); @@ -252,269 +282,6 @@ void reconnect() { const uint16_t LEDMATRIX_WIDTH = LEDMATRIX_SEGMENTS * 8; -static const uint8_t font[] PROGMEM = { - 2, 0b00000000, 0b00000000, /* 032 = */ - 2, 0b01101111, 0b01101111, /* 033 = ! */ - 3, 0b00000011, 0b00000000, 0b00000011, /* 034 = " */ - 5, 0b00010100, 0b01111111, 0b00010100, 0b01111111, 0b00010100, /* 035 = # */ - 5, 0b00100100, 0b00101010, 0b01111111, 0b00101010, 0b00010010, /* 036 = $ */ - 5, 0b00100011, 0b00010011, 0b00001000, 0b01100100, 0b01100010, /* 037 = % */ - 5, 0b00110100, 0b01001010, 0b01001010, 0b00110100, 0b01010000, /* 038 = & */ - 1, 0b00000011, /* 039 = ' */ - 3, 0b00011100, 0b00100010, 0b01000001, /* 040 = ( */ - 3, 0b01000001, 0b00100010, 0b00011100, /* 041 = ) */ - 3, 0b00000101, 0b00000010, 0b00000101, /* 042 = * */ - 5, 0b00001000, 0b00001000, 0b00111110, 0b00001000, 0b00001000, /* 043 = + */ - 2, 0b11100000, 0b01100000, /* 044 = , */ - 5, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, /* 045 = - */ - 2, 0b01100000, 0b01100000, /* 046 = . */ - 5, 0b01000000, 0b00110000, 0b00001000, 0b00000110, 0b00000001, /* 047 = / */ - 5, 0b00111110, 0b01010001, 0b01001001, 0b01000101, 0b00111110, /* 048 = 0 */ - 5, 0b00000000, 0b01000010, 0b01111111, 0b01000000, 0b00000000, /* 049 = 1 */ - 5, 0b01000010, 0b01100001, 0b01010001, 0b01001001, 0b01000110, /* 050 = 2 */ - 5, 0b00100001, 0b01000001, 0b01000101, 0b01001011, 0b00110001, /* 051 = 3 */ - 5, 0b00011000, 0b00010100, 0b00010010, 0b01111111, 0b00010000, /* 052 = 4 */ - 5, 0b00100111, 0b01000101, 0b01000101, 0b01000101, 0b00111001, /* 053 = 5 */ - 5, 0b00111100, 0b01001010, 0b01001001, 0b01001001, 0b00110000, /* 054 = 6 */ - 5, 0b00000001, 0b00000001, 0b01111001, 0b00000101, 0b00000011, /* 055 = 7 */ - 5, 0b00110110, 0b01001001, 0b01001001, 0b01001001, 0b00110110, /* 056 = 8 */ - 5, 0b00000110, 0b01001001, 0b01001001, 0b00101001, 0b00011110, /* 057 = 9 */ - 2, 0b00110110, 0b00110110, /* 058 = : */ - 2, 0b01110110, 0b00110110, /* 059 = ; */ - 4, 0b00001000, 0b00010100, 0b00100010, 0b01000001, /* 060 = < */ - 5, 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00010100, /* 061 = = */ - 4, 0b01000001, 0b00100010, 0b00010100, 0b00001000, /* 062 = > */ - 5, 0b00000010, 0b00000001, 0b01010001, 0b00001001, 0b00000110, /* 063 = ? */ - 5, 0b00111110, 0b01000001, 0b01011101, 0b01010101, 0b01011110, /* 064 = @ */ - 5, 0b01111110, 0b00001001, 0b00001001, 0b00001001, 0b01111110, /* 065 = A */ - 5, 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110110, /* 066 = B */ - 5, 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, /* 067 = C */ - 5, 0b01111111, 0b01000001, 0b01000001, 0b01000001, 0b00111110, /* 068 = D */ - 5, 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01000001, /* 069 = E */ - 5, 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000001, /* 070 = F */ - 5, 0b00111110, 0b01000001, 0b01001001, 0b01001001, 0b01111010, /* 071 = G */ - 5, 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, /* 072 = H */ - 5, 0b01000001, 0b01000001, 0b01111111, 0b01000001, 0b01000001, /* 073 = I */ - 5, 0b00100000, 0b01000001, 0b01000001, 0b00111111, 0b00000001, /* 074 = J */ - 5, 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, /* 075 = K */ - 5, 0b01111111, 0b01000000, 0b01000000, 0b01000000, 0b01000000, /* 076 = L */ - 5, 0b01111111, 0b00000010, 0b00000100, 0b00000010, 0b01111111, /* 077 = M */ - 5, 0b01111111, 0b00000110, 0b00001000, 0b00110000, 0b01111111, /* 078 = N */ - 5, 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, /* 079 = O */ - 5, 0b01111111, 0b00010001, 0b00010001, 0b00010001, 0b00001110, /* 080 = P */ - 5, 0b00111110, 0b01000001, 0b01010001, 0b00100001, 0b01011110, /* 081 = Q */ - 5, 0b01111111, 0b00001001, 0b00011001, 0b00101001, 0b01000110, /* 082 = R */ - 5, 0b00100110, 0b01001001, 0b01001001, 0b01001001, 0b00110010, /* 083 = S */ - 5, 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, /* 084 = T */ - 5, 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b00111111, /* 085 = U */ - 5, 0b00011111, 0b00100000, 0b01000000, 0b00100000, 0b00011111, /* 086 = V */ - 5, 0b00111111, 0b01000000, 0b00110000, 0b01000000, 0b00111111, /* 087 = W */ - 5, 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, /* 088 = X */ - 5, 0b00000111, 0b00001000, 0b01110000, 0b00001000, 0b00000111, /* 089 = Y */ - 5, 0b01100001, 0b01010001, 0b01001001, 0b01000101, 0b01000011, /* 090 = Z */ - 3, 0b01111111, 0b01000001, 0b01000001, /* 091 = [ */ - 5, 0b00000001, 0b00000110, 0b00001000, 0b00110000, 0b01000000, /* 092 = \ */ - 3, 0b01000001, 0b01000001, 0b01111111, /* 093 = ] */ - 5, 0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100, /* 094 = ^ */ - 5, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, /* 095 = _ */ - 1, 0b00000011, /* 096 = ' */ - 5, 0b00100000, 0b01010100, 0b01010100, 0b01010100, 0b01111000, /* 097 = a */ - 5, 0b01111111, 0b00101000, 0b01000100, 0b01000100, 0b00111000, /* 098 = b */ - 5, 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00101000, /* 099 = c */ - 5, 0b00111000, 0b01000100, 0b01000100, 0b00101000, 0b01111111, /* 100 = d */ - 5, 0b00111000, 0b01010100, 0b01010100, 0b01010100, 0b00011000, /* 101 = e */ - 5, 0b00000100, 0b01111110, 0b00000101, 0b00000001, 0b00000010, /* 102 = f */ - 5, 0b00011000, 0b10100100, 0b10100100, 0b10100100, 0b01111100, /* 103 = g */ - 5, 0b01111111, 0b00000100, 0b00000100, 0b00000100, 0b01111000, /* 104 = h */ - 3, 0b01000100, 0b01111101, 0b01000000, /* 105 = i */ - 4, 0b01000000, 0b10000000, 0b10000100, 0b01111101, /* 106 = j */ - 5, 0b01111111, 0b00010000, 0b00010000, 0b00101000, 0b01000100, /* 107 = k */ - 3, 0b01000001, 0b01111111, 0b01000000, /* 108 = l */ - 5, 0b01111100, 0b00000100, 0b01111100, 0b00000100, 0b01111000, /* 109 = m */ - 5, 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b01111000, /* 110 = n */ - 5, 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00111000, /* 111 = o */ - 5, 0b11111100, 0b00100100, 0b00100100, 0b00100100, 0b00011000, /* 112 = p */ - 5, 0b00011000, 0b00100100, 0b00100100, 0b00100100, 0b11111100, /* 113 = q */ - 5, 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b00001000, /* 114 = r */ - 5, 0b01001000, 0b01010100, 0b01010100, 0b01010100, 0b00100000, /* 115 = s */ - 5, 0b00000100, 0b00111110, 0b01000100, 0b01000000, 0b00100000, /* 116 = t */ - 5, 0b00111100, 0b01000000, 0b01000000, 0b00100000, 0b01111100, /* 117 = u */ - 5, 0b00011100, 0b00100000, 0b01000000, 0b00100000, 0b00011100, /* 118 = v */ - 5, 0b00111100, 0b01000000, 0b00110000, 0b01000000, 0b00111100, /* 119 = w */ - 5, 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, /* 120 = x */ - 5, 0b00000100, 0b01001000, 0b00110000, 0b00001000, 0b00000100, /* 121 = y */ - 5, 0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, /* 122 = z */ - 3, 0b00001000, 0b00110110, 0b01000001, /* 123 = { */ - 1, 0b01111111, /* 124 = | */ - 3, 0b01000001, 0b00110110, 0b00001000, /* 125 = } */ - 5, 0b00011000, 0b00000100, 0b00001000, 0b00010000, 0b00001100, /* 126 = ~ */ - // extension: German Umlauts - 5, 0b01010101, 0b10101010, 0b01010101, 0b10101010, 0b01010101, /* 127 = checkerboard pattern */ - 5, 0b00011100, 0b00110110, 0b01010101, 0b01010101, 0b01000001, /* 128 = € */ - 0, /* 129 =  */ - 0, /* 130 = ‚ */ - 0, /* 131 = ƒ */ - 0, /* 132 = „ */ - 5, 0b01000000, 0b00000000, 0b01000000, 0b00000000, 0b01000000, /* 133 = … */ - 0, /* 134 = † */ - 0, /* 135 = ‡ */ - 0, /* 136 = ˆ */ - 0, /* 137 = ‰ */ - 0, /* 138 = Š */ - 0, /* 139 = ‹ */ - 0, /* 140 = Œ */ - 0, /* 141 =  */ - 0, /* 142 = Ž */ - 0, /* 143 =  */ - 0, /* 144 =  */ - 0, /* 145 = ‘ */ - 0, /* 146 = ’ */ - 0, /* 147 = “ */ - 0, /* 148 = ” */ - 0, /* 149 = • */ - 4, 0b00001000, 0b00001000, 0b00001000, 0b00001000, /* 150 = – */ - 5, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, /* 151 = — */ - 0, /* 152 = ˜ */ - 0, /* 153 = ™ */ - 0, /* 154 = š */ - 0, /* 155 = › */ - 0, /* 156 = œ */ - 0, /* 157 =  */ - 0, /* 158 = ž */ - 0, /* 159 = Ÿ */ - 0, /* 160 =   */ - 0, /* 161 = ¡ */ - 0, /* 162 = ¢ */ - 0, /* 163 = £ */ - 0, /* 164 = ¤ */ - 0, /* 165 = ¥ */ - 0, /* 166 = ¦ */ - 5, 0b00001010, 0b01010101, 0b01010101, 0b01010101, 0b00101000, /* 167 = § */ - 0, /* 168 = ¨ */ - 0, /* 169 = © */ - 0, /* 170 = ª */ - 0, /* 171 = « */ - 0, /* 172 = ¬ */ - 0, /* 173 = ­ */ - 0, /* 174 = ® */ - 0, /* 175 = ¯ */ - 4, 0b00000110, 0b00001001, 0b00001001, 0b00000110, /* 176 = ° */ - 0, /* 177 = ± */ - 0, /* 178 = ² */ - 0, /* 179 = ³ */ - 0, /* 180 = ´ */ - 5, 0b11111100, 0b00100000, 0b01000000, 0b00100000, 0b01111100, /* 181 = µ */ - 0, /* 182 = ¶ */ - 0, /* 183 = · */ - 0, /* 184 = ¸ */ - 0, /* 185 = ¹ */ - 0, /* 186 = º */ - 0, /* 187 = » */ - 0, /* 188 = ¼ */ - 0, /* 189 = ½ */ - 0, /* 190 = ¾ */ - 0, /* 191 = ¿ */ - 0, /* 192 = À */ - 0, /* 193 = Á */ - 0, /* 194 =  */ - 0, /* 195 = à */ - 5, 0b11111000, 0b00010101, 0b00010100, 0b00010101, 0b11111000, /* 196 = Ä */ - 0, /* 197 = Å */ - 0, /* 198 = Æ */ - 0, /* 199 = Ç */ - 0, /* 200 = È */ - 0, /* 201 = É */ - 0, /* 202 = Ê */ - 0, /* 203 = Ë */ - 0, /* 204 = Ì */ - 0, /* 205 = Í */ - 0, /* 206 = Î */ - 0, /* 207 = Ï */ - 0, /* 208 = Ð */ - 0, /* 209 = Ñ */ - 0, /* 210 = Ò */ - 0, /* 211 = Ó */ - 0, /* 212 = Ô */ - 0, /* 213 = Õ */ - 5, 0b01111000, 0b10000101, 0b10000100, 0b10000101, 0b01111000, /* 214 = Ö */ - 0, /* 215 = × */ - 0, /* 216 = Ø */ - 0, /* 217 = Ù */ - 0, /* 218 = Ú */ - 0, /* 219 = Û */ - 5, 0b01111100, 0b10000001, 0b10000000, 0b10000001, 0b11111100, /* 220 = Ü */ - 0, /* 221 = Ý */ - 0, /* 222 = Þ */ - 5, 0b11111110, 0b00000001, 0b01000101, 0b01001010, 0b00110000, /* 223 = ß */ - 0, /* 224 = à */ - 0, /* 225 = á */ - 0, /* 226 = â */ - 0, /* 227 = ã */ - 5, 0b00100000, 0b01010101, 0b01010100, 0b01010101, 0b01111000, /* 228 = ä */ - 0, /* 229 = å */ - 0, /* 230 = æ */ - 0, /* 231 = ç */ - 0, /* 232 = è */ - 0, /* 233 = é */ - 0, /* 234 = ê */ - 0, /* 235 = ë */ - 0, /* 236 = ì */ - 0, /* 237 = í */ - 0, /* 238 = î */ - 0, /* 239 = ï */ - 0, /* 240 = ð */ - 0, /* 241 = ñ */ - 0, /* 242 = ò */ - 0, /* 243 = ó */ - 0, /* 244 = ô */ - 0, /* 245 = õ */ - 5, 0b00111000, 0b01000101, 0b01000100, 0b01000101, 0b00111000, /* 246 = ö */ - 0, /* 247 = ÷ */ - 0, /* 248 = ø */ - 0, /* 249 = ù */ - 0, /* 250 = ú */ - 0, /* 251 = û */ - 5, 0b00111100, 0b01000001, 0b01000000, 0b00100001, 0b01111100, /* 252 = ü */ - 0, /* 253 = ý */ - 0, /* 254 = þ */ - 0, /* 255 = ÿ*/ -}; - -uint16_t* font_index; // will be calculated at start by calculate_font_index() -/**static const uint16_t font_index[95] PROGMEM = { - 0, 3, 6, 10, 16, 22, 28, 34, 36, 40, 44, 48, 54, 57, 63, 66, 72, 78, 82, 88, 94, - 100, 106, 112, 118, 124, 130, 133, 136, 141, 147, 152, 158, 164, 170, 176, - 182, 188, 194, 200, 206, 212, 218, 224, 230, 236, 242, 248, 254, 260, 266, - 272, 278, 284, 290, 296, 302, 308, 314, 320, 324, 330, 334, 340, 346, 348, - 354, 360, 366, 372, 378, 384, 390, 396, 400, 405, 411, 415, 421, 427, 433, - 439, 445, 451, 457, 463, 469, 475, 481, 487, 493, 499, 503, 505, 509 - };**/ - -void calculate_font_index() -{ - uint16 fontsize = sizeof(font); - Serial.println((String)"font uses " + fontsize + " bytes."); - uint8_t* fontptr = (uint8_t*)font; - int num_chars = 0; - // 1st sweep: count chars - while (fontptr < font + fontsize) { - int char_width = pgm_read_byte(fontptr); //*fontptr; - fontptr += char_width + 1; // add character X size - num_chars++; - } - // allocate index table - uint16_t* index = (uint16_t*)malloc(num_chars * sizeof(uint16_t)); - // 2nd sweep: calculate index values - fontptr = (uint8_t*)font; - for (int i = 0; i < num_chars; i++) { - index[i] = fontptr - font; - int char_width = pgm_read_byte(fontptr); //*fontptr; - //Serial.println((String)"idx " + i + " = " + (char)(i+32) + ": " + char_width + " -> " + index[i]); - fontptr += char_width + 1; // add character X size - } - font_index = index; -} - void nextChar() { if (text[++textIndex] == '\0') @@ -522,7 +289,7 @@ void nextChar() textIndex = 0; scrollWhitespace = LEDMATRIX_WIDTH; // start over with empty display if (scrollDelay) - client.publish("ledMatrix/status", "repeat"); + client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "repeat"); } } diff --git a/Readme.md b/Readme.md index 322b4ba..6294d44 100644 --- a/Readme.md +++ b/Readme.md @@ -1,35 +1,56 @@ -# MarQueeTT +# MarQueeTT[ino] ## A MAX7221 LED-Matrix Scrolling Marquee controlled via MQTT -### Configuration -Copy `local_config.dist.h` to `local_config.h` and configure to your needs. -`local_config.h` is ignored by git your secrets are safe there. +### MQTT Topics +#### Global Topics -### MQTT Topics +##### Subscribed by Device + +- `ledMatrix/text` : A UTF-8 coded text, max. 4096 bytes long. +- `ledMatrix/intensity`: 0 = lowest, 15 = highest. Default: 1. +- `ledMatrix/delay`: 0 = no scrolling; 1 = fastest, 1000 = slowest scrolling. Default: 25 +- `ledMatrix/blink`: 0 = no blinking; 1 = fastest, 1000 = slowest blinking. Default: 0 +- `ledMatrix/enable`: 0 = display off, 1 = display on. Default: 1 + +#### Individual Topics -#### Subscribed by Marquee +##### Subscribed by Device -- ledMatrix/text: A UTF-8 coded text, max. 4096 bytes long. -- ledMatrix/intensity: 0 = lowest, 15 = highest. Default: 1. -- ledMatrix/delay: 1 = fastest, 1000 = slowest scrolling. Default: 25, 0 for static text. -- ledMatrix/blink: 0 = no blinking; 1 = fastest, 1000 = slowest blinking. Default: 0 -- ledMatrix/enable: 0 = display off, 1 = display on. Default: 1 +Same as global topics, but `ledMatrix//...` instead of `ledMatrix/...` ( == serial number in 3 hex bytes). -#### Published by Marquee +TBD: priority of individual topics over global topics ? -- ledMatrix/status: startup, reconnect, repeat, offline +Extension: text channels, `` = 0..9 + +- `ledMatrix/text/` text for a specific channel (`.../text/0` = same as `.../text`) +- `ledMatrix/channel` set channels to be displayed + - `[,…]` list of channels + - `` only one channel + - "" (empty) only channel 0 + +##### Published by Device + +- ledMatrix/aabbcc/status: + - `startup` — sent on system start (after first MQTT connect) + - `reconnect` — sent on MQTT reconnect + - `repeat` — sent at end of a sequence (TBD: for each channel or total sequence?) + - `offline` — sent as will when the MQTT connection disconnects unexpectedly ### Wiring Connect your display's X pin to your controller's Y pin: -- Data IN to MOSI -- CLK to SCK -- CS to any digial port except MISO or SS +- `Data IN` to `MOSI` +- `CLK` to `SCK` +- `CS` to any digial port except `MISO` or `SS` (e.g. `D4`) See https://github.com/bartoszbielawski/LEDMatrixDriver#pin-selection for more information +### Extension Ideas + +- WS2812 LED (one or more?) for quick signalling +- acoustic output via piezo element for signalling +- some push buttons for remote feedback or for local display control - diff --git a/font.h b/font.h new file mode 100644 index 0000000..e598a18 --- /dev/null +++ b/font.h @@ -0,0 +1,264 @@ + + +static const uint8_t font[] PROGMEM = { + 2, 0b00000000, 0b00000000, /* 032 = */ + 2, 0b01101111, 0b01101111, /* 033 = ! */ + 3, 0b00000011, 0b00000000, 0b00000011, /* 034 = " */ + 5, 0b00010100, 0b01111111, 0b00010100, 0b01111111, 0b00010100, /* 035 = # */ + 5, 0b00100100, 0b00101010, 0b01111111, 0b00101010, 0b00010010, /* 036 = $ */ + 5, 0b00100011, 0b00010011, 0b00001000, 0b01100100, 0b01100010, /* 037 = % */ + 5, 0b00110100, 0b01001010, 0b01001010, 0b00110100, 0b01010000, /* 038 = & */ + 1, 0b00000011, /* 039 = ' */ + 3, 0b00011100, 0b00100010, 0b01000001, /* 040 = ( */ + 3, 0b01000001, 0b00100010, 0b00011100, /* 041 = ) */ + 3, 0b00000101, 0b00000010, 0b00000101, /* 042 = * */ + 5, 0b00001000, 0b00001000, 0b00111110, 0b00001000, 0b00001000, /* 043 = + */ + 2, 0b11100000, 0b01100000, /* 044 = , */ + 5, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, /* 045 = - */ + 2, 0b01100000, 0b01100000, /* 046 = . */ + 5, 0b01000000, 0b00110000, 0b00001000, 0b00000110, 0b00000001, /* 047 = / */ + 5, 0b00111110, 0b01010001, 0b01001001, 0b01000101, 0b00111110, /* 048 = 0 */ + 5, 0b00000000, 0b01000010, 0b01111111, 0b01000000, 0b00000000, /* 049 = 1 */ + 5, 0b01000010, 0b01100001, 0b01010001, 0b01001001, 0b01000110, /* 050 = 2 */ + 5, 0b00100001, 0b01000001, 0b01000101, 0b01001011, 0b00110001, /* 051 = 3 */ + 5, 0b00011000, 0b00010100, 0b00010010, 0b01111111, 0b00010000, /* 052 = 4 */ + 5, 0b00100111, 0b01000101, 0b01000101, 0b01000101, 0b00111001, /* 053 = 5 */ + 5, 0b00111100, 0b01001010, 0b01001001, 0b01001001, 0b00110000, /* 054 = 6 */ + 5, 0b00000001, 0b00000001, 0b01111001, 0b00000101, 0b00000011, /* 055 = 7 */ + 5, 0b00110110, 0b01001001, 0b01001001, 0b01001001, 0b00110110, /* 056 = 8 */ + 5, 0b00000110, 0b01001001, 0b01001001, 0b00101001, 0b00011110, /* 057 = 9 */ + 2, 0b00110110, 0b00110110, /* 058 = : */ + 2, 0b01110110, 0b00110110, /* 059 = ; */ + 4, 0b00001000, 0b00010100, 0b00100010, 0b01000001, /* 060 = < */ + 5, 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00010100, /* 061 = = */ + 4, 0b01000001, 0b00100010, 0b00010100, 0b00001000, /* 062 = > */ + 5, 0b00000010, 0b00000001, 0b01010001, 0b00001001, 0b00000110, /* 063 = ? */ + 5, 0b00111110, 0b01000001, 0b01011101, 0b01010101, 0b01011110, /* 064 = @ */ + 5, 0b01111110, 0b00001001, 0b00001001, 0b00001001, 0b01111110, /* 065 = A */ + 5, 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110110, /* 066 = B */ + 5, 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, /* 067 = C */ + 5, 0b01111111, 0b01000001, 0b01000001, 0b01000001, 0b00111110, /* 068 = D */ + 5, 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01000001, /* 069 = E */ + 5, 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000001, /* 070 = F */ + 5, 0b00111110, 0b01000001, 0b01001001, 0b01001001, 0b01111010, /* 071 = G */ + 5, 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, /* 072 = H */ + 3, 0b01000001, 0b01111111, 0b01000001, /* 073 = I */ + 5, 0b00100000, 0b01000001, 0b01000001, 0b00111111, 0b00000001, /* 074 = J */ + 5, 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, /* 075 = K */ + 5, 0b01111111, 0b01000000, 0b01000000, 0b01000000, 0b01000000, /* 076 = L */ + 5, 0b01111111, 0b00000010, 0b00000100, 0b00000010, 0b01111111, /* 077 = M */ + 5, 0b01111111, 0b00000100, 0b00001000, 0b00010000, 0b01111111, /* 078 = N */ + 5, 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, /* 079 = O */ + 5, 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000110, /* 080 = P */ + 5, 0b00111110, 0b01000001, 0b01010001, 0b00100001, 0b01011110, /* 081 = Q */ + 5, 0b01111111, 0b00001001, 0b00011001, 0b00101001, 0b01000110, /* 082 = R */ + 5, 0b00100110, 0b01001001, 0b01001001, 0b01001001, 0b00110010, /* 083 = S */ + 5, 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, /* 084 = T */ + 5, 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b00111111, /* 085 = U */ + 5, 0b00011111, 0b00100000, 0b01000000, 0b00100000, 0b00011111, /* 086 = V */ + 5, 0b00111111, 0b01000000, 0b00110000, 0b01000000, 0b00111111, /* 087 = W */ + 5, 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, /* 088 = X */ + 5, 0b00000011, 0b00000100, 0b01111000, 0b00000100, 0b00000011, /* 089 = Y */ + 5, 0b01100001, 0b01010001, 0b01001001, 0b01000101, 0b01000011, /* 090 = Z */ + 3, 0b01111111, 0b01000001, 0b01000001, /* 091 = [ */ + 5, 0b00000001, 0b00000110, 0b00001000, 0b00110000, 0b01000000, /* 092 = \ */ + 3, 0b01000001, 0b01000001, 0b01111111, /* 093 = ] */ + 5, 0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100, /* 094 = ^ */ + 5, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, /* 095 = _ */ + 1, 0b00000011, /* 096 = ' */ + 5, 0b00100000, 0b01010100, 0b01010100, 0b01010100, 0b01111000, /* 097 = a */ + 5, 0b01111111, 0b00101000, 0b01000100, 0b01000100, 0b00111000, /* 098 = b */ + 5, 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00101000, /* 099 = c */ + 5, 0b00111000, 0b01000100, 0b01000100, 0b00101000, 0b01111111, /* 100 = d */ + 5, 0b00111000, 0b01010100, 0b01010100, 0b01010100, 0b00011000, /* 101 = e */ + 4, 0b00000100, 0b01111110, 0b00000101, 0b00000001, /* 102 = f */ + 5, 0b00011000, 0b10100100, 0b10100100, 0b10100100, 0b01111100, /* 103 = g */ + 5, 0b01111111, 0b00000100, 0b00000100, 0b00000100, 0b01111000, /* 104 = h */ + 3, 0b01000100, 0b01111101, 0b01000000, /* 105 = i */ + 4, 0b01000000, 0b10000000, 0b10000100, 0b01111101, /* 106 = j */ + 5, 0b01111111, 0b00010000, 0b00010000, 0b00101000, 0b01000100, /* 107 = k */ + 3, 0b01000001, 0b01111111, 0b01000000, /* 108 = l */ + 5, 0b01111100, 0b00000100, 0b01111100, 0b00000100, 0b01111000, /* 109 = m */ + 5, 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b01111000, /* 110 = n */ + 5, 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00111000, /* 111 = o */ + 5, 0b11111100, 0b00100100, 0b00100100, 0b00100100, 0b00011000, /* 112 = p */ + 5, 0b00011000, 0b00100100, 0b00100100, 0b00100100, 0b11111100, /* 113 = q */ + 4, 0b01111100, 0b00001000, 0b00000100, 0b00001000, /* 114 = r */ + 5, 0b01001000, 0b01010100, 0b01010100, 0b01010100, 0b00100000, /* 115 = s */ + 4, 0b00000100, 0b00111110, 0b01000100, 0b01000000, /* 116 = t */ + 5, 0b00111100, 0b01000000, 0b01000000, 0b00100000, 0b01111100, /* 117 = u */ + 5, 0b00011100, 0b00100000, 0b01000000, 0b00100000, 0b00011100, /* 118 = v */ + 5, 0b00111100, 0b01000000, 0b00110000, 0b01000000, 0b00111100, /* 119 = w */ + 5, 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, /* 120 = x */ + 5, 0b00011100, 0b10100000, 0b10100000, 0b10100000, 0b01111100, /* 121 = y */ + 5, 0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, /* 122 = z */ + 3, 0b00001000, 0b00110110, 0b01000001, /* 123 = { */ + 1, 0b01111111, /* 124 = | */ + 3, 0b01000001, 0b00110110, 0b00001000, /* 125 = } */ + 5, 0b00011000, 0b00000100, 0b00001000, 0b00010000, 0b00001100, /* 126 = ~ */ + // extension: German Umlauts + 5, 0b01010101, 0b10101010, 0b01010101, 0b10101010, 0b01010101, /* 127 = checkerboard pattern */ + 5, 0b00011100, 0b00110110, 0b01010101, 0b01010101, 0b01000001, /* 128 = € */ + 0, /* 129 =  */ + 0, /* 130 = ‚ */ + 0, /* 131 = ƒ */ + 0, /* 132 = „ */ + 5, 0b01000000, 0b00000000, 0b01000000, 0b00000000, 0b01000000, /* 133 = … */ + 0, /* 134 = † */ + 0, /* 135 = ‡ */ + 0, /* 136 = ˆ */ + 0, /* 137 = ‰ */ + 0, /* 138 = Š */ + 0, /* 139 = ‹ */ + 0, /* 140 = Œ */ + 0, /* 141 =  */ + 0, /* 142 = Ž */ + 0, /* 143 =  */ + 0, /* 144 =  */ + 0, /* 145 = ‘ */ + 0, /* 146 = ’ */ + 0, /* 147 = “ */ + 0, /* 148 = ” */ + 0, /* 149 = • */ + 4, 0b00001000, 0b00001000, 0b00001000, 0b00001000, /* 150 = – */ + 5, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, /* 151 = — */ + 0, /* 152 = ˜ */ + 0, /* 153 = ™ */ + 0, /* 154 = š */ + 0, /* 155 = › */ + 0, /* 156 = œ */ + 0, /* 157 =  */ + 0, /* 158 = ž */ + 0, /* 159 = Ÿ */ + 0, /* 160 =   */ + 0, /* 161 = ¡ */ + 0, /* 162 = ¢ */ + 0, /* 163 = £ */ + 0, /* 164 = ¤ */ + 0, /* 165 = ¥ */ + 0, /* 166 = ¦ */ + 5, 0b00001010, 0b01010101, 0b01010101, 0b01010101, 0b00101000, /* 167 = § */ + 0, /* 168 = ¨ */ + 0, /* 169 = © */ + 0, /* 170 = ª */ + 0, /* 171 = « */ + 0, /* 172 = ¬ */ + 0, /* 173 = ­ */ + 0, /* 174 = ® */ + 0, /* 175 = ¯ */ + 4, 0b00000110, 0b00001001, 0b00001001, 0b00000110, /* 176 = ° */ + 0, /* 177 = ± */ + 0, /* 178 = ² */ + 0, /* 179 = ³ */ + 0, /* 180 = ´ */ + 5, 0b11111100, 0b00100000, 0b01000000, 0b01000000, 0b00111100, /* 181 = µ */ + 0, /* 182 = ¶ */ + 0, /* 183 = · */ + 0, /* 184 = ¸ */ + 0, /* 185 = ¹ */ + 0, /* 186 = º */ + 0, /* 187 = » */ + 0, /* 188 = ¼ */ + 0, /* 189 = ½ */ + 0, /* 190 = ¾ */ + 0, /* 191 = ¿ */ + 0, /* 192 = À */ + 0, /* 193 = Á */ + 0, /* 194 =  */ + 0, /* 195 = à */ + 5, 0b11111000, 0b00010101, 0b00010100, 0b00010101, 0b11111000, /* 196 = Ä */ + 0, /* 197 = Å */ + 0, /* 198 = Æ */ + 0, /* 199 = Ç */ + 0, /* 200 = È */ + 0, /* 201 = É */ + 0, /* 202 = Ê */ + 0, /* 203 = Ë */ + 0, /* 204 = Ì */ + 0, /* 205 = Í */ + 0, /* 206 = Î */ + 0, /* 207 = Ï */ + 0, /* 208 = Ð */ + 0, /* 209 = Ñ */ + 0, /* 210 = Ò */ + 0, /* 211 = Ó */ + 0, /* 212 = Ô */ + 0, /* 213 = Õ */ + 5, 0b01111000, 0b10000101, 0b10000100, 0b10000101, 0b01111000, /* 214 = Ö */ + 0, /* 215 = × */ + 0, /* 216 = Ø */ + 0, /* 217 = Ù */ + 0, /* 218 = Ú */ + 0, /* 219 = Û */ + 5, 0b01111100, 0b10000001, 0b10000000, 0b10000001, 0b11111100, /* 220 = Ü */ + 0, /* 221 = Ý */ + 0, /* 222 = Þ */ + 5, 0b11111110, 0b00000001, 0b01000101, 0b01001010, 0b00110000, /* 223 = ß */ + 0, /* 224 = à */ + 0, /* 225 = á */ + 0, /* 226 = â */ + 0, /* 227 = ã */ + 5, 0b00100000, 0b01010101, 0b01010100, 0b01010101, 0b01111000, /* 228 = ä */ + 0, /* 229 = å */ + 0, /* 230 = æ */ + 0, /* 231 = ç */ + 0, /* 232 = è */ + 0, /* 233 = é */ + 0, /* 234 = ê */ + 0, /* 235 = ë */ + 0, /* 236 = ì */ + 0, /* 237 = í */ + 0, /* 238 = î */ + 0, /* 239 = ï */ + 0, /* 240 = ð */ + 0, /* 241 = ñ */ + 0, /* 242 = ò */ + 0, /* 243 = ó */ + 0, /* 244 = ô */ + 0, /* 245 = õ */ + 5, 0b00111000, 0b01000101, 0b01000100, 0b01000101, 0b00111000, /* 246 = ö */ + 0, /* 247 = ÷ */ + 0, /* 248 = ø */ + 0, /* 249 = ù */ + 0, /* 250 = ú */ + 0, /* 251 = û */ + 5, 0b00111100, 0b01000001, 0b01000000, 0b00100001, 0b01111100, /* 252 = ü */ + 0, /* 253 = ý */ + 0, /* 254 = þ */ + 0, /* 255 = ÿ*/ +}; + +uint16_t* font_index; // will be calculated at start by calculate_font_index() +/**static const uint16_t font_index[95] PROGMEM = { + 0, 3, 6, 10, 16, 22, 28, 34, 36, 40, 44, 48, 54, 57, 63, 66, 72, 78, 82, 88, 94, + 100, 106, 112, 118, 124, 130, 133, 136, 141, 147, 152, 158, 164, 170, 176, + 182, 188, 194, 200, 206, 212, 218, 224, 230, 236, 242, 248, 254, 260, 266, + 272, 278, 284, 290, 296, 302, 308, 314, 320, 324, 330, 334, 340, 346, 348, + 354, 360, 366, 372, 378, 384, 390, 396, 400, 405, 411, 415, 421, 427, 433, + 439, 445, 451, 457, 463, 469, 475, 481, 487, 493, 499, 503, 505, 509 + };**/ + +void calculate_font_index() +{ + uint16 fontsize = sizeof(font); + Serial.println((String)"Font uses " + fontsize + " bytes."); + uint8_t* fontptr = (uint8_t*)font; + int num_chars = 0; + // 1st sweep: count chars + while (fontptr < font + fontsize) { + int char_width = pgm_read_byte(fontptr); //*fontptr; + fontptr += char_width + 1; // add character X size + num_chars++; + } + // allocate index table + uint16_t* index = (uint16_t*)malloc(num_chars * sizeof(uint16_t)); + // 2nd sweep: calculate index values + fontptr = (uint8_t*)font; + for (int i = 0; i < num_chars; i++) { + index[i] = fontptr - font; + int char_width = pgm_read_byte(fontptr); //*fontptr; + //Serial.println((String)"idx " + i + " = " + (char)(i+32) + ": " + char_width + " -> " + index[i]); + fontptr += char_width + 1; // add character X size + } + font_index = index; +} diff --git a/local_config.dist.h b/local_config.dist.h index 5c615a0..bbff462 100644 --- a/local_config.dist.h +++ b/local_config.dist.h @@ -1,3 +1,6 @@ + +// local configuration -- copy this to local_config.h and change to your needs. + const char* ssid = ""; // Name of the SSID to connect to const char* password = ""; // Password used to connect to your wifi @@ -5,6 +8,9 @@ const char* mqtt_server = ""; // H const char* mqtt_username = ""; // Username used to connect to MQTT-Server, leave empty if unused const char* mqtt_password = ""; // Password used to connect to MQTT-Server, leave empty if unused +// you may change the default topic root "ledMatrix" to something different here +//#define TOPICROOT "ledMatrix" + const int LEDMATRIX_SEGMENTS = 4; // Number of 8x8 Segments const uint8_t LEDMATRIX_CS_PIN = D4; // CableSelect pin #define MAX_TEXT_LENGTH 4096 // Maximal text length, to large might use up to much ram From 03116815803df0fbfeb4264a9de2705bf3057dc0 Mon Sep 17 00:00:00 2001 From: Kater_S Date: Mon, 28 Dec 2020 14:35:24 +0100 Subject: [PATCH 02/12] introduce silent mode user may set `do_publishes` constant to false in local configuration file to prevent device from publishing anything to the MQTT broker --- MarQueTT.ino | 8 +++++--- local_config.dist.h | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/MarQueTT.ino b/MarQueTT.ino index 9539c6e..14f3a4a 100644 --- a/MarQueTT.ino +++ b/MarQueTT.ino @@ -33,7 +33,8 @@ void setup() { led.setIntensity(0); led.setEnabled(true); calculate_font_index(); - client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "startup"); + if (do_publishes) + client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "startup"); } void loop() @@ -54,7 +55,8 @@ void loop() loop_matrix(); if (!client.connected()) { reconnect(); - client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "reconnect"); + if (do_publishes) + client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "reconnect"); } client.loop(); } @@ -288,7 +290,7 @@ void nextChar() { textIndex = 0; scrollWhitespace = LEDMATRIX_WIDTH; // start over with empty display - if (scrollDelay) + if (scrollDelay && do_publishes) client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "repeat"); } } diff --git a/local_config.dist.h b/local_config.dist.h index bbff462..e811789 100644 --- a/local_config.dist.h +++ b/local_config.dist.h @@ -8,6 +8,9 @@ const char* mqtt_server = ""; // H const char* mqtt_username = ""; // Username used to connect to MQTT-Server, leave empty if unused const char* mqtt_password = ""; // Password used to connect to MQTT-Server, leave empty if unused +// set to false if you do not want to publish anything: +const bool do_publishes = true; + // you may change the default topic root "ledMatrix" to something different here //#define TOPICROOT "ledMatrix" From 6c7a546da583f619e17634ffd8fdd348de9d116a Mon Sep 17 00:00:00 2001 From: Kater_S Date: Tue, 29 Dec 2020 13:37:33 +0100 Subject: [PATCH 03/12] Multi-Channel Text etc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add support for multi-channel text incl. channel selection; add bullet (•) to font; configurable topic names (via TOPICROOT) --- MarQueTT.ino | 124 +++++++++++++++++++++++++++++++++++++++++++-------- font.h | 4 +- 2 files changed, 107 insertions(+), 21 deletions(-) diff --git a/MarQueTT.ino b/MarQueTT.ino index 14f3a4a..3aff8f1 100644 --- a/MarQueTT.ino +++ b/MarQueTT.ino @@ -8,8 +8,13 @@ #define TOPICROOT "ledMatrix" #endif -uint8_t text[MAX_TEXT_LENGTH]; LEDMatrixDriver led(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN, 0); + +uint8_t texts[NUM_CHANNELS][MAX_TEXT_LENGTH]; +uint8_t textcycle[NUM_CHANNELS]; +uint8_t num_textcycles = 1; +uint8_t current_cycle = 0; +uint16_t current_channel = 0; uint16_t textIndex = 0; uint8_t colIndex = 0; uint16_t scrollWhitespace = 0; @@ -27,9 +32,12 @@ void setup() { client.setServer(mqtt_server, 1883); client.setCallback(callback); client.setBufferSize(MAX_TEXT_LENGTH); - for (int i = 0; i < sizeof(text); i++) { - text[i] = initialText[i]; + for (int c = 0; c < NUM_CHANNELS; c++) { + for (int i = 0; i < sizeof(texts[c]); i++) { + texts[c][i] = initialText[i]; + } } + textcycle[0] = 0; led.setIntensity(0); led.setEnabled(true); calculate_font_index(); @@ -82,7 +90,7 @@ void setup_wifi() { Serial.println(WiFi.macAddress()); byte mac[6]; WiFi.macAddress(mac); - snprintf(devname, sizeof(devname), "MarQueTTino/%02X%02X%02X", mac[3], mac[4], mac[5]); + snprintf(devname, sizeof(devname), "%02X%02X%02X", mac[3], mac[4], mac[5]); Serial.println((String)"This device is called '" + devname + "'."); } @@ -101,9 +109,15 @@ void callback(char* topic, byte* payload, unsigned int length) { for (int i = 0; i < length; i++) Serial.print((char)(payload[i])); Serial.println("]"); - char* command = topic + String(topic).lastIndexOf("/") + 1; + String command = topic + String(topic).lastIndexOf(TOPICROOT "/") + strlen(TOPICROOT) + 1; + + if (command.startsWith(devname)) { + command.remove(0, strlen(devname) + 1); + } + + Serial.println((String)"Command = [" + command + "]"); - if (!strcmp(command, "intensity")) { + if (command.equals("intensity")) { int intensity = 0; for (int i = 0 ; i < length; i++) { intensity *= 10; @@ -114,7 +128,7 @@ void callback(char* topic, byte* payload, unsigned int length) { return; } - if (!strcmp(command, "delay")) { + if (command.equals("delay")) { scrollDelay = 0; for (int i = 0 ; i < length; i++) { scrollDelay *= 10; @@ -132,7 +146,7 @@ void callback(char* topic, byte* payload, unsigned int length) { return; } - if (!strcmp(command, "blink")) { + if (command.equals("blink")) { blinkDelay = 0; for (int i = 0 ; i < length; i++) { blinkDelay *= 10; @@ -154,14 +168,75 @@ void callback(char* topic, byte* payload, unsigned int length) { } - if (!strcmp(command, "enable")) { + if (command.equals("enable")) { led.setEnabled(payload[0] == '1'); } - if (!strcmp(command, "text")) { + if (command.equals("channel")) { + payload[length] = 0; + String msg = (String)(char*)payload; + if (msg.equals("")) { + Serial.println((String)"set channel to default (0)"); + textcycle[0] = 0; + num_textcycles = 1; + current_channel = textcycle[current_cycle]; + } else { + // parse comma-separated list of channels (or a single channel number) + Serial.println((String)"set channel to comma-separated list: " + msg); + num_textcycles = 0; + int pos = 0; + while (1) { + int newpos = msg.substring(pos).indexOf(","); + if (newpos != -1) { + Serial.println((String)"pos=" + pos + " -> search [" + msg.substring(pos) + + "] => newpos=" + (pos + newpos) + + " => add substring " + msg.substring(pos, pos + newpos)); + textcycle[num_textcycles++] = msg.substring(pos, pos + newpos).toInt(); + pos = pos + newpos + 1; + } else { + Serial.println((String)"pos=" + pos + " -> search [" + msg.substring(pos) + + "] => not found" + + " => add substring " + msg.substring(pos)); + textcycle[num_textcycles++] = msg.substring(pos).toInt(); + break; // done! + } + } + Serial.print((String)"Found " + num_textcycles + " cycle indices:"); + for (int i = 0; i < num_textcycles; i++) + Serial.print((String)" " + textcycle[i]); + Serial.println("."); + current_cycle = 0; + current_channel = textcycle[current_cycle]; + current_cycle = (current_cycle + 1) % num_textcycles; + } + // if display is scrolling, text will be changed at the end of the current cycle + if (scrollDelay == 0) { + // otherwise trigger redraw on display + textIndex = 0; + } + return; + } + + + if (command.startsWith("text")) { const bool pr = 1; // set to 1 for debug prints - if (pr) printHex8(payload, length); + if (pr) { + Serial.print("content = ["); + printHex8(payload, length); + Serial.println((String)"] (" + length + " bytes)"); + } + + int target_channel; + if (command.equals("text")) + target_channel = 0; + else + target_channel = command.substring(command.lastIndexOf("/") + 1).toInt(); + if (target_channel < 0) target_channel = 0; + if (target_channel > NUM_CHANNELS - 1) target_channel = NUM_CHANNELS - 1; + Serial.println((String)"Target text channel: " + target_channel); + + uint8_t* text = texts[target_channel]; text[0] = ' '; for (int i = 0 ; i < 4096; i++) { text[i] = 0; @@ -181,7 +256,7 @@ void callback(char* topic, byte* payload, unsigned int length) { if (b == 0xc3) { // UTF-8 1st byte text[j++] = payload[i++] + 64; // map 2nd byte to Latin-1 (simplified) } else if (b == 0xc2) { - if (i < length && payload[i] == 0xA7) { // § = section sign + if (i < length && payload[i] == 0xA7) { // § = section sign text[j++] = 0xA7; } else if (i < length && payload[i] == 0xB0) { // ° = degrees sign text[j++] = 0xB0; @@ -206,6 +281,8 @@ void callback(char* topic, byte* payload, unsigned int length) { text[j++] = 0x96; // – = en dash } else if (payload[i] == 0x80 && payload[i + 1] == 0x94) { text[j++] = 0x97; // — = em dash + } else if (payload[i] == 0x80 && payload[i + 1] == 0xA2) { + text[j++] = 0xB7; // • = bullet } else if (payload[i] == 0x80 && payload[i + 1] == 0xA6) { text[j++] = 0x85; // … = ellipsis } else { @@ -245,8 +322,11 @@ void callback(char* topic, byte* payload, unsigned int length) { text[i] = 0x7f; } } - if (pr) Serial.print((String)"=> Text (" + j + " bytes): "); - if (pr) printHex8(text, j + 1); + if (pr) { + Serial.print((String)"=> Text[channel " + target_channel + "] (" + j + " bytes): "); + printHex8(text, j + 1); + Serial.println(); + } textIndex = 0; colIndex = 0; @@ -271,8 +351,12 @@ void reconnect() { client.subscribe(((String)TOPICROOT "/" + devname + "/delay").c_str()); client.subscribe(TOPICROOT "/text"); client.subscribe(((String)TOPICROOT "/" + devname + "/text").c_str()); + client.subscribe(TOPICROOT "/text/#"); + client.subscribe(((String)TOPICROOT "/" + devname + "/text/#").c_str()); client.subscribe(TOPICROOT "/blink"); client.subscribe(((String)TOPICROOT "/" + devname + "/blink").c_str()); + client.subscribe(TOPICROOT "/channel"); + client.subscribe(((String)TOPICROOT "/" + devname + "/channel").c_str()); } else { Serial.print("failed, rc="); Serial.print(client.state()); @@ -286,12 +370,14 @@ const uint16_t LEDMATRIX_WIDTH = LEDMATRIX_SEGMENTS * 8; void nextChar() { - if (text[++textIndex] == '\0') + if (texts[current_channel][++textIndex] == '\0') { textIndex = 0; scrollWhitespace = LEDMATRIX_WIDTH; // start over with empty display if (scrollDelay && do_publishes) client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "repeat"); + current_channel = textcycle[current_cycle]; + current_cycle = (current_cycle + 1) % num_textcycles; } } @@ -312,7 +398,7 @@ void writeCol() scrollWhitespace--; return; } - uint8_t asc = text[textIndex] - 32; + uint8_t asc = texts[current_channel][textIndex] - 32; uint16_t idx = pgm_read_word(&(font_index[asc])); uint8_t w = pgm_read_byte(&(font[idx])); uint8_t col = pgm_read_byte(&(font[colIndex + idx + 1])); @@ -338,7 +424,7 @@ void loop_matrix() marquee(); } else { if (textIndex == 0) { // start writing text to display (e.g. after text was changed) - Serial.println("display static text"); + Serial.println((String)"Display static text no." + current_channel); colIndex = 0; marqueeDelayTimestamp = 0; scrollWhitespace = 0; @@ -346,7 +432,7 @@ void loop_matrix() uint8_t displayColumn = 0; // start left while (displayColumn < LEDMATRIX_WIDTH) { // write one column - uint8_t asc = text[textIndex] - 32; + uint8_t asc = texts[current_channel][textIndex] - 32; uint16_t idx = pgm_read_word(&(font_index[asc])); uint8_t w = pgm_read_byte(&(font[idx])); uint8_t col = pgm_read_byte(&(font[colIndex + idx + 1])); @@ -356,7 +442,7 @@ void loop_matrix() if (++colIndex == w) { displayColumn += 1; colIndex = 0; - if (text[++textIndex] == '\0') { + if (texts[current_channel][++textIndex] == '\0') { return; // done textIndex = 0; } diff --git a/font.h b/font.h index e598a18..21ff061 100644 --- a/font.h +++ b/font.h @@ -153,7 +153,7 @@ static const uint8_t font[] PROGMEM = { 0, /* 180 = ´ */ 5, 0b11111100, 0b00100000, 0b01000000, 0b01000000, 0b00111100, /* 181 = µ */ 0, /* 182 = ¶ */ - 0, /* 183 = · */ + 5, 0b00001000, 0b00011100, 0b00111110, 0b00011100, 0b00001000, /* 183 = · / • */ 0, /* 184 = ¸ */ 0, /* 185 = ¹ */ 0, /* 186 = º */ @@ -225,7 +225,7 @@ static const uint8_t font[] PROGMEM = { 5, 0b00111100, 0b01000001, 0b01000000, 0b00100001, 0b01111100, /* 252 = ü */ 0, /* 253 = ý */ 0, /* 254 = þ */ - 0, /* 255 = ÿ*/ + 0, /* 255 = ÿ */ }; uint16_t* font_index; // will be calculated at start by calculate_font_index() From ff50e9c34922d349f474bdb832bcb4708045c940 Mon Sep 17 00:00:00 2001 From: Kater_S Date: Tue, 29 Dec 2020 18:12:31 +0100 Subject: [PATCH 04/12] Fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit added local_config.dist.h which was not updated – sorry. --- local_config.dist.h | 1 + 1 file changed, 1 insertion(+) diff --git a/local_config.dist.h b/local_config.dist.h index e811789..06372df 100644 --- a/local_config.dist.h +++ b/local_config.dist.h @@ -17,5 +17,6 @@ const bool do_publishes = true; const int LEDMATRIX_SEGMENTS = 4; // Number of 8x8 Segments const uint8_t LEDMATRIX_CS_PIN = D4; // CableSelect pin #define MAX_TEXT_LENGTH 4096 // Maximal text length, to large might use up to much ram +#define NUM_CHANNELS 10 // number of text channels const char* initialText = "Ready, waiting for text via MQTT"; // Initial Text shown before the first MQTT-Message is recieved, don't leave empty, to shwo no text on startup set to " " uint16_t scrollDelay = 25; // Initial Scroll deplay From 9d798fe4f9ad792a29075cc180032f7a3b1ec311 Mon Sep 17 00:00:00 2001 From: Kater_S Date: Sat, 2 Jan 2021 19:21:39 +0100 Subject: [PATCH 05/12] Bugfix, OTA fixed text buffer problem; OTA updates; logging via telnet (port 22); --- MarQueTT.ino | 243 ++++++++++++++++++++++++++++++-------------- font.h | 4 +- local_config.dist.h | 15 ++- 3 files changed, 180 insertions(+), 82 deletions(-) diff --git a/MarQueTT.ino b/MarQueTT.ino index 3aff8f1..fe4aa9f 100644 --- a/MarQueTT.ino +++ b/MarQueTT.ino @@ -1,17 +1,33 @@ #include #include +#include #include -#include "font.h" #include "local_config.h" #ifndef TOPICROOT #define TOPICROOT "ledMatrix" #endif +#ifndef LOG_TELNET +#define LOG_TELNET 0 +#endif + +#if LOG_TELNET +#include +#define LogTarget TelnetStream +#else +#define LogTarget Serial +#endif + +#include "font.h" + +// variables + LEDMatrixDriver led(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN, 0); +uint8_t scrollbuffer[MAX_TEXT_LENGTH]; uint8_t texts[NUM_CHANNELS][MAX_TEXT_LENGTH]; -uint8_t textcycle[NUM_CHANNELS]; +uint8_t textcycle[MAX_TEXTCYCLE]; uint8_t num_textcycles = 1; uint8_t current_cycle = 0; uint16_t current_channel = 0; @@ -21,11 +37,17 @@ uint16_t scrollWhitespace = 0; uint64_t marqueeDelayTimestamp = 0; uint64_t marqueeBlinkTimestamp; uint16_t blinkDelay = 0; -char devname[20]; +char devaddr[20]; +char devname[40]; WiFiClient espClient; PubSubClient client(espClient); +// forward declarations + +void getScrolltextFromBuffer(int channel); + + void setup() { Serial.begin(115200); setup_wifi(); @@ -33,10 +55,9 @@ void setup() { client.setCallback(callback); client.setBufferSize(MAX_TEXT_LENGTH); for (int c = 0; c < NUM_CHANNELS; c++) { - for (int i = 0; i < sizeof(texts[c]); i++) { - texts[c][i] = initialText[i]; - } + snprintf((char*)(texts[c]), MAX_TEXT_LENGTH, "[%d] %s", c, initialText); } + getScrolltextFromBuffer(0); textcycle[0] = 0; led.setIntensity(0); led.setEnabled(true); @@ -45,8 +66,77 @@ void setup() { client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "startup"); } + +void setup_wifi() { + delay(10); + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + randomSeed(micros()); + Serial.println(""); + Serial.print("WiFi connected"); + Serial.print(" - IP address: "); + Serial.print(WiFi.localIP()); + Serial.print(" - MAC address: "); + Serial.println(WiFi.macAddress()); + byte mac[6]; + WiFi.macAddress(mac); + snprintf(devname, sizeof(devaddr), "%02X%02X%02X", mac[3], mac[4], mac[5]); + snprintf(devname, sizeof(devname), "MarQueTTino-%02X%02X%02X", mac[3], mac[4], mac[5]); + Serial.println((String)"This device is called '" + devname + "'."); + snprintf((char*)(texts[0]), sizeof(texts[0]), "MarQueTTino %02X%02X%02X", mac[3], mac[4], mac[5]); + + WiFi.hostname(devname); + ArduinoOTA.setHostname(devname); + + ArduinoOTA.onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) { + type = "sketch"; + } else { // U_FS + type = "filesystem"; + } + + // NOTE: if updating FS this would be the place to unmount FS using FS.end() + LogTarget.println("Start updating " + type); + }); + ArduinoOTA.onEnd([]() { + LogTarget.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + LogTarget.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + LogTarget.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) { + LogTarget.println("Auth Failed"); + } else if (error == OTA_BEGIN_ERROR) { + LogTarget.println("Begin Failed"); + } else if (error == OTA_CONNECT_ERROR) { + LogTarget.println("Connect Failed"); + } else if (error == OTA_RECEIVE_ERROR) { + LogTarget.println("Receive Failed"); + } else if (error == OTA_END_ERROR) { + LogTarget.println("End Failed"); + } + }); + ArduinoOTA.begin(); +#if LOG_TELNET + TelnetStream.begin(); + Serial.println((String)"All further logging is routed to telnet. Just connect to " + devname + " port 22."); +#endif +} + + void loop() { + ArduinoOTA.handle(); if (blinkDelay) { if (marqueeBlinkTimestamp > millis()) { marqueeBlinkTimestamp > millis(); @@ -70,44 +160,20 @@ void loop() } -void setup_wifi() { - delay(10); - Serial.println(); - Serial.print("Connecting to "); - Serial.println(ssid); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - randomSeed(micros()); - Serial.println(""); - Serial.print("WiFi connected"); - Serial.print(" - IP address: "); - Serial.print(WiFi.localIP()); - Serial.print(" - MAC address: "); - Serial.println(WiFi.macAddress()); - byte mac[6]; - WiFi.macAddress(mac); - snprintf(devname, sizeof(devname), "%02X%02X%02X", mac[3], mac[4], mac[5]); - Serial.println((String)"This device is called '" + devname + "'."); -} - void printHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with leading zeroes { char tmp[16]; for (int i = 0; i < length; i++) { sprintf(tmp, "0x%.2X", data[i]); - Serial.print(tmp); Serial.print(" "); + LogTarget.print(tmp); LogTarget.print(" "); } } void callback(char* topic, byte* payload, unsigned int length) { - Serial.print((String)"MQTT in: " + topic + "\t = ["); - for (int i = 0; i < length; i++) Serial.print((char)(payload[i])); - Serial.println("]"); + LogTarget.print((String)"MQTT in: " + topic + "\t = ["); + for (int i = 0; i < length; i++) LogTarget.print((char)(payload[i])); + LogTarget.println("]"); String command = topic + String(topic).lastIndexOf(TOPICROOT "/") + strlen(TOPICROOT) + 1; @@ -115,7 +181,7 @@ void callback(char* topic, byte* payload, unsigned int length) { command.remove(0, strlen(devname) + 1); } - Serial.println((String)"Command = [" + command + "]"); + LogTarget.println((String)"Command = [" + command + "]"); if (command.equals("intensity")) { int intensity = 0; @@ -177,35 +243,36 @@ void callback(char* topic, byte* payload, unsigned int length) { payload[length] = 0; String msg = (String)(char*)payload; if (msg.equals("")) { - Serial.println((String)"set channel to default (0)"); + LogTarget.println((String)"set channel to default (0)"); textcycle[0] = 0; num_textcycles = 1; current_channel = textcycle[current_cycle]; } else { // parse comma-separated list of channels (or a single channel number) - Serial.println((String)"set channel to comma-separated list: " + msg); + LogTarget.println((String)"set channel to comma-separated list: " + msg); num_textcycles = 0; int pos = 0; while (1) { int newpos = msg.substring(pos).indexOf(","); if (newpos != -1) { - Serial.println((String)"pos=" + pos + " -> search [" + msg.substring(pos) - + "] => newpos=" + (pos + newpos) - + " => add substring " + msg.substring(pos, pos + newpos)); + LogTarget.println((String)"pos=" + pos + " -> search [" + msg.substring(pos) + + "] => newpos=" + (pos + newpos) + + " => add substring " + msg.substring(pos, pos + newpos)); textcycle[num_textcycles++] = msg.substring(pos, pos + newpos).toInt(); + if (num_textcycles == MAX_TEXTCYCLE) break; // no more room left in array pos = pos + newpos + 1; } else { - Serial.println((String)"pos=" + pos + " -> search [" + msg.substring(pos) - + "] => not found" - + " => add substring " + msg.substring(pos)); + LogTarget.println((String)"pos=" + pos + " -> search [" + msg.substring(pos) + + "] => not found" + + " => add substring " + msg.substring(pos)); textcycle[num_textcycles++] = msg.substring(pos).toInt(); break; // done! } } - Serial.print((String)"Found " + num_textcycles + " cycle indices:"); + LogTarget.print((String)"Found " + num_textcycles + " cycle indices:"); for (int i = 0; i < num_textcycles; i++) - Serial.print((String)" " + textcycle[i]); - Serial.println("."); + LogTarget.print((String)" " + textcycle[i]); + LogTarget.println("."); current_cycle = 0; current_channel = textcycle[current_cycle]; current_cycle = (current_cycle + 1) % num_textcycles; @@ -220,11 +287,11 @@ void callback(char* topic, byte* payload, unsigned int length) { if (command.startsWith("text")) { - const bool pr = 1; // set to 1 for debug prints + const bool pr = 0; // set to 1 for debug prints if (pr) { - Serial.print("content = ["); + LogTarget.print("content = ["); printHex8(payload, length); - Serial.println((String)"] (" + length + " bytes)"); + LogTarget.println((String)"] (" + length + " bytes)"); } int target_channel; @@ -234,7 +301,7 @@ void callback(char* topic, byte* payload, unsigned int length) { target_channel = command.substring(command.lastIndexOf("/") + 1).toInt(); if (target_channel < 0) target_channel = 0; if (target_channel > NUM_CHANNELS - 1) target_channel = NUM_CHANNELS - 1; - Serial.println((String)"Target text channel: " + target_channel); + LogTarget.println((String)"Target text channel: " + target_channel); uint8_t* text = texts[target_channel]; text[0] = ' '; @@ -246,13 +313,13 @@ void callback(char* topic, byte* payload, unsigned int length) { while (i < length) { uint8_t b = payload[i++]; sprintf(tmp, "0x%.2X = '%c' -> ", b, b); - if (pr) Serial.print(tmp); + if (pr) LogTarget.print(tmp); if ((b & 0b10000000) == 0) { // 7-bit ASCII - if (pr) Serial.println("ASCII"); + if (pr) LogTarget.println("ASCII"); text[j++] = b; } else if ((b & 0b11100000) == 0b11000000) { // UTF-8 2-byte sequence: starts with 0b110xxxxx - if (pr) Serial.println("UTF-8 (2)"); + if (pr) LogTarget.println("UTF-8 (2)"); if (b == 0xc3) { // UTF-8 1st byte text[j++] = payload[i++] + 64; // map 2nd byte to Latin-1 (simplified) } else if (b == 0xc2) { @@ -273,7 +340,7 @@ void callback(char* topic, byte* payload, unsigned int length) { i += 1; } } else if ((b & 0b111100000) == 0b11100000) { // UTF-8 3-byte sequence: starts with 0b1110xxxx - if (pr) Serial.println("UTF-8 (3)"); + if (pr) LogTarget.println("UTF-8 (3)"); if (i + 1 < length && b == 0xE2) { if (payload[i] == 0x82 && payload[i + 1] == 0xAC) { text[j++] = 0x80; // € = euro sign @@ -297,20 +364,20 @@ void callback(char* topic, byte* payload, unsigned int length) { text[j++] = 0x7f; // add checkerboard pattern } } else if ((b & 0b111110000) == 0b11110000) { // UTF-8 4-byte sequence_ starts with 0b11110xxx - if (pr) Serial.println("UTF-8 (4)"); + if (pr) LogTarget.println("UTF-8 (4)"); // unknown, skip remaining 3 bytes i += 3; text[j++] = 0x7f; // add checkerboard pattern text[j++] = 0x7f; // add checkerboard pattern text[j++] = 0x7f; // add checkerboard pattern } else { // unsupported (illegal) UTF-8 sequence - if (pr) Serial.print("UTF-8 (n) "); + if (pr) LogTarget.print("UTF-8 (n) "); while ((payload[i] & 0b10000000) && i < length) { - if (pr) Serial.print("+"); + if (pr) LogTarget.print("+"); i++; // skip non-ASCII characters text[j++] = 0x7f; // add checkerboard pattern } - if (pr) Serial.println(); + if (pr) LogTarget.println(); } } for (int i = 0; i < j; i++) { @@ -323,26 +390,28 @@ void callback(char* topic, byte* payload, unsigned int length) { } } if (pr) { - Serial.print((String)"=> Text[channel " + target_channel + "] (" + j + " bytes): "); + LogTarget.print((String)"=> Text[channel " + target_channel + "] (" + j + " bytes): "); printHex8(text, j + 1); - Serial.println(); + LogTarget.println(); } - textIndex = 0; - colIndex = 0; - marqueeDelayTimestamp = 0; - scrollWhitespace = 0; - led.clear(); + /** do NOT start new text + textIndex = 0; + colIndex = 0; + marqueeDelayTimestamp = 0; + scrollWhitespace = 0; + led.clear(); + **/ } } void reconnect() { while (!client.connected()) { - Serial.print("Attempting MQTT connection..."); + LogTarget.print("Attempting MQTT connection..."); String clientId = "ESP8266Client-"; clientId += String(random(0xffff), HEX); if (client.connect(clientId.c_str(), mqtt_username, mqtt_password, TOPICROOT "/status", 1, true, "Offline")) { - Serial.println("connected"); + LogTarget.println("connected"); client.subscribe(TOPICROOT "/enable"); client.subscribe(((String)TOPICROOT "/" + devname + "/enable").c_str()); client.subscribe(TOPICROOT "/intensity"); @@ -358,9 +427,9 @@ void reconnect() { client.subscribe(TOPICROOT "/channel"); client.subscribe(((String)TOPICROOT "/" + devname + "/channel").c_str()); } else { - Serial.print("failed, rc="); - Serial.print(client.state()); - Serial.println(" try again in 5 seconds"); + LogTarget.print("failed, rc="); + LogTarget.print(client.state()); + LogTarget.println(" try again in 5 seconds"); delay(5000); } } @@ -370,7 +439,7 @@ const uint16_t LEDMATRIX_WIDTH = LEDMATRIX_SEGMENTS * 8; void nextChar() { - if (texts[current_channel][++textIndex] == '\0') + if (scrollbuffer[++textIndex] == '\0') { textIndex = 0; scrollWhitespace = LEDMATRIX_WIDTH; // start over with empty display @@ -378,6 +447,7 @@ void nextChar() client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "repeat"); current_channel = textcycle[current_cycle]; current_cycle = (current_cycle + 1) % num_textcycles; + getScrolltextFromBuffer(current_channel); } } @@ -398,7 +468,7 @@ void writeCol() scrollWhitespace--; return; } - uint8_t asc = texts[current_channel][textIndex] - 32; + uint8_t asc = scrollbuffer[textIndex] - 32; uint16_t idx = pgm_read_word(&(font_index[asc])); uint8_t w = pgm_read_byte(&(font[idx])); uint8_t col = pgm_read_byte(&(font[colIndex + idx + 1])); @@ -418,13 +488,34 @@ void marquee() led.display(); } +void getScrolltextFromBuffer(int channel) +{ + LogTarget.print((String)"Show buffer " + channel + ": ["); + bool eot = false; + for (int i = 0; i < MAX_TEXT_LENGTH; i++) { + if (eot) { + scrollbuffer[i] = 0; + } else { + uint8_t ch = texts[channel][i]; + scrollbuffer[i] = ch; + LogTarget.print(String(char(ch))); + if (!ch) { + eot = true; + LogTarget.println((String)"] (" + i + " bytes)"); + } + } + } + scrollbuffer[MAX_TEXT_LENGTH - 1] = 0; +} + void loop_matrix() { if (scrollDelay) { marquee(); } else { - if (textIndex == 0) { // start writing text to display (e.g. after text was changed) - Serial.println((String)"Display static text no." + current_channel); + if (textIndex == 0) { // begin writing text to display (e.g. after text was changed) + LogTarget.println((String)"Display static text no." + current_channel); + getScrolltextFromBuffer(current_channel); colIndex = 0; marqueeDelayTimestamp = 0; scrollWhitespace = 0; @@ -432,7 +523,7 @@ void loop_matrix() uint8_t displayColumn = 0; // start left while (displayColumn < LEDMATRIX_WIDTH) { // write one column - uint8_t asc = texts[current_channel][textIndex] - 32; + uint8_t asc = scrollbuffer[textIndex] - 32; uint16_t idx = pgm_read_word(&(font_index[asc])); uint8_t w = pgm_read_byte(&(font[idx])); uint8_t col = pgm_read_byte(&(font[colIndex + idx + 1])); @@ -442,7 +533,7 @@ void loop_matrix() if (++colIndex == w) { displayColumn += 1; colIndex = 0; - if (texts[current_channel][++textIndex] == '\0') { + if (scrollbuffer[++textIndex] == '\0') { return; // done textIndex = 0; } diff --git a/font.h b/font.h index 21ff061..5d944ba 100644 --- a/font.h +++ b/font.h @@ -241,7 +241,7 @@ uint16_t* font_index; // will be calculated at start by calculate_font_index() void calculate_font_index() { uint16 fontsize = sizeof(font); - Serial.println((String)"Font uses " + fontsize + " bytes."); + LogTarget.println((String)"Font uses " + fontsize + " bytes."); uint8_t* fontptr = (uint8_t*)font; int num_chars = 0; // 1st sweep: count chars @@ -257,7 +257,7 @@ void calculate_font_index() for (int i = 0; i < num_chars; i++) { index[i] = fontptr - font; int char_width = pgm_read_byte(fontptr); //*fontptr; - //Serial.println((String)"idx " + i + " = " + (char)(i+32) + ": " + char_width + " -> " + index[i]); + //LogTarget.println((String)"idx " + i + " = " + (char)(i+32) + ": " + char_width + " -> " + index[i]); fontptr += char_width + 1; // add character X size } font_index = index; diff --git a/local_config.dist.h b/local_config.dist.h index 06372df..fd5a182 100644 --- a/local_config.dist.h +++ b/local_config.dist.h @@ -14,9 +14,16 @@ const bool do_publishes = true; // you may change the default topic root "ledMatrix" to something different here //#define TOPICROOT "ledMatrix" +// you may set LOG_TELNET here to 1 if you want logging via telnet (Port 23): +//#define LOG_TELNET 1 + const int LEDMATRIX_SEGMENTS = 4; // Number of 8x8 Segments const uint8_t LEDMATRIX_CS_PIN = D4; // CableSelect pin -#define MAX_TEXT_LENGTH 4096 // Maximal text length, to large might use up to much ram -#define NUM_CHANNELS 10 // number of text channels -const char* initialText = "Ready, waiting for text via MQTT"; // Initial Text shown before the first MQTT-Message is recieved, don't leave empty, to shwo no text on startup set to " " -uint16_t scrollDelay = 25; // Initial Scroll deplay + +#define MAX_TEXT_LENGTH 3500 // Maximal text length, to large might use up to much ram +#define NUM_CHANNELS 10 // Number of text channels +uint16_t scrollDelay = 25; // Initial scroll delay +const char* initialText = "no such text"; // Initial Text shown before the first MQTT-Message is received + // Don't leave empty -- to show no text on startup set to " " + // Use only 7-Bit ASCII characters! +#define MAX_TEXTCYCLE 32 // Maximum number of texts in one cycle From 2a07c30481d447c0cf3b52120b02d5855d780a23 Mon Sep 17 00:00:00 2001 From: Kater_S Date: Sat, 2 Jan 2021 19:46:35 +0100 Subject: [PATCH 06/12] Start message Show name and device number on startup --- MarQueTT.ino | 19 ++++++++++++------- local_config.dist.h | 1 + 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/MarQueTT.ino b/MarQueTT.ino index fe4aa9f..e0e80e0 100644 --- a/MarQueTT.ino +++ b/MarQueTT.ino @@ -30,6 +30,7 @@ uint8_t texts[NUM_CHANNELS][MAX_TEXT_LENGTH]; uint8_t textcycle[MAX_TEXTCYCLE]; uint8_t num_textcycles = 1; uint8_t current_cycle = 0; +uint8_t start_countdown = START_CYCLES; uint16_t current_channel = 0; uint16_t textIndex = 0; uint8_t colIndex = 0; @@ -55,9 +56,9 @@ void setup() { client.setCallback(callback); client.setBufferSize(MAX_TEXT_LENGTH); for (int c = 0; c < NUM_CHANNELS; c++) { - snprintf((char*)(texts[c]), MAX_TEXT_LENGTH, "[%d] %s", c, initialText); + //snprintf((char*)(texts[c]), MAX_TEXT_LENGTH, "[%d] %s", c, initialText); + snprintf((char*)(texts[c]), sizeof(texts[c]), "MarQueTTino %s", devaddr); } - getScrolltextFromBuffer(0); textcycle[0] = 0; led.setIntensity(0); led.setEnabled(true); @@ -87,10 +88,9 @@ void setup_wifi() { Serial.println(WiFi.macAddress()); byte mac[6]; WiFi.macAddress(mac); - snprintf(devname, sizeof(devaddr), "%02X%02X%02X", mac[3], mac[4], mac[5]); + snprintf(devaddr, sizeof(devaddr), "%02X%02X%02X", mac[3], mac[4], mac[5]); snprintf(devname, sizeof(devname), "MarQueTTino-%02X%02X%02X", mac[3], mac[4], mac[5]); Serial.println((String)"This device is called '" + devname + "'."); - snprintf((char*)(texts[0]), sizeof(texts[0]), "MarQueTTino %02X%02X%02X", mac[3], mac[4], mac[5]); WiFi.hostname(devname); ArduinoOTA.setHostname(devname); @@ -445,9 +445,14 @@ void nextChar() scrollWhitespace = LEDMATRIX_WIDTH; // start over with empty display if (scrollDelay && do_publishes) client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "repeat"); - current_channel = textcycle[current_cycle]; - current_cycle = (current_cycle + 1) % num_textcycles; - getScrolltextFromBuffer(current_channel); + + if (start_countdown) { + start_countdown--; + } else { + current_channel = textcycle[current_cycle]; + current_cycle = (current_cycle + 1) % num_textcycles; + getScrolltextFromBuffer(current_channel); + } } } diff --git a/local_config.dist.h b/local_config.dist.h index fd5a182..58ab75a 100644 --- a/local_config.dist.h +++ b/local_config.dist.h @@ -27,3 +27,4 @@ const char* initialText = "no such text"; // Initial Text shown before t // Don't leave empty -- to show no text on startup set to " " // Use only 7-Bit ASCII characters! #define MAX_TEXTCYCLE 32 // Maximum number of texts in one cycle +#define START_CYCLES 3 // Number of cycles for the start message From 8d535b7027da8bbe49fec92d74e87c2989eda11b Mon Sep 17 00:00:00 2001 From: Kater_S Date: Mon, 4 Jan 2021 15:38:34 +0100 Subject: [PATCH 07/12] Version number show / log / publish version number at startup --- MarQueTT.ino | 43 +++++++++++++++++++++++++++++++++++++++++-- Readme.md | 29 +++++++++++++++++------------ 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/MarQueTT.ino b/MarQueTT.ino index e0e80e0..1eadc06 100644 --- a/MarQueTT.ino +++ b/MarQueTT.ino @@ -1,9 +1,33 @@ +/* + * MarQueTT[ino] Matrix Display based on n x 8x8 monochrome LEDs, driven by MAX7221 + * + * a 2020/2021 c3RE project + * + */ + +#define VERSION "1.3.3" + +/* Version history: + * 1.3.3 show + publish version number + * 1.3.2 show device address (lower 3 bytes of MAC address) + * 1.3.1 OTA, TelnetStream logging + * 1.3.0 multiple text channels, cycling + * 1.2.2 silent (no-publish) mode + * 1.2.1 device-specific topics, + * 1.2.0 no-scroll mode + * 1.1.1 publish will (offline status), font editor (offline HTML) + * 1.1.0 larger MQTT message buffer, publish status, UTF-8 special characters + * 1.0.1 blinking + * 1.0.0 initial version + */ + #include #include #include #include #include "local_config.h" + #ifndef TOPICROOT #define TOPICROOT "ledMatrix" #endif @@ -21,6 +45,7 @@ #include "font.h" + // variables LEDMatrixDriver led(LEDMATRIX_SEGMENTS, LEDMATRIX_CS_PIN, 0); @@ -44,27 +69,36 @@ char devname[40]; WiFiClient espClient; PubSubClient client(espClient); + // forward declarations void getScrolltextFromBuffer(int channel); +// functions + void setup() { Serial.begin(115200); + Serial.println(); + Serial.println(); + Serial.println("MarQueTT[ino] Version " VERSION); + Serial.println(); setup_wifi(); client.setServer(mqtt_server, 1883); client.setCallback(callback); client.setBufferSize(MAX_TEXT_LENGTH); for (int c = 0; c < NUM_CHANNELS; c++) { //snprintf((char*)(texts[c]), MAX_TEXT_LENGTH, "[%d] %s", c, initialText); - snprintf((char*)(texts[c]), sizeof(texts[c]), "MarQueTTino %s", devaddr); + snprintf((char*)(texts[c]), sizeof(texts[c]), "MarQueTTino %s Version %s", devaddr, VERSION); } textcycle[0] = 0; led.setIntensity(0); led.setEnabled(true); calculate_font_index(); - if (do_publishes) + if (do_publishes) { client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), "startup"); + client.publish((((String)TOPICROOT "/" + devname + "/status").c_str()), ((String)"version " + VERSION).c_str()); + } } @@ -169,6 +203,7 @@ void printHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with l } } + void callback(char* topic, byte* payload, unsigned int length) { LogTarget.print((String)"MQTT in: " + topic + "\t = ["); @@ -405,6 +440,7 @@ void callback(char* topic, byte* payload, unsigned int length) { } } + void reconnect() { while (!client.connected()) { LogTarget.print("Attempting MQTT connection..."); @@ -435,6 +471,7 @@ void reconnect() { } } + const uint16_t LEDMATRIX_WIDTH = LEDMATRIX_SEGMENTS * 8; void nextChar() @@ -493,6 +530,7 @@ void marquee() led.display(); } + void getScrolltextFromBuffer(int channel) { LogTarget.print((String)"Show buffer " + channel + ": ["); @@ -513,6 +551,7 @@ void getScrolltextFromBuffer(int channel) scrollbuffer[MAX_TEXT_LENGTH - 1] = 0; } + void loop_matrix() { if (scrollDelay) { diff --git a/Readme.md b/Readme.md index 6294d44..d0709b3 100644 --- a/Readme.md +++ b/Readme.md @@ -22,21 +22,22 @@ Same as global topics, but `ledMatrix//...` instead of `ledMatrix/...` ( TBD: priority of individual topics over global topics ? -Extension: text channels, `` = 0..9 +Extension: text channels, `` = `0`..`9` -- `ledMatrix/text/` text for a specific channel (`.../text/0` = same as `.../text`) -- `ledMatrix/channel` set channels to be displayed - - `[,…]` list of channels - - `` only one channel - - "" (empty) only channel 0 +- `ledMatrix/text/` — text for a specific channel (`.../text/0` = same as `.../text`) +- `ledMatrix/channel` — set channels to be displayed + - `[,...]` — list of channels + - `` — only one channel + - "" (empty) — only channel 0 ##### Published by Device -- ledMatrix/aabbcc/status: - - `startup` — sent on system start (after first MQTT connect) - - `reconnect` — sent on MQTT reconnect - - `repeat` — sent at end of a sequence (TBD: for each channel or total sequence?) - - `offline` — sent as will when the MQTT connection disconnects unexpectedly +- `ledMatrix//status`: + - `startup` — sent on system start (after first MQTT connect) + - `version x.y.z` — sent on system start (after first MQTT connect) + - `reconnect` — sent on MQTT reconnect + - `repeat` — sent at end of a sequence (TBD: for each channel or total sequence?) + - `offline` — sent as will when the MQTT connection disconnects unexpectedly ### Wiring @@ -48,9 +49,13 @@ Connect your display's X pin to your controller's Y pin: See https://github.com/bartoszbielawski/LEDMatrixDriver#pin-selection for more information +### Change Ideas + +- Use `ledMatrix/all` instead of `ledMatrix` as a prefix for global topics so `ledMatrix/all/#` matches all global topics + ### Extension Ideas - WS2812 LED (one or more?) for quick signalling - acoustic output via piezo element for signalling -- some push buttons for remote feedback or for local display control +- some push buttons for remote feedback or for local display control (cycle next, acknowledge, etc.) From 56c77e82ab31a88de2aa58c542846d8584a74fe4 Mon Sep 17 00:00:00 2001 From: Kater_S Date: Mon, 4 Jan 2021 19:59:21 +0100 Subject: [PATCH 08/12] Node-RED example added Node-RED example flow --- Node-RED-flow.json | 2565 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2565 insertions(+) create mode 100644 Node-RED-flow.json diff --git a/Node-RED-flow.json b/Node-RED-flow.json new file mode 100644 index 0000000..39836df --- /dev/null +++ b/Node-RED-flow.json @@ -0,0 +1,2565 @@ +[ + { + "id": "cebedba3.f3ea28", + "type": "tab", + "label": "Marquee", + "disabled": false, + "info": "" + }, + { + "id": "dd2463b.19fa72", + "type": "mqtt out", + "z": "cebedba3.f3ea28", + "name": "→ Home", + "topic": "", + "qos": "", + "retain": "", + "broker": "4a8f9596.4cdd74", + "x": 560, + "y": 1160, + "wires": [] + }, + { + "id": "f4868eed.e7efa", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "assemble news", + "func": "\nvar msgs = [];\ntext = `+++[${msg.topic}]+++ `.concat(msg.payload.feed.title[0].replace(\"Nachrichten\",\"NRW\").replace(\"- WDR.de\",\"\")).concat(\"+++ \");\nnum = 0;\nfor (const i in msg.payload.feed.entry) {\n if (num >= 8) break;\n title = msg.payload.feed.entry[i].title[0];\n if (title.includes(\" ich \") || title.includes(\"FAQ\"))\n continue;\n if (title.startsWith(\"Lokalzeit \") || title.includes(\"WDR aktuell\") || title.includes(\" ich \"))\n break; // no more news\n if (title.includes(\"Fussball\") || title.includes(\"Fußball\") || title.includes(\"Bundesliga\") || title.includes(\"Regionalliga\") || title.includes(\"Spieltag\") || title.includes(\"Sport\"))\n break; // no more news\n text = text.concat(title).concat(\" +++ \")\n num++;\n// msgs.push({\n// \"topic\": \"ledMatrix/text\", \n// \"payload\": title\n// });\n}\ntext = text.replace(\" \", \" \");\nmsgs.push({\n \"topic\": `ledMatrix/text/${msg.topic}`,\n \"payload\": text\n });\nflow.set(\"headlines\", msgs);\n\nreturn msgs;\n", + "outputs": 1, + "noerr": 0, + "x": 1060, + "y": 540, + "wires": [ + [ + "4284b11f.f2812" + ] + ] + }, + { + "id": "f2621320.750528", + "type": "xml", + "z": "cebedba3.f3ea28", + "name": "", + "property": "payload", + "attr": "", + "chr": "", + "x": 910, + "y": 540, + "wires": [ + [ + "f4868eed.e7efa" + ] + ] + }, + { + "id": "e8e5e3bb.c28ae", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "feed Marquee with information", + "info": "", + "x": 150, + "y": 40, + "wires": [] + }, + { + "id": "e29aa28f.5dcfc", + "type": "http request", + "z": "cebedba3.f3ea28", + "name": "Ruhrgebiet", + "method": "GET", + "ret": "txt", + "paytoqs": false, + "url": "https://www1.wdr.de/nachrichten/ruhrgebiet/uebersicht-ruhrgebiet-100.feed", + "tls": "", + "persist": false, + "proxy": "", + "authType": "", + "x": 670, + "y": 580, + "wires": [ + [ + "f2621320.750528" + ] + ] + }, + { + "id": "aeec710b.c2d3a", + "type": "http request", + "z": "cebedba3.f3ea28", + "name": "NRW", + "method": "GET", + "ret": "txt", + "paytoqs": false, + "url": "https://www1.wdr.de/wissen/uebersicht-nachrichten-100.feed", + "tls": "", + "persist": false, + "proxy": "", + "authType": "", + "x": 650, + "y": 540, + "wires": [ + [ + "f2621320.750528" + ] + ] + }, + { + "id": "41656cf9.ba6b44", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 540, + "wires": [ + [ + "705997f2.8a6558" + ] + ] + }, + { + "id": "e39dd00d.c9fdd8", + "type": "openweathermap", + "z": "cebedba3.f3ea28", + "name": "current home weather", + "wtype": "current", + "lon": "7.00000", + "lat": "51.00000", + "city": "", + "country": "", + "language": "de", + "x": 700, + "y": 660, + "wires": [ + [ + "a85bbc0.8cf34c8" + ] + ] + }, + { + "id": "c3b6068a.19c72", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 130, + "y": 580, + "wires": [ + [ + "e19f983e.5d5258" + ] + ] + }, + { + "id": "a85bbc0.8cf34c8", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "assemble weather", + "func": "\npl = msg.payload;\nsectors = 16;\nwdir = Math.floor((pl.winddirection + (180/sectors)) / (360/sectors));\n//wdirname = [\"N\",\"NO\",\"O\",\"SO\",\"S\",\"SW\",\"W\",\"NW\",\"N\"][wdir];\nwdirname = [\"N\",\"NNO\",\"NO\",\"ONO\",\"O\",\"OSO\",\"SO\",\"SSO\",\"S\",\"SSW\",\"SW\",\"WSW\",\"W\",\"WNW\",\"NW\",\"NNW\",\"N\"][wdir];\n\ntext = text = `+++[${msg.topic}]+++ Wetter in ${pl.location}: ${pl.detail} bei ${pl.tempc}°C, Wind ${(pl.windspeed*3.6).toFixed(1)} km/h aus ${wdirname}. +++`;\n\nmsg.topic = `ledMatrix/text/${msg.topic}`;\nmsg.payload = text;\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 1070, + "y": 660, + "wires": [ + [ + "4593c2c0.497ffc" + ] + ] + }, + { + "id": "e19f983e.5d5258", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 4", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "4", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 360, + "y": 580, + "wires": [ + [ + "e29aa28f.5dcfc" + ] + ] + }, + { + "id": "d8ee53b9.4e6e6", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 5", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "5", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 360, + "y": 620, + "wires": [ + [ + "e39dd00d.c9fdd8", + "c41a439e.99a3b" + ] + ] + }, + { + "id": "705997f2.8a6558", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 3", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "3", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 360, + "y": 540, + "wires": [ + [ + "aeec710b.c2d3a" + ] + ] + }, + { + "id": "a41af02f.9fd8a", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 500, + "wires": [ + [ + "e88563e0.e28218" + ] + ] + }, + { + "id": "1a55dc19.a3d7d4", + "type": "http request", + "z": "cebedba3.f3ea28", + "name": "Tagesschau", + "method": "GET", + "ret": "txt", + "paytoqs": false, + "url": "https://www.tagesschau.de/xml/rss2/", + "tls": "", + "persist": false, + "proxy": "", + "authType": "", + "x": 670, + "y": 500, + "wires": [ + [ + "a0dec78d.cc7bd" + ] + ] + }, + { + "id": "3e90984d.576c68", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 130, + "y": 620, + "wires": [ + [ + "d8ee53b9.4e6e6" + ] + ] + }, + { + "id": "e88563e0.e28218", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 2", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "2", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 360, + "y": 500, + "wires": [ + [ + "1a55dc19.a3d7d4" + ] + ] + }, + { + "id": "33dd8f8c.8c1238", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "assemble news", + "func": "\nvar msgs = [];\ntext = `+++[${msg.topic}]+++ `.concat(msg.payload.rss.channel[0].title).concat(\" +++ \");\ntext = text.replace(\"*\",\"*\").replace(\" - Die Nachrichten der ARD\",\"\")\nnum = 0;\n\nfor (const i in msg.payload.rss.channel[0].item) {\n if (num >= 18) break;\n title = `${msg.payload.rss.channel[0].item[i].title}`;\n if (title.includes(\" ich \") || title.includes(\"FAQ\") || title.includes(\"tagesschau.de\"))\n continue; // skip\n if (title.startsWith(\"Lokalzeit \") || title.includes(\"WDR aktuell\") || title.includes(\" ich \"))\n break; // no more news\n if (title.includes(\"Fussball\") || title.includes(\"Fußball\") || title.includes(\"Bundesliga\") || title.includes(\"Regionalliga\") || title.includes(\"Spieltag\") || title.includes(\"Sport\"))\n continue; // skip\n text = text.concat(title).concat(\" +++ \")\n num++;\n// msgs.push({\n// \"topic\": \"ledMatrix/text\", \n// \"payload\": title\n// });\n}\ntext = text.replace(\" \", \" \");\nmsgs.push({\n \"topic\": `ledMatrix/text/${msg.topic}`,\n \"payload\": text\n });\nflow.set(\"headlines\", msgs);\n\nreturn msgs;\n", + "outputs": 1, + "noerr": 0, + "x": 1060, + "y": 500, + "wires": [ + [ + "4284b11f.f2812" + ] + ] + }, + { + "id": "a0dec78d.cc7bd", + "type": "xml", + "z": "cebedba3.f3ea28", + "name": "", + "property": "payload", + "attr": "", + "chr": "", + "x": 910, + "y": 500, + "wires": [ + [ + "33dd8f8c.8c1238" + ] + ] + }, + { + "id": "6b97b6f9.2f6468", + "type": "link out", + "z": "cebedba3.f3ea28", + "name": "control delay ring", + "links": [ + "52004887.5ae24", + "fd6021b7.1de958", + "257a7e31.44d1e2", + "ed40bb1b.ffa6e8", + "d4b48cf7.7f193", + "ed13c146.0fbd18" + ], + "x": 835, + "y": 280, + "wires": [] + }, + { + "id": "52004887.5ae24", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "6b97b6f9.2f6468", + "9335f64f.6fefd8" + ], + "x": 255, + "y": 500, + "wires": [ + [ + "e88563e0.e28218" + ] + ] + }, + { + "id": "fd6021b7.1de958", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "6b97b6f9.2f6468", + "9335f64f.6fefd8" + ], + "x": 255, + "y": 540, + "wires": [ + [ + "705997f2.8a6558" + ] + ] + }, + { + "id": "257a7e31.44d1e2", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "6b97b6f9.2f6468", + "9335f64f.6fefd8" + ], + "x": 255, + "y": 580, + "wires": [ + [ + "e19f983e.5d5258" + ] + ] + }, + { + "id": "ed40bb1b.ffa6e8", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "6b97b6f9.2f6468", + "9335f64f.6fefd8" + ], + "x": 255, + "y": 620, + "wires": [ + [ + "d8ee53b9.4e6e6" + ] + ] + }, + { + "id": "a360f30d.33a3b", + "type": "mqtt in", + "z": "cebedba3.f3ea28", + "name": "MM26", + "topic": "#", + "qos": "2", + "datatype": "auto", + "broker": "4a8f9596.4cdd74", + "x": 350, + "y": 100, + "wires": [ + [ + "db8a67dc.515f48" + ] + ] + }, + { + "id": "db8a67dc.515f48", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "store single value received via MQTT", + "func": "\nif (msg.topic == \"meter/elec/pwrTotal\")\n global.set(\"power\", `Strom: ${parseFloat(msg.payload).toFixed(1)} W`);\n\nif (msg.topic == \"sensor/airq/any/co2_scd30\")\n global.set(\"co2\", `CO2: ${parseFloat(msg.payload).toFixed(1)} ppm`);\n\nif (msg.topic == \"ttn/dev/sensorsre/node03/c_PM1.0\")\n global.set(\"pm010\", `PM1.0: ${parseFloat(msg.payload).toFixed(1)} ppm`);\n\nif (msg.topic == \"sensor/airq/any/iaq_bme680\")\n global.set(\"iaq\", `IAQ: ${parseFloat(msg.payload).toFixed(1)}`);\n\nif (msg.topic == \"meter/elec/pwrTotal\")\n global.set(\"power\", `Strom: ${parseFloat(msg.payload).toFixed(1)} W`);\n\n/**else if (msg.topic == \"cmnd/DruckerDose/power\" && msg.payload == \"OFF\")\n global.set(\"printprog\", null);**/\n\nelse return;\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 750, + "y": 100, + "wires": [ + [ + "149bebac.68066c" + ] + ] + }, + { + "id": "39667662.97d7da", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "assemble stored info", + "func": "\nvar val = \"\";\n\nt = msg.topic;\n\nif (t == \"6\") {\n val = global.get(\"power\") || \"\";\n} else if (t == \"7\") {\n co2 = global.get(\"co2\") || \"\";\n pm010 = global.get(\"pm010\") || \"\";\n iaq = global.get(\"iaq\") || \"\";\n val = co2 + \", \" + pm010 + \", \" + iaq;\n} else {\n val = global.get(t) || \"\";\n}\n\nif (val === \"\") return;\n\ntext = `+++ ${val} +++`;\n\nmsg.topic = `ledMatrix/text/${msg.topic}`;\nmsg.payload = text;\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 1080, + "y": 700, + "wires": [ + [ + "4593c2c0.497ffc" + ] + ] + }, + { + "id": "d9eb8338.71c248", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 6", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "6", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 360, + "y": 700, + "wires": [ + [ + "39667662.97d7da" + ] + ] + }, + { + "id": "de74d8b3.5ef1a8", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 130, + "y": 700, + "wires": [ + [ + "d9eb8338.71c248" + ] + ] + }, + { + "id": "d4b48cf7.7f193", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "6b97b6f9.2f6468", + "9335f64f.6fefd8" + ], + "x": 255, + "y": 700, + "wires": [ + [ + "d9eb8338.71c248" + ] + ] + }, + { + "id": "abc134ae.ca3d4", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 7", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "7", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 360, + "y": 740, + "wires": [ + [ + "39667662.97d7da" + ] + ] + }, + { + "id": "ea2fcdbc.6cd0b", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 130, + "y": 740, + "wires": [ + [ + "abc134ae.ca3d4" + ] + ] + }, + { + "id": "ed13c146.0fbd18", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "6b97b6f9.2f6468", + "9335f64f.6fefd8" + ], + "x": 255, + "y": 740, + "wires": [ + [ + "abc134ae.ca3d4" + ] + ] + }, + { + "id": "40c4c428.84e84c", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "store JSON value received via MQTT", + "func": "\nif (msg.topic == \"octoprint/progress/printing\") {\n global.set(\"printprog\", `3D: ${msg.payload.progress}% ${msg.payload.path.replace('.gcode','')}`);\n}\nelse return;\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 750, + "y": 140, + "wires": [ + [ + "149bebac.68066c" + ] + ] + }, + { + "id": "29ba9bdd.8c7614", + "type": "json", + "z": "cebedba3.f3ea28", + "name": "", + "property": "payload", + "action": "", + "pretty": false, + "x": 510, + "y": 140, + "wires": [ + [ + "40c4c428.84e84c" + ] + ] + }, + { + "id": "74fe0b70.8ec204", + "type": "mqtt in", + "z": "cebedba3.f3ea28", + "name": "OctoPrint", + "topic": "octoprint/#", + "qos": "2", + "datatype": "auto", + "broker": "4a8f9596.4cdd74", + "x": 360, + "y": 140, + "wires": [ + [ + "29ba9bdd.8c7614" + ] + ] + }, + { + "id": "c41a439e.99a3b", + "type": "openweathermap", + "z": "cebedba3.f3ea28", + "name": "current c3RE weather", + "wtype": "current", + "lon": "7.169770", + "lat": "51.624873", + "city": "", + "country": "", + "language": "de", + "x": 700, + "y": 620, + "wires": [ + [ + "f1d286a2.3980b" + ] + ] + }, + { + "id": "f964ea5c.e37e78", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "OWM API key", + "info": "xxxx", + "x": 890, + "y": 640, + "wires": [] + }, + { + "id": "9878de70.e9c0f", + "type": "mqtt out", + "z": "cebedba3.f3ea28", + "name": "→ c3RE", + "topic": "", + "qos": "", + "retain": "", + "broker": "1fc8a953.00a93f", + "x": 1680, + "y": 340, + "wires": [] + }, + { + "id": "4284b11f.f2812", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡ both", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 1290, + "y": 500, + "wires": [ + [ + "c1a11e89.30e8a8", + "9fed47f7.cfd55" + ] + ] + }, + { + "id": "f1d286a2.3980b", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "assemble weather", + "func": "\n\npl = msg.payload;\nsectors = 16;\nwdir = Math.floor((pl.winddirection + (180/sectors)) / (360/sectors));\n//wdirname = [\"N\",\"NO\",\"O\",\"SO\",\"S\",\"SW\",\"W\",\"NW\",\"N\"][wdir];\nwdirname = [\"N\",\"NNO\",\"NO\",\"ONO\",\"O\",\"OSO\",\"SO\",\"SSO\",\"S\",\"SSW\",\"SW\",\"WSW\",\"W\",\"WNW\",\"NW\",\"NNW\",\"N\"][wdir];\n\ntext = text = `+++[${msg.topic}]+++ Wetter in ${pl.location}: ${pl.detail} bei ${pl.tempc}°C, Wind ${(pl.windspeed*3.6).toFixed(1)} km/h aus ${wdirname}. +++`;\n\nmsg.topic = `ledMatrix/text/${msg.topic}`;\nmsg.payload = text;\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 1070, + "y": 620, + "wires": [ + [ + "d5c405f9.9b8c58" + ] + ] + }, + { + "id": "149bebac.68066c", + "type": "debug", + "z": "cebedba3.f3ea28", + "name": "", + "active": false, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "x": 1030, + "y": 80, + "wires": [] + }, + { + "id": "c7069600.a83fc8", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "assemble fake news", + "func": "\nvar msgs = [];\ntext = `+++[${msg.topic}]+++ `.concat(\"News\").concat(\" +++ \");\ntext = text.replace(\"*\",\"*\").replace(\" - Die Nachrichten der ARD\",\"\")\nnum = 0;\n\ntext = text.concat(\"Trump verschanzt sich im Weißen Haus\").concat(\" +++ \")\ntext = text.concat(\"Corona abgesagt - keine weiteren Infektionen in 2021\").concat(\" +++ \")\ntext = text.concat(\"Merkel kandidiert erneut als Kanzlerin\").concat(\" +++ \")\ntext = text.concat(\"Trump erleidet Herzinfarkt\").concat(\" +++ \")\ntext = text.concat(\"100 € Sachschaden: Einbrecher räumen komplette Primark-Filiale aus\").concat(\" +++ \")\ntext = text.concat('Spahn: \"Wenn alle zusammenhalten, können wir Corona überstehen, ohne Pflegekräfte besser zu bezahlen\"').concat(\" +++ \")\ntext = text.concat(\"Katholische Kirche erklärt Selfies zur Sünde\").concat(\" +++ \")\ntext = text.concat(\"Ab 11. Januar nach Lockdown: allgemeiner Winterschlaf bis April angeordnet\").concat(\" +++ \")\n\ntext = text.replace(\" \", \" \");\nmsgs.push({\n \"topic\": \"ledMatrix/text\", \n \"payload\": text\n });\nflow.set(\"headlines\", msgs);\n\nreturn msgs;\n", + "outputs": 1, + "noerr": 0, + "x": 1080, + "y": 460, + "wires": [ + [ + "4284b11f.f2812" + ] + ] + }, + { + "id": "1c560e52.bc2cb2", + "type": "debug", + "z": "cebedba3.f3ea28", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "x": 1690, + "y": 880, + "wires": [] + }, + { + "id": "7c16346f.8b5754", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "0", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 810, + "y": 460, + "wires": [ + [ + "c7069600.a83fc8" + ] + ] + }, + { + "id": "4c8ee403.497b64", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "", + "topic": "ledMatrix/text", + "payload": "***", + "payloadType": "str", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 1360, + "y": 340, + "wires": [ + [ + "c1a11e89.30e8a8" + ] + ] + }, + { + "id": "c1a11e89.30e8a8", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 1530, + "y": 340, + "wires": [ + [ + "9878de70.e9c0f", + "56ca1aa8.ec9c5c" + ] + ] + }, + { + "id": "9fed47f7.cfd55", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 1530, + "y": 840, + "wires": [ + [ + "ea8d9a9f.5f8b7", + "1c560e52.bc2cb2" + ] + ] + }, + { + "id": "348a7348.01b7ac", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "", + "topic": "ledMatrix/text", + "payload": "***", + "payloadType": "str", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 1340, + "y": 840, + "wires": [ + [ + "9fed47f7.cfd55" + ] + ] + }, + { + "id": "f4c97cdf.4e5218", + "type": "mqtt in", + "z": "cebedba3.f3ea28", + "name": "", + "topic": "ledMatrix/#", + "qos": "2", + "datatype": "auto", + "broker": "1fc8a953.00a93f", + "x": 150, + "y": 1160, + "wires": [ + [ + "220f16aa.62f912" + ] + ] + }, + { + "id": "220f16aa.62f912", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "filter out certain topics", + "func": "\nif (msg.topic == \"ledMatrix/status\") return;\n\nif (msg.topic == \"ledMatrix/intensity\") return;\n\nif (msg.topic == \"ledMatrix/delay\") return;\n\nif (msg.topic == \"ledMatrix/channel\") return;\n\nif (msg.topic == \"ledMatrix/text/5\") return;\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 360, + "y": 1160, + "wires": [ + [ + "dd2463b.19fa72" + ] + ] + }, + { + "id": "ea8d9a9f.5f8b7", + "type": "mqtt out", + "z": "cebedba3.f3ea28", + "name": "→ Home", + "topic": "", + "qos": "", + "retain": "", + "broker": "4a8f9596.4cdd74", + "x": 1680, + "y": 840, + "wires": [] + }, + { + "id": "fbf1b100.9819b", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 7", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "7", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 1020, + "y": 1000, + "wires": [ + [ + "4207c369.d42e44" + ] + ] + }, + { + "id": "230d124a.45369e", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 890, + "y": 1000, + "wires": [ + [ + "fbf1b100.9819b" + ] + ] + }, + { + "id": "bb5a8b3a.4b5418", + "type": "delay", + "z": "cebedba3.f3ea28", + "name": "", + "pauseType": "delay", + "timeout": "60", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "x": 720, + "y": 1000, + "wires": [ + [ + "fbf1b100.9819b", + "f2bbded0.2d4a2" + ] + ] + }, + { + "id": "f2bbded0.2d4a2", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 570, + "y": 1000, + "wires": [ + [ + "bb5a8b3a.4b5418" + ] + ] + }, + { + "id": "4207c369.d42e44", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "assemble stored info", + "func": "\n\nt = msg.topic;\n\nval = global.get(t) || \"\";\n\nif (val === \"\") return;\n\ntext = `+++ ${val} +++`;\n\nmsg.topic = `ledMatrix/text`; //${msg.topic}`;\nmsg.payload = text;\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 1220, + "y": 1000, + "wires": [ + [] + ] + }, + { + "id": "e28c4b44.b0f04", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "START", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 940, + "wires": [ + [ + "49c7ca3.512b034" + ] + ] + }, + { + "id": "1bfac141.609847", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "STOP", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 980, + "wires": [ + [ + "3798a852.f93d6" + ] + ] + }, + { + "id": "3798a852.f93d6", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "", + "rules": [ + { + "t": "set", + "p": "reset", + "pt": "msg", + "to": "1", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 280, + "y": 980, + "wires": [ + [ + "49c7ca3.512b034" + ] + ] + }, + { + "id": "49c7ca3.512b034", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 1000, + "wires": [ + [ + "f2bbded0.2d4a2" + ] + ] + }, + { + "id": "a64a366a.e0eaf", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "STEP", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 1020, + "wires": [ + [ + "4ebc741d.a3eeb4" + ] + ] + }, + { + "id": "4ebc741d.a3eeb4", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "", + "rules": [ + { + "t": "set", + "p": "flush", + "pt": "msg", + "to": "1", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 280, + "y": 1020, + "wires": [ + [ + "49c7ca3.512b034" + ] + ] + }, + { + "id": "56ca1aa8.ec9c5c", + "type": "debug", + "z": "cebedba3.f3ea28", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "x": 1690, + "y": 380, + "wires": [] + }, + { + "id": "99588967.46bf1", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "special information", + "info": "", + "x": 110, + "y": 900, + "wires": [] + }, + { + "id": "3b8e356f.439d4a", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "global information", + "info": "", + "x": 110, + "y": 460, + "wires": [] + }, + { + "id": "d451f24c.f4a4b8", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "local information", + "info": "", + "x": 100, + "y": 660, + "wires": [] + }, + { + "id": "fd1f4398.982218", + "type": "delay", + "z": "cebedba3.f3ea28", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "x": 280, + "y": 1600, + "wires": [ + [ + "bf4df305.9c3b88" + ] + ] + }, + { + "id": "636d2315.5f83d4", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 530, + "y": 1600, + "wires": [ + [ + "b96e7f63.47f45" + ] + ] + }, + { + "id": "526e5879.52649", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 530, + "y": 1640, + "wires": [ + [ + "612be7f4.f3edc" + ] + ] + }, + { + "id": "612be7f4.f3edc", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 4", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "4", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 660, + "y": 1640, + "wires": [ + [ + "c84ad8eb.54af68" + ] + ] + }, + { + "id": "d14c3cd9.930b28", + "type": "delay", + "z": "cebedba3.f3ea28", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "x": 280, + "y": 1640, + "wires": [ + [ + "d939ddc4.30ff1" + ] + ] + }, + { + "id": "14ff1521.10c70b", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 5", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "5", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 660, + "y": 1680, + "wires": [ + [ + "c84ad8eb.54af68" + ] + ] + }, + { + "id": "b96e7f63.47f45", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 3", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "3", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 660, + "y": 1600, + "wires": [ + [ + "c84ad8eb.54af68" + ] + ] + }, + { + "id": "b65b56e2.649a6", + "type": "delay", + "z": "cebedba3.f3ea28", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "x": 280, + "y": 1680, + "wires": [ + [ + "6d6fe4bd.f6b0a4" + ] + ] + }, + { + "id": "2ac775f4.d51d8a", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 530, + "y": 1560, + "wires": [ + [ + "a85e1904.d75dc8" + ] + ] + }, + { + "id": "2f30136.2aafeec", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 130, + "y": 1600, + "wires": [ + [ + "fd1f4398.982218", + "b96e7f63.47f45" + ] + ] + }, + { + "id": "bf4df305.9c3b88", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 130, + "y": 1640, + "wires": [ + [ + "d14c3cd9.930b28", + "612be7f4.f3edc" + ] + ] + }, + { + "id": "d939ddc4.30ff1", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 130, + "y": 1680, + "wires": [ + [ + "b65b56e2.649a6", + "14ff1521.10c70b" + ] + ] + }, + { + "id": "9914050e.b031b", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 530, + "y": 1680, + "wires": [ + [ + "14ff1521.10c70b" + ] + ] + }, + { + "id": "9d57c865.9b5308", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 130, + "y": 1560, + "wires": [ + [ + "a85e1904.d75dc8", + "7c4d9438.1c7794" + ] + ] + }, + { + "id": "7c4d9438.1c7794", + "type": "delay", + "z": "cebedba3.f3ea28", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "x": 280, + "y": 1560, + "wires": [ + [ + "2f30136.2aafeec" + ] + ] + }, + { + "id": "a85e1904.d75dc8", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 2", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "2", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 660, + "y": 1560, + "wires": [ + [ + "c84ad8eb.54af68" + ] + ] + }, + { + "id": "c76ade3f.225e2", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "995482bc.2b74f8", + "9335f64f.6fefd8" + ], + "x": 155, + "y": 1560, + "wires": [ + [ + "7c4d9438.1c7794" + ] + ] + }, + { + "id": "9bcbf571.a491", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "995482bc.2b74f8", + "9335f64f.6fefd8" + ], + "x": 155, + "y": 1600, + "wires": [ + [ + "fd1f4398.982218" + ] + ] + }, + { + "id": "fcd5a7fc.51e62", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "995482bc.2b74f8", + "9335f64f.6fefd8" + ], + "x": 155, + "y": 1640, + "wires": [ + [ + "d14c3cd9.930b28" + ] + ] + }, + { + "id": "bf8c8d60.8153b", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "995482bc.2b74f8", + "9335f64f.6fefd8" + ], + "x": 155, + "y": 1680, + "wires": [ + [ + "b65b56e2.649a6" + ] + ] + }, + { + "id": "c1377cf6.9d5e28", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 6", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "6", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 660, + "y": 1720, + "wires": [ + [ + "c84ad8eb.54af68" + ] + ] + }, + { + "id": "b1c941eb.a36d78", + "type": "delay", + "z": "cebedba3.f3ea28", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "x": 280, + "y": 1720, + "wires": [ + [ + "3f54e199.d1e57e" + ] + ] + }, + { + "id": "6d6fe4bd.f6b0a4", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 130, + "y": 1720, + "wires": [ + [ + "b1c941eb.a36d78", + "c1377cf6.9d5e28" + ] + ] + }, + { + "id": "316b0dfb.75a00a", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 530, + "y": 1720, + "wires": [ + [ + "c1377cf6.9d5e28" + ] + ] + }, + { + "id": "d8b275f6.1f7e18", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "995482bc.2b74f8", + "9335f64f.6fefd8" + ], + "x": 155, + "y": 1720, + "wires": [ + [ + "b1c941eb.a36d78" + ] + ] + }, + { + "id": "a4fd79b9.92c09", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "Topic := 7", + "rules": [ + { + "t": "set", + "p": "topic", + "pt": "msg", + "to": "7", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 660, + "y": 1760, + "wires": [ + [ + "c84ad8eb.54af68" + ] + ] + }, + { + "id": "400f1d2e.550194", + "type": "delay", + "z": "cebedba3.f3ea28", + "name": "", + "pauseType": "delay", + "timeout": "10", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "x": 280, + "y": 1760, + "wires": [ + [ + "9d57c865.9b5308" + ] + ] + }, + { + "id": "3f54e199.d1e57e", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 130, + "y": 1760, + "wires": [ + [ + "400f1d2e.550194", + "a4fd79b9.92c09" + ] + ] + }, + { + "id": "641f3d74.9d8b94", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "TRIG", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "", + "x": 530, + "y": 1760, + "wires": [ + [ + "a4fd79b9.92c09" + ] + ] + }, + { + "id": "201f46f0.2a751a", + "type": "link in", + "z": "cebedba3.f3ea28", + "name": "", + "links": [ + "995482bc.2b74f8", + "9335f64f.6fefd8" + ], + "x": 155, + "y": 1760, + "wires": [ + [ + "400f1d2e.550194" + ] + ] + }, + { + "id": "995482bc.2b74f8", + "type": "link out", + "z": "cebedba3.f3ea28", + "name": "control delay ring", + "links": [ + "c76ade3f.225e2", + "9bcbf571.a491", + "fcd5a7fc.51e62", + "bf8c8d60.8153b", + "d8b275f6.1f7e18", + "201f46f0.2a751a" + ], + "x": 755, + "y": 1460, + "wires": [] + }, + { + "id": "92f28cb4.7719d8", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 670, + "y": 1460, + "wires": [ + [ + "995482bc.2b74f8" + ] + ] + }, + { + "id": "e5546c3c.8860d", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "", + "rules": [ + { + "t": "set", + "p": "reset", + "pt": "msg", + "to": "1", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 500, + "y": 1440, + "wires": [ + [ + "92f28cb4.7719d8" + ] + ] + }, + { + "id": "63f8ecbd.570a84", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "", + "rules": [ + { + "t": "set", + "p": "flush", + "pt": "msg", + "to": "1", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 500, + "y": 1480, + "wires": [ + [ + "92f28cb4.7719d8" + ] + ] + }, + { + "id": "5bbb72db.0aa324", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "START", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 1400, + "wires": [ + [ + "7d92537.a3480ac" + ] + ] + }, + { + "id": "d5411f5c.22f89", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "STOP", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 1440, + "wires": [ + [ + "e9f752be.8b1998" + ] + ] + }, + { + "id": "15748f43.5d8a69", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "STEP", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 1480, + "wires": [ + [ + "cc13a2a9.d4ae7" + ] + ] + }, + { + "id": "9ed756ca.a1295", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "UI", + "info": "", + "x": 70, + "y": 1280, + "wires": [] + }, + { + "id": "d16a0009.4ec3", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "connect c3RE → home", + "info": "", + "x": 120, + "y": 1120, + "wires": [] + }, + { + "id": "fb9f70d0.ed07c", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "refresh", + "info": "", + "x": 70, + "y": 180, + "wires": [] + }, + { + "id": "64fa3a47.07bd54", + "type": "delay", + "z": "cebedba3.f3ea28", + "name": "", + "pauseType": "delay", + "timeout": "60", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "x": 720, + "y": 280, + "wires": [ + [ + "9e060bbe.c74c18", + "6b97b6f9.2f6468" + ] + ] + }, + { + "id": "9e060bbe.c74c18", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 570, + "y": 280, + "wires": [ + [ + "64fa3a47.07bd54" + ] + ] + }, + { + "id": "532bf271.09f354", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "START", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 220, + "wires": [ + [ + "842dbe89.bae108" + ] + ] + }, + { + "id": "91fe1db3.b1bc", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "STOP", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 260, + "wires": [ + [ + "d986a1bb.2c39" + ] + ] + }, + { + "id": "d986a1bb.2c39", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "", + "rules": [ + { + "t": "set", + "p": "reset", + "pt": "msg", + "to": "1", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 280, + "y": 260, + "wires": [ + [ + "842dbe89.bae108" + ] + ] + }, + { + "id": "842dbe89.bae108", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 430, + "y": 280, + "wires": [ + [ + "9e060bbe.c74c18" + ] + ] + }, + { + "id": "c4295dd2.934758", + "type": "inject", + "z": "cebedba3.f3ea28", + "name": "STEP", + "topic": "", + "payload": "", + "payloadType": "date", + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "x": 130, + "y": 300, + "wires": [ + [ + "e004b60f.0317d" + ] + ] + }, + { + "id": "e004b60f.0317d", + "type": "change", + "z": "cebedba3.f3ea28", + "name": "", + "rules": [ + { + "t": "set", + "p": "flush", + "pt": "msg", + "to": "1", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 280, + "y": 300, + "wires": [ + [ + "842dbe89.bae108" + ] + ] + }, + { + "id": "4593c2c0.497ffc", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡ only local", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 1310, + "y": 660, + "wires": [ + [ + "9fed47f7.cfd55" + ] + ] + }, + { + "id": "d5c405f9.9b8c58", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡ only global", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 1310, + "y": 580, + "wires": [ + [ + "c1a11e89.30e8a8" + ] + ] + }, + { + "id": "a8eca210.842ff8", + "type": "comment", + "z": "cebedba3.f3ea28", + "name": "<< currently unused", + "info": "", + "x": 1430, + "y": 1000, + "wires": [] + }, + { + "id": "95ecc860.71ded", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "get stored value", + "func": "\nmsg.payload = flow.get(`uimarquee_${msg.topic}`) || `[${msg.topic}]...`;\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 1040, + "y": 1560, + "wires": [ + [ + "f6edb468.b587c8" + ] + ] + }, + { + "id": "c84ad8eb.54af68", + "type": "change", + "z": "cebedba3.f3ea28", + "name": " ≡", + "rules": [], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 870, + "y": 1560, + "wires": [ + [ + "95ecc860.71ded" + ] + ] + }, + { + "id": "f6edb468.b587c8", + "type": "ui_text", + "z": "cebedba3.f3ea28", + "group": "7eddd0b5.69293", + "order": 1, + "width": 12, + "height": 9, + "name": "", + "label": "{{msg.topic}}", + "format": "{{msg.payload}}", + "layout": "col-center", + "x": 1210, + "y": 1560, + "wires": [] + }, + { + "id": "7d92537.a3480ac", + "type": "ui_button", + "z": "cebedba3.f3ea28", + "name": "start button", + "group": "7eddd0b5.69293", + "order": 2, + "width": 4, + "height": 1, + "passthru": true, + "label": "start", + "tooltip": "", + "color": "", + "bgcolor": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "", + "x": 290, + "y": 1400, + "wires": [ + [ + "9d57c865.9b5308" + ] + ] + }, + { + "id": "e9f752be.8b1998", + "type": "ui_button", + "z": "cebedba3.f3ea28", + "name": "stop button", + "group": "7eddd0b5.69293", + "order": 6, + "width": 4, + "height": 1, + "passthru": true, + "label": "stop", + "tooltip": "", + "color": "", + "bgcolor": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "", + "x": 290, + "y": 1440, + "wires": [ + [ + "e5546c3c.8860d" + ] + ] + }, + { + "id": "cc13a2a9.d4ae7", + "type": "ui_button", + "z": "cebedba3.f3ea28", + "name": "step button", + "group": "7eddd0b5.69293", + "order": 4, + "width": 2, + "height": 1, + "passthru": true, + "label": "step", + "tooltip": "", + "color": "", + "bgcolor": "", + "icon": "", + "payload": "", + "payloadType": "str", + "topic": "", + "x": 290, + "y": 1480, + "wires": [ + [ + "63f8ecbd.570a84" + ] + ] + }, + { + "id": "34ebd1c6.9760fe", + "type": "mqtt in", + "z": "cebedba3.f3ea28", + "name": "", + "topic": "ledMatrix/text/#", + "qos": "2", + "datatype": "auto", + "broker": "4a8f9596.4cdd74", + "x": 160, + "y": 1320, + "wires": [ + [ + "2bdb30e5.700c48" + ] + ] + }, + { + "id": "2bdb30e5.700c48", + "type": "function", + "z": "cebedba3.f3ea28", + "name": "store value", + "func": "\nmsg.topic = msg.topic.replace(\"ledMatrix/text/\", \"uimarquee_\");\n\nflow.set(msg.topic, msg.payload);\n\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 350, + "y": 1320, + "wires": [ + [] + ] + }, + { + "id": "7cc428db.629df", + "type": "ui_text_input", + "z": "cebedba3.f3ea28", + "name": "Channel(s)", + "label": "", + "tooltip": "", + "group": "48e68465.bfcf0c", + "order": 1, + "width": 6, + "height": 1, + "passthru": true, + "mode": "text", + "delay": 300, + "topic": "channel", + "x": 290, + "y": 1860, + "wires": [ + [ + "3490f20c.2aecbe" + ] + ] + }, + { + "id": "3490f20c.2aecbe", + "type": "mqtt out", + "z": "cebedba3.f3ea28", + "name": "→ Home ledMatrix/channel", + "topic": "ledMatrix/channel", + "qos": "", + "retain": "", + "broker": "4a8f9596.4cdd74", + "x": 580, + "y": 1860, + "wires": [] + }, + { + "id": "4a8f9596.4cdd74", + "type": "mqtt-broker", + "z": "", + "name": "dave", + "broker": "dave", + "port": "1883", + "clientid": "", + "usetls": false, + "compatmode": true, + "keepalive": "60", + "cleansession": true, + "birthTopic": "", + "birthQos": "0", + "birthRetain": "false", + "birthPayload": "", + "closeTopic": "", + "closeRetain": "false", + "closePayload": "", + "willTopic": "", + "willQos": "0", + "willRetain": "false", + "willPayload": "" + }, + { + "id": "1fc8a953.00a93f", + "type": "mqtt-broker", + "z": "", + "name": "c3RE", + "broker": "c3re.de", + "port": "1883", + "clientid": "", + "usetls": false, + "compatmode": true, + "keepalive": "60", + "cleansession": true, + "birthTopic": "", + "birthQos": "0", + "birthRetain": "false", + "birthPayload": "", + "closeTopic": "", + "closeQos": "0", + "closeRetain": "false", + "closePayload": "", + "willTopic": "", + "willQos": "0", + "willRetain": "false", + "willPayload": "" + }, + { + "id": "7eddd0b5.69293", + "type": "ui_group", + "z": "", + "name": "Local Display", + "tab": "b108c526.4e2e58", + "order": 2, + "disp": true, + "width": "12", + "collapse": false + }, + { + "id": "48e68465.bfcf0c", + "type": "ui_group", + "z": "", + "name": "MarQueTTino Control", + "tab": "b108c526.4e2e58", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "b108c526.4e2e58", + "type": "ui_tab", + "z": "", + "name": "Marquee", + "icon": "dashboard", + "disabled": false, + "hidden": false + } +] \ No newline at end of file From bdb2389cdf277559c5288fb95629bc72f17030a7 Mon Sep 17 00:00:00 2001 From: Kater_S Date: Fri, 8 Jan 2021 13:33:57 +0100 Subject: [PATCH 09/12] small edit --- Readme.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index d0709b3..161d45f 100644 --- a/Readme.md +++ b/Readme.md @@ -14,6 +14,15 @@ - `ledMatrix/blink`: 0 = no blinking; 1 = fastest, 1000 = slowest blinking. Default: 0 - `ledMatrix/enable`: 0 = display off, 1 = display on. Default: 1 +**Extension:** text channels, `` = `0`..`9` + +- `ledMatrix/text/` — text for a specific channel (`.../text/0` = same as `.../text`) +- `ledMatrix/channel` — set channels to be displayed + - `[,...]` — list of channels + - `` — only one channel + - "" (empty) — only channel 0 + + #### Individual Topics ##### Subscribed by Device @@ -22,14 +31,6 @@ Same as global topics, but `ledMatrix//...` instead of `ledMatrix/...` ( TBD: priority of individual topics over global topics ? -Extension: text channels, `` = `0`..`9` - -- `ledMatrix/text/` — text for a specific channel (`.../text/0` = same as `.../text`) -- `ledMatrix/channel` — set channels to be displayed - - `[,...]` — list of channels - - `` — only one channel - - "" (empty) — only channel 0 - ##### Published by Device - `ledMatrix//status`: From a999f7d3d14f563e62d6538d82972023bd548b4a Mon Sep 17 00:00:00 2001 From: Kater_S Date: Mon, 11 Jan 2021 20:13:45 +0100 Subject: [PATCH 10/12] static text options negative delay determines time to show each channel when not scrolling; case-insensitive subscribed topics; cut off logged text buffers to prevent display stuttering --- MarQueTT.ino | 116 +++++++++++++++++++++++++------------------- local_config.dist.h | 4 +- 2 files changed, 70 insertions(+), 50 deletions(-) diff --git a/MarQueTT.ino b/MarQueTT.ino index 1eadc06..ced9fab 100644 --- a/MarQueTT.ino +++ b/MarQueTT.ino @@ -1,25 +1,26 @@ /* - * MarQueTT[ino] Matrix Display based on n x 8x8 monochrome LEDs, driven by MAX7221 - * - * a 2020/2021 c3RE project - * - */ + MarQueTT[ino] Matrix Display based on n x 8x8 monochrome LEDs, driven by MAX7221 -#define VERSION "1.3.3" + a 2020/2021 c3RE project + +*/ + +#define VERSION "1.3.4" /* Version history: - * 1.3.3 show + publish version number - * 1.3.2 show device address (lower 3 bytes of MAC address) - * 1.3.1 OTA, TelnetStream logging - * 1.3.0 multiple text channels, cycling - * 1.2.2 silent (no-publish) mode - * 1.2.1 device-specific topics, - * 1.2.0 no-scroll mode - * 1.1.1 publish will (offline status), font editor (offline HTML) - * 1.1.0 larger MQTT message buffer, publish status, UTF-8 special characters - * 1.0.1 blinking - * 1.0.0 initial version - */ + 1.3.4 options for static text display + 1.3.3 show + publish version number + 1.3.2 show device address (lower 3 bytes of MAC address) + 1.3.1 OTA, TelnetStream logging + 1.3.0 multiple text channels, cycling + 1.2.2 silent (no-publish) mode + 1.2.1 device-specific topics, + 1.2.0 no-scroll mode + 1.1.1 publish will (offline status), font editor (offline HTML) + 1.1.0 larger MQTT message buffer, publish status, UTF-8 special characters + 1.0.1 blinking + 1.0.0 initial version +*/ #include #include @@ -60,11 +61,13 @@ uint16_t current_channel = 0; uint16_t textIndex = 0; uint8_t colIndex = 0; uint16_t scrollWhitespace = 0; +uint64_t marqueeCycleTimestamp = 0; uint64_t marqueeDelayTimestamp = 0; uint64_t marqueeBlinkTimestamp; uint16_t blinkDelay = 0; char devaddr[20]; char devname[40]; +String devname_lc; WiFiClient espClient; PubSubClient client(espClient); @@ -125,6 +128,8 @@ void setup_wifi() { snprintf(devaddr, sizeof(devaddr), "%02X%02X%02X", mac[3], mac[4], mac[5]); snprintf(devname, sizeof(devname), "MarQueTTino-%02X%02X%02X", mac[3], mac[4], mac[5]); Serial.println((String)"This device is called '" + devname + "'."); + devname_lc = String(devname); + devname_lc.toLowerCase(); // used for topic comparisons WiFi.hostname(devname); ArduinoOTA.setHostname(devname); @@ -172,9 +177,6 @@ void loop() { ArduinoOTA.handle(); if (blinkDelay) { - if (marqueeBlinkTimestamp > millis()) { - marqueeBlinkTimestamp > millis(); - } if (marqueeBlinkTimestamp + blinkDelay < millis()) { led.setEnabled(false); delay(1); @@ -204,16 +206,20 @@ void printHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with l } -void callback(char* topic, byte* payload, unsigned int length) { +void callback(char* topic, byte* payload, unsigned int length) +{ + if (String(topic).endsWith("status")) return; // don't process stuff we just published LogTarget.print((String)"MQTT in: " + topic + "\t = ["); - for (int i = 0; i < length; i++) LogTarget.print((char)(payload[i])); + for (int i = 0; i < min((int)length, 30); i++) LogTarget.print((char)(payload[i])); + if (length > 30) LogTarget.print("..."); LogTarget.println("]"); String command = topic + String(topic).lastIndexOf(TOPICROOT "/") + strlen(TOPICROOT) + 1; + command.toLowerCase(); - if (command.startsWith(devname)) { - command.remove(0, strlen(devname) + 1); + if (command.startsWith(devname_lc)) { // device-specific topic was used + command.remove(0, strlen(devname) + 1); // strip device name } LogTarget.println((String)"Command = [" + command + "]"); @@ -231,19 +237,31 @@ void callback(char* topic, byte* payload, unsigned int length) { if (command.equals("delay")) { scrollDelay = 0; - for (int i = 0 ; i < length; i++) { + bool negative = false; + int i = 0; + if (payload[0] == '-') { + negative = true; + i = 1; + } + for ( ; i < length; i++) { scrollDelay *= 10; scrollDelay += payload[i] - '0'; } + if (scrollDelay == 0) { textIndex = 0; - } else if (scrollDelay < 1) { - scrollDelay = 1; - } - if (scrollDelay > 10000) { - scrollDelay = 10000; + LogTarget.println((String)"Set no-scroll, cycle = " + cycleDelay); + } else if (negative) { + cycleDelay = scrollDelay; // negative value given is new cycle delay, no scrolling + textIndex = 0; + scrollDelay = 0; + LogTarget.println((String)"Set no-scroll, cycle = " + cycleDelay); + } else { + if (scrollDelay > 10000) { + scrollDelay = 10000; + } + LogTarget.println((String)"Set scroll delay = " + scrollDelay); } - return; } @@ -347,8 +365,10 @@ void callback(char* topic, byte* payload, unsigned int length) { int i = 0, j = 0; while (i < length) { uint8_t b = payload[i++]; - sprintf(tmp, "0x%.2X = '%c' -> ", b, b); - if (pr) LogTarget.print(tmp); + if (pr) { + sprintf(tmp, "0x%.2X = '%c' -> ", b, b); + LogTarget.print(tmp); + } if ((b & 0b10000000) == 0) { // 7-bit ASCII if (pr) LogTarget.println("ASCII"); text[j++] = b; @@ -448,20 +468,7 @@ void reconnect() { clientId += String(random(0xffff), HEX); if (client.connect(clientId.c_str(), mqtt_username, mqtt_password, TOPICROOT "/status", 1, true, "Offline")) { LogTarget.println("connected"); - client.subscribe(TOPICROOT "/enable"); - client.subscribe(((String)TOPICROOT "/" + devname + "/enable").c_str()); - client.subscribe(TOPICROOT "/intensity"); - client.subscribe(((String)TOPICROOT "/" + devname + "/intensity").c_str()); - client.subscribe(TOPICROOT "/delay"); - client.subscribe(((String)TOPICROOT "/" + devname + "/delay").c_str()); - client.subscribe(TOPICROOT "/text"); - client.subscribe(((String)TOPICROOT "/" + devname + "/text").c_str()); - client.subscribe(TOPICROOT "/text/#"); - client.subscribe(((String)TOPICROOT "/" + devname + "/text/#").c_str()); - client.subscribe(TOPICROOT "/blink"); - client.subscribe(((String)TOPICROOT "/" + devname + "/blink").c_str()); - client.subscribe(TOPICROOT "/channel"); - client.subscribe(((String)TOPICROOT "/" + devname + "/channel").c_str()); + client.subscribe(TOPICROOT "/#"); } else { LogTarget.print("failed, rc="); LogTarget.print(client.state()); @@ -541,7 +548,8 @@ void getScrolltextFromBuffer(int channel) } else { uint8_t ch = texts[channel][i]; scrollbuffer[i] = ch; - LogTarget.print(String(char(ch))); + if (i<=30) + LogTarget.print(String(char(ch))); if (!ch) { eot = true; LogTarget.println((String)"] (" + i + " bytes)"); @@ -557,6 +565,16 @@ void loop_matrix() if (scrollDelay) { marquee(); } else { + + // cycle non-scrolling text channels + if (marqueeCycleTimestamp + cycleDelay < millis()) { + marqueeCycleTimestamp = millis(); + textIndex = 0; + LogTarget.println("next static text"); + current_channel = textcycle[current_cycle]; + current_cycle = (current_cycle + 1) % num_textcycles; + } + if (textIndex == 0) { // begin writing text to display (e.g. after text was changed) LogTarget.println((String)"Display static text no." + current_channel); getScrolltextFromBuffer(current_channel); diff --git a/local_config.dist.h b/local_config.dist.h index 58ab75a..5fe70ed 100644 --- a/local_config.dist.h +++ b/local_config.dist.h @@ -20,9 +20,11 @@ const bool do_publishes = true; const int LEDMATRIX_SEGMENTS = 4; // Number of 8x8 Segments const uint8_t LEDMATRIX_CS_PIN = D4; // CableSelect pin -#define MAX_TEXT_LENGTH 3500 // Maximal text length, to large might use up to much ram +#define MAX_TEXT_LENGTH 3000 // Maximal text length + // too large might use up too much RAM and cause strange errors #define NUM_CHANNELS 10 // Number of text channels uint16_t scrollDelay = 25; // Initial scroll delay +uint16_t cycleDelay = 5; // Initial cycle delay (if delay==0, i.e. no scroll) const char* initialText = "no such text"; // Initial Text shown before the first MQTT-Message is received // Don't leave empty -- to show no text on startup set to " " // Use only 7-Bit ASCII characters! From e8e00a9f27daa67f074f7be9710dd59e47db27a1 Mon Sep 17 00:00:00 2001 From: Kater_S Date: Mon, 11 Jan 2021 20:17:27 +0100 Subject: [PATCH 11/12] Bugfix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit initial cycleDelay was 5 – now set to 5000. --- local_config.dist.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local_config.dist.h b/local_config.dist.h index 5fe70ed..fcad870 100644 --- a/local_config.dist.h +++ b/local_config.dist.h @@ -24,7 +24,7 @@ const uint8_t LEDMATRIX_CS_PIN = D4; // C // too large might use up too much RAM and cause strange errors #define NUM_CHANNELS 10 // Number of text channels uint16_t scrollDelay = 25; // Initial scroll delay -uint16_t cycleDelay = 5; // Initial cycle delay (if delay==0, i.e. no scroll) +uint16_t cycleDelay = 5000; // Initial cycle delay (if delay==0, i.e. no scroll) const char* initialText = "no such text"; // Initial Text shown before the first MQTT-Message is received // Don't leave empty -- to show no text on startup set to " " // Use only 7-Bit ASCII characters! From 5bacc7132343b8ba383986e63d1196171a12c5de Mon Sep 17 00:00:00 2001 From: Kater_S Date: Tue, 12 Jan 2021 11:57:36 +0100 Subject: [PATCH 12/12] Documentation some additions to Readme.md --- Readme.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 161d45f..a7a4b54 100644 --- a/Readme.md +++ b/Readme.md @@ -10,10 +10,13 @@ - `ledMatrix/text` : A UTF-8 coded text, max. 4096 bytes long. - `ledMatrix/intensity`: 0 = lowest, 15 = highest. Default: 1. -- `ledMatrix/delay`: 0 = no scrolling; 1 = fastest, 1000 = slowest scrolling. Default: 25 +- `ledMatrix/delay`: 1 = fastest, 1000 = slowest scrolling. Default: 25 +-                                         0 = no scrolling; < 0: negative value sets page cycle time in ms. Default: 5000 ms - `ledMatrix/blink`: 0 = no blinking; 1 = fastest, 1000 = slowest blinking. Default: 0 - `ledMatrix/enable`: 0 = display off, 1 = display on. Default: 1 +Default values are configurable in `local_config.h`. You can also use retained messages, preferably with individual topics (see below). + **Extension:** text channels, `` = `0`..`9` - `ledMatrix/text/` — text for a specific channel (`.../text/0` = same as `.../text`) @@ -40,7 +43,9 @@ TBD: priority of individual topics over global topics ? - `repeat` — sent at end of a sequence (TBD: for each channel or total sequence?) - `offline` — sent as will when the MQTT connection disconnects unexpectedly -### Wiring +### Setup + +#### Wiring Connect your display's X pin to your controller's Y pin: @@ -50,6 +55,12 @@ Connect your display's X pin to your controller's Y pin: See https://github.com/bartoszbielawski/LEDMatrixDriver#pin-selection for more information +#### Software + +- Copy `local_config.dist.h` to `local_config.h` and fill in WLAN credentials. +- Check the `LEDMATRIX_`* constants according to your hardware setup. +- You might also want to change the default values. + ### Change Ideas - Use `ledMatrix/all` instead of `ledMatrix` as a prefix for global topics so `ledMatrix/all/#` matches all global topics