Compare commits
No commits in common. "22a0c86d157b005057395eb17907c90102b96a6c" and "32ba4133809c5d2cf9ace0ae8f4b116ba11ed673" have entirely different histories.
22a0c86d15
...
32ba413380
316
src/util.h
316
src/util.h
@ -1,310 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// TODO: reverse engineer builtin and custom bitmaps
|
enum Brightness {
|
||||||
|
Normal,
|
||||||
// seems like our sign listens to messages on this address
|
Bright,
|
||||||
// it is also the software's default value
|
|
||||||
static constexpr auto SIGN_ADDRESS = 128;
|
|
||||||
|
|
||||||
enum class FontType {
|
|
||||||
Font5x6Short = 'q',
|
|
||||||
Font5x11ShortAndWide = 'r',
|
|
||||||
Font7x6Default = 's',
|
|
||||||
Font7x11Wide = 't',
|
|
||||||
Font7x9 = 'u',
|
|
||||||
Font7x17ExtraWide = 'v',
|
|
||||||
FontSmallFont = 'w',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Brightness {
|
void sendTextToSign(String text, Brightness brightness = Bright) {
|
||||||
Normal = 'a',
|
// debugging
|
||||||
Bright = 'b',
|
Serial.print("Sending text \"");
|
||||||
};
|
Serial.print(text);
|
||||||
|
Serial.println("\"");
|
||||||
|
|
||||||
enum class Method {
|
// TODO: find out how the brightness attribute in the LED sign is encoded in the UART signal
|
||||||
Cyclic = 'A',
|
Serial1.print("f01A\\s");
|
||||||
Immediate = 'B',
|
Serial1.print(text);
|
||||||
OpenFromRight = 'C',
|
Serial1.print("\r\r\r");
|
||||||
OpenFromLeft = 'D',
|
|
||||||
OpenFromCenter = 'E',
|
|
||||||
OpenToCenter = 'F',
|
|
||||||
CoverFromCenter = 'G',
|
|
||||||
CoverFromRight = 'H',
|
|
||||||
CoverFromLeft = 'I',
|
|
||||||
CoverToCenter = 'J',
|
|
||||||
ScrollUp = 'K',
|
|
||||||
ScrollDown = 'L',
|
|
||||||
InterlaceToCenter = 'M',
|
|
||||||
InterlaceCover = 'N',
|
|
||||||
CoverUp = 'O',
|
|
||||||
CoverDown = 'P',
|
|
||||||
ScanLine = 'Q',
|
|
||||||
Explode = 'R',
|
|
||||||
PacMan = 'S',
|
|
||||||
FallAndStack = 'T',
|
|
||||||
Shoot = 'U',
|
|
||||||
Flash = 'V',
|
|
||||||
Random = 'W',
|
|
||||||
SlideIn = 'X',
|
|
||||||
};
|
|
||||||
|
|
||||||
// macros are literal characters prefixed by ^
|
|
||||||
enum class Macros {
|
|
||||||
CurrentTime12hWithoutAmPm = 'D',
|
|
||||||
CurrentTime12hWithAmPm = 'E',
|
|
||||||
CurrentTime24h = 'F',
|
|
||||||
CurrentDateMmDdYyyy = 'A',
|
|
||||||
CurrentDateYyyy_Mm_Dd = 'B',
|
|
||||||
CurrentDateMm_Dd_Yyyy = 'C',
|
|
||||||
CurrentDateDd_Mm_Yyyy = 'a',
|
|
||||||
Beep1 = 'q',
|
|
||||||
Beep2 = 'r',
|
|
||||||
Beep3 = 's',
|
|
||||||
};
|
|
||||||
|
|
||||||
// symbols should be inserted as string representations of their numeric value prefixed by ^
|
|
||||||
enum class Symbols {
|
|
||||||
Sun = 66,
|
|
||||||
CloudOrWind = 67,
|
|
||||||
UmbrellaRaining = 68,
|
|
||||||
WallClock = 69,
|
|
||||||
Telephone = 70,
|
|
||||||
Glasses = 71,
|
|
||||||
Faucet = 72,
|
|
||||||
RocketOrSyringeOrSo = 73,
|
|
||||||
WhatTheHell = 74,
|
|
||||||
Key = 75,
|
|
||||||
Shirt = 76,
|
|
||||||
Helicopter = 77,
|
|
||||||
Car = 78,
|
|
||||||
Tank = 79,
|
|
||||||
House = 80,
|
|
||||||
Teapot = 81,
|
|
||||||
Trees = 82,
|
|
||||||
Duck = 83,
|
|
||||||
Scooter = 84,
|
|
||||||
Bicycle = 85,
|
|
||||||
Crown = 86,
|
|
||||||
AppleOrSo = 87,
|
|
||||||
ArrowRight = 88,
|
|
||||||
ArrowLeft = 89,
|
|
||||||
ArrowDownLeft = 90,
|
|
||||||
ArrowUpLeft = 91,
|
|
||||||
Cup = 92,
|
|
||||||
Chair = 93,
|
|
||||||
HighHeel = 94,
|
|
||||||
PrizeCup = 95,
|
|
||||||
};
|
|
||||||
|
|
||||||
// cartoons are represented as these characters prefixed by ^
|
|
||||||
enum class Cartoon {
|
|
||||||
MerryXMas = 'i',
|
|
||||||
HappyNewYear = 'j',
|
|
||||||
FourthOfJuly = 'k',
|
|
||||||
HappyEaster = 'l',
|
|
||||||
HappyHalloween = 'm',
|
|
||||||
DontDrinkAndDrive = 'n',
|
|
||||||
NoSmoking = 'o',
|
|
||||||
Welcome = 'p',
|
|
||||||
};
|
|
||||||
|
|
||||||
// the descriptions originate from the UTF-16 specification, https://www.ssec.wisc.edu/~tomw/java/unicode.html
|
|
||||||
// the extended charset starts where ASCII ends
|
|
||||||
enum class ExtendedChar {
|
|
||||||
// Ç
|
|
||||||
LatinCapitalLetterCWithCedilla = 0x80,
|
|
||||||
// ü
|
|
||||||
LatinSmallLetterUWithDiaeresis = 0x81,
|
|
||||||
// é
|
|
||||||
LatinSmallLetterEWithAcute = 0x82,
|
|
||||||
// â
|
|
||||||
LatinSmallLetterAWithCircumflex = 0x83,
|
|
||||||
// ä
|
|
||||||
LatinSmallLetterAWithDiaeresis = 0x84,
|
|
||||||
// á
|
|
||||||
LatinSmallLetterAWithGrave = 0x85,
|
|
||||||
// å
|
|
||||||
LatinSmallLetterAWithRingAbove = 0x86,
|
|
||||||
// ç
|
|
||||||
LatinSmallLetterCWithCedilla = 0x87,
|
|
||||||
// ê
|
|
||||||
LatinSmallLetterEWithCircumflex = 0x88,
|
|
||||||
// ë
|
|
||||||
LatinSmallLetterEWithDiaeresis = 0x89,
|
|
||||||
// è
|
|
||||||
LatinSmallLetterEWithGrave = 0x8a,
|
|
||||||
// Ï
|
|
||||||
LatinCapitalLetterIWithDiaeresis = 0x8b,
|
|
||||||
// Î
|
|
||||||
LatinCapitalLetterIWithCircumflex = 0x8c,
|
|
||||||
// Ì
|
|
||||||
LatinCapitalLetterIWithGrave = 0x8d,
|
|
||||||
// Ä
|
|
||||||
LatinCapitalLetterAWithDiaeresis = 0x8f,
|
|
||||||
// Å
|
|
||||||
LatinCapitalLetterAWithRingAbove = 0x8f,
|
|
||||||
// É
|
|
||||||
LatinCapitalLetterEWithAcute = 0x90,
|
|
||||||
// æ
|
|
||||||
LatinSmallLetterAe = 0x91,
|
|
||||||
// Æ
|
|
||||||
LatinCapitalLetterAe = 0x92,
|
|
||||||
// ô
|
|
||||||
LatinSmallLetterOWithCircumflex = 0x93,
|
|
||||||
// ö
|
|
||||||
LatinSmallLetterOWithDiaeresis = 0x94,
|
|
||||||
// ò
|
|
||||||
LatinSmallLetterOWithGrave = 0x95,
|
|
||||||
// û
|
|
||||||
LatinSmallLetterUWithCircumflex = 0x96,
|
|
||||||
// ù
|
|
||||||
LatinSmallLetterUWithGrave = 0x97,
|
|
||||||
// ÿ
|
|
||||||
LatinSmallLetterYWithDiaeresis = 0x98,
|
|
||||||
// Ö
|
|
||||||
LatinCapitalLLetterOWithDiaeresis = 0x99,
|
|
||||||
// Ü
|
|
||||||
LatinCapitalLetterUWithDiaeresis = 0x9a,
|
|
||||||
// ¢
|
|
||||||
CentSign = 0x9b,
|
|
||||||
// £
|
|
||||||
PoundSign = 0x9c,
|
|
||||||
// ¥
|
|
||||||
YenSign = 0x9d,
|
|
||||||
// urm...
|
|
||||||
UnknownPCurrencySign = 0x9e,
|
|
||||||
// ƒ
|
|
||||||
LatinSmallLetterFWithHook = 0x9f,
|
|
||||||
// á
|
|
||||||
LatinSmallLetterAWithAcute = 0xa0,
|
|
||||||
// Í
|
|
||||||
LatinCapitalLetterIWithAcute = 0xa1,
|
|
||||||
// Ó
|
|
||||||
LatinCapitalLetterOWithAcute = 0xa2,
|
|
||||||
// ú
|
|
||||||
LatinSmallLetterUWithAcute = 0xa3,
|
|
||||||
// ñ
|
|
||||||
LatinSmallLetterNWithTilde = 0xa4,
|
|
||||||
// Ñ
|
|
||||||
LatinCapitalLetterNWithTilde = 0xa5,
|
|
||||||
// a̱
|
|
||||||
LatinSmallLetterAWithMacronBelow = 0xa6,
|
|
||||||
// couldn't find that one in the UTF-16 table
|
|
||||||
LatinSmallLetterOWithMacronBelow = 0xa7,
|
|
||||||
// ¿
|
|
||||||
InvertedQuestionMark = 0xa8,
|
|
||||||
// you can probably imagine what it'll look like
|
|
||||||
LatinCapitalLetterZWithRingAbove = 0xa9,
|
|
||||||
// Ź
|
|
||||||
LatinCapitalLetterZWithAcute = 0xaa,
|
|
||||||
// ń
|
|
||||||
LatinSmallLetterNWithAcute = 0xab,
|
|
||||||
// some weird L with a diagonal line
|
|
||||||
AnotherWeirdSign = 0xac,
|
|
||||||
// Ś or ś, can't tell for sure, let's assume capital
|
|
||||||
LatinCapitalLetterSWithAcute = 0xab,
|
|
||||||
// Ć
|
|
||||||
LatinCapitalLetterCWithAcute = 0xac,
|
|
||||||
// ȩ
|
|
||||||
LatinSmallLetterEWithCedilla = 0xad,
|
|
||||||
// well, you can imagine what it'll look like
|
|
||||||
LatinSmallLetterAWithCedilla = 0xb0,
|
|
||||||
// €
|
|
||||||
EuroSign = 0xb1,
|
|
||||||
// E with some vertical line below
|
|
||||||
LatinCapitalLetterEWithSomeLineBelow = 0xb3,
|
|
||||||
// a with some vertical line below
|
|
||||||
LatinSmallLetterAWithSomeWeirdLineBelow = 0xb4,
|
|
||||||
// α
|
|
||||||
GreekSmallLetterAlpha = 0xe0,
|
|
||||||
// β
|
|
||||||
GreekSmallLetterBeta = 0xe1,
|
|
||||||
// Γ
|
|
||||||
GreekCapitalLetterGamma = 0xe2,
|
|
||||||
// π
|
|
||||||
GreekSmallLetterPi = 0xe3,
|
|
||||||
// Σ
|
|
||||||
GreekCapitalLetterSigma = 0xe4,
|
|
||||||
// σ
|
|
||||||
GreekSmallLetterSigma = 0xe5,
|
|
||||||
// μ
|
|
||||||
GreekSmallLetterMu = 0xe6,
|
|
||||||
// τ
|
|
||||||
GreekSmallLetterTau = 0xe7,
|
|
||||||
// Φ
|
|
||||||
GreekCapitalLetterPhi = 0xe8,
|
|
||||||
// θ
|
|
||||||
GreekSmallLetterTheta = 0xe9,
|
|
||||||
// Ω
|
|
||||||
GreekCapitalLetterOmega = 0xea,
|
|
||||||
// δ
|
|
||||||
GreekSmallLetterDelta = 0xeb,
|
|
||||||
// ∞
|
|
||||||
Infinity = 0xec,
|
|
||||||
// φ
|
|
||||||
GreekSmallLetterPhi = 0xed,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Message syntax:
|
|
||||||
* separator: ~
|
|
||||||
* address (int string): 128
|
|
||||||
* separator: ~
|
|
||||||
* random string(?): f01
|
|
||||||
* method (single char)
|
|
||||||
* format string (see below)
|
|
||||||
* end of message: \r\r\r
|
|
||||||
*
|
|
||||||
* the format string contains the message along with control characters that define some of the formatting
|
|
||||||
* note: we have not tested the UTF-8 capabilities, so far only ASCII
|
|
||||||
* it supports multiple lines (yet to be tested)
|
|
||||||
* you can also insert macros (e.g., current time/date), symbols (e.g., a sun) and cartoons
|
|
||||||
* the specials are preceded by a caret following the respective character
|
|
||||||
* then, there is an extended charset that supports a small subset of UTF-16 (yes, that's right, it's not just UTF-8)
|
|
||||||
* it can further contain information that should be used from their occurrence on
|
|
||||||
* the formatting escape sequences are prefixed by a literal backspace following a char that represents the format
|
|
||||||
* brightness is \<ab>, speed is Y<1-8>, "stay time" is \Z<1-8>, font type is \[A-X]
|
|
||||||
* new lines are introduced by a single \r, the end of the message is marked by three of them
|
|
||||||
*
|
|
||||||
* default formatting:
|
|
||||||
* - method: cyclic
|
|
||||||
* - font type: 7x6
|
|
||||||
* - speed: 5
|
|
||||||
* - stay time: 4
|
|
||||||
* - brightness: bright
|
|
||||||
*/
|
|
||||||
|
|
||||||
void sendTextToSign(
|
|
||||||
const String &text,
|
|
||||||
const Method method = Method::Cyclic,
|
|
||||||
const FontType fontType = FontType::Font7x6Default,
|
|
||||||
const int speed = 5,
|
|
||||||
const int stayTime = 4,
|
|
||||||
const Brightness brightness = Brightness::Bright
|
|
||||||
) {
|
|
||||||
if (speed < 0 || speed > 8) {
|
|
||||||
Serial.println("Invalid speed: " + String(speed, DEC));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (stayTime < 0 || stayTime > 8) {
|
|
||||||
Serial.println("Invalid stayTime: " + String(stayTime, DEC));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("Sending text: \"" + text + "\"");
|
|
||||||
|
|
||||||
String toSend = "~" + String(SIGN_ADDRESS, DEC) + "~f01" + (char) method + "\\" + (char) brightness + "\\Y" + String(speed, DEC) + "\\Z" + String(stayTime, DEC) + "\\" + (char) fontType + text + "\r\r\r";
|
|
||||||
|
|
||||||
Serial1.print(toSend);
|
|
||||||
|
|
||||||
Serial.println("Text sent to device: " + toSend);
|
|
||||||
Serial.print("Hex dump: ");
|
|
||||||
for (size_t i = 0; i < toSend.length(); ++i) {
|
|
||||||
auto hexChar = String(toSend[i], HEX);
|
|
||||||
if (hexChar.length() == 1) {
|
|
||||||
hexChar = '0' + hexChar;
|
|
||||||
}
|
|
||||||
Serial.print(hexChar + ' ');
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,11 @@ namespace {
|
|||||||
<input type="text" name="text">
|
<input type="text" name="text">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="bright">Bright mode</label>
|
||||||
|
<input type="checkbox" name="bright">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button>Send</button>
|
<button>Send</button>
|
||||||
</div>
|
</div>
|
||||||
@ -40,14 +45,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleFormSend() {
|
void handleFormSend() {
|
||||||
String text = webServer.arg("text");
|
sendTextToSign("HELLO FORM");
|
||||||
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
webServer.send(404, "text/plain", "empty text");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTextToSign(text);
|
|
||||||
|
|
||||||
// redirect back to root page
|
// redirect back to root page
|
||||||
webServer.sendHeader("Location", "/");
|
webServer.sendHeader("Location", "/");
|
||||||
@ -56,14 +54,7 @@ namespace {
|
|||||||
|
|
||||||
// RESTful API endpoint -- returns a proper status code rather than a redirect
|
// RESTful API endpoint -- returns a proper status code rather than a redirect
|
||||||
void handleApiSend() {
|
void handleApiSend() {
|
||||||
String text = webServer.arg("text");
|
sendTextToSign("HELLO API");
|
||||||
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
webServer.send(404, "text/plain", "empty text");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sendTextToSign(text);
|
|
||||||
webServer.send(200, "text/plain", "ok");
|
webServer.send(200, "text/plain", "ok");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
"""
|
|
||||||
Simple socket server used to reverse engineer the messaging protocol the bundled software uses to send data to the
|
|
||||||
LED sign.
|
|
||||||
|
|
||||||
The server just dumps incoming messages in a hexdump-like format as well as an easy to read Python bytestring.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import string
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_client(reader, writer):
|
|
||||||
message = await reader.read(255)
|
|
||||||
|
|
||||||
hex_chars = [hex(j)[2:].zfill(2) for j in message]
|
|
||||||
|
|
||||||
ascii_chars = []
|
|
||||||
for j in message:
|
|
||||||
char = chr(j)
|
|
||||||
if char in (string.ascii_letters + string.digits + string.punctuation):
|
|
||||||
ascii_chars.append(char)
|
|
||||||
else:
|
|
||||||
ascii_chars.append(".")
|
|
||||||
|
|
||||||
print("Message received: ", end="")
|
|
||||||
print(" ".join(hex_chars), end=" | ")
|
|
||||||
print("".join(ascii_chars), end=" | ")
|
|
||||||
print("".join(repr(message)))
|
|
||||||
|
|
||||||
writer.close()
|
|
||||||
|
|
||||||
|
|
||||||
async def run_server():
|
|
||||||
server = await asyncio.start_server(handle_client, '', 3000)
|
|
||||||
async with server:
|
|
||||||
await server.serve_forever()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(run_server())
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user