Compare commits

..

56 Commits

Author SHA1 Message Date
0f2598b600
Added LVGL update preview 2024-06-24 23:07:18 -04:00
03a84e8301
Added sponsor notice 2024-06-09 18:54:28 -04:00
dbca0988eb
Added buymeacoffee to funding 2024-06-09 18:45:45 -04:00
1b00c01641
Added funding button 2024-06-09 18:38:23 -04:00
a15fae2fd9
Fixed actions and other issues 2024-06-08 13:45:30 -04:00
a530492937
Finally got the code split up into multiple files for organization 2024-06-07 23:58:03 -04:00
8950faf44a
Fixed QUIT messages not displaying, started mainlining gotify code 2024-06-06 16:51:16 -04:00
a5e208755e
Fixed QUIT messages not displaying, started mainlining gotify code 2024-06-06 16:50:21 -04:00
995529af02
Fixed screen timeout issue from missing bool change 2024-06-06 01:31:16 -04:00
b0b1ee297d
Splitting code into their own .cpp and header files 2024-06-06 01:04:24 -04:00
9d19af9c95
Moved lora.cpp into main source folder 2024-06-06 00:21:34 -04:00
4aa62e9982
Lora recv loop added 2024-06-06 00:17:47 -04:00
847963c2ab
update.... 2 hie 4 commit message 2024-06-05 23:23:18 -04:00
e697bf6790
Fixed missing return statement on keyboard input 2024-06-05 20:24:48 -04:00
6dc045e66f
Fixed screen turn on not working from keyboard input 2024-06-05 20:20:53 -04:00
e77b192dee
Fixed keyboard not working 2024-06-05 20:17:57 -04:00
68b86a1d6d
Added SD card support and started Lora support 2024-06-05 20:04:40 -04:00
4b4be4c9db
Added SD card support 2024-06-05 19:32:59 -04:00
fb5784188e
Added brightness control function 2024-06-05 19:25:30 -04:00
da18703357
Added missing wireguard lib 2024-06-05 19:07:24 -04:00
bd15b524fb
Started adding Wireguard support 2024-06-05 19:00:15 -04:00
eaf5cdfc8d
ABC imports (ocd lol) 2024-06-05 18:18:23 -04:00
b00d779471
Fixed speaker.h spacing 2024-06-05 18:13:36 -04:00
5845a84250
TAKEEE ONNN MEEEEE boot intro fixed 2024-06-05 18:12:24 -04:00
0cfe90ff99
Improved RTTTL processing with the ESP8266Audio library 2024-06-05 18:07:54 -04:00
d4911ee2d3
Added IRC mention sounds, moved boot sound to AFTER the XBM display 2024-06-05 15:21:36 -04:00
86b4488d92
Added support for the T-Deck internal speaker! Boot sounds added, notification sounds incomming. 2024-06-05 15:06:51 -04:00
e991577a77
Merged setDefaultPreferences with loadPreferences 2024-06-05 12:07:43 -04:00
b2506fb589
Added default to get_encryption_type to hide warning when building 2024-06-05 11:54:20 -04:00
66845d0dcf
Added mac address randomization, improved wifi handling, and added a wifi event trigger 2024-06-05 11:50:08 -04:00
12a437ff69
Merged the loadPreferences and setDefaultPreferences functions 2024-06-04 12:15:09 -04:00
885c0f57c1
Added documentation on wiping settings on boot 2024-06-04 11:26:27 -04:00
b3f87b2fc4
Added the ability to wipe the stored preferences by press w on the boot screen and fixed issue where pressing a key to turn screen on was typing the char out 2024-06-04 02:41:58 -04:00
efdd8bcb34
Finally fixed line wraps with color properly 2024-05-30 00:15:49 -04:00
Dionysus
5312071d1c
Merge pull request #7 from imnotacoder-eeeee/main
Fixed multi-line with my beta fix, and normalized colors to match master
2024-05-29 23:15:03 -04:00
imnotacoder-eeeee
432ccdca90 MULTILINE FIX BETA, SET COLORS TO MASTER BRANCHES 2024-05-29 21:35:51 -04:00
imnotacoder-eeeee
252e97f139 multi-line doesnt seem bugged and joins still work, will work on the small gap in buffer at the bottom 2024-05-29 21:34:07 -04:00
imnotacoder-eeeee
711e8dee5b multi-line doesnt seem bugged and joins still work, will work on the small gap in buffer at the bottom 2024-05-29 21:31:51 -04:00
e
8ff6e5811e
Update README.md
Changed /debug to /info.
2024-05-29 21:18:10 -04:00
77a9144837
Added extended preferences 2024-05-29 17:14:53 -04:00
4afe369d27
Added back removed colors by accident, few verbose serial changes 2024-05-29 02:50:20 -04:00
462ed146c9
Still need ifdef undef for some warnings I guess.... 2024-05-29 00:37:46 -04:00
833d15db65
Found a better way to silence library warnings 2024-05-29 00:34:22 -04:00
a590dfccc8
Fixed warnings about redefinitions 2024-05-29 00:05:04 -04:00
6bb2532038
Updated roadmap and added serial logs 2024-05-28 20:48:06 -04:00
527eb75d67
Added memory constraints to things to keep everything memory safe from anomalies & attacks 2024-05-28 20:40:00 -04:00
eec732eb29
Added input buffer protection so we do not exceed the 512 limit IRC servers have 2024-05-28 19:45:12 -04:00
0d59ab3b7e
Updated README 2024-05-28 19:07:34 -04:00
8e3f702c98
Added cheers to e for hand typing color codes 2024-05-28 18:58:06 -04:00
af93986495
More code cleanup and comments 2024-05-28 18:56:20 -04:00
f4dc64fd69
Big code cleanup and organization 2024-05-28 18:32:54 -04:00
2445482bfd
Updated README.md 2024-05-28 01:01:25 -04:00
e620ad7a76
Fixed typo 2024-05-28 00:57:52 -04:00
b212090e91
Removed .vscode directory 2024-05-28 00:46:29 -04:00
0289343061
Updated roadmap and known issues 2024-05-28 00:42:29 -04:00
bf090d5d88
Added fist release! 2024-05-27 18:52:54 -04:00
30 changed files with 1930 additions and 1177 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
github: acidvegas
patreon: acid_drop
buy_me_a_coffee: acidvegas

5
.gitignore vendored
View File

@ -1,5 +1,2 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
.vscode

View File

Before

Width:  |  Height:  |  Size: 322 KiB

After

Width:  |  Height:  |  Size: 322 KiB

BIN
.screens/lvgl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

View File

Before

Width:  |  Height:  |  Size: 377 KiB

After

Width:  |  Height:  |  Size: 377 KiB

View File

@ -1,10 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

56
.vscode/settings.json vendored
View File

@ -1,56 +0,0 @@
{
"files.associations": {
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp"
}
}

View File

@ -2,20 +2,16 @@
<img src="./.screens/aciddrop2.png" />
</p>
# Work in progress
This is a custom firmware being developed for the [LilyGo T-Deck](https://www.lilygo.cc/products/t-deck), currently it is experimental & buggy.
This is a custom firmware being developed for the [LilyGo T-Deck](https://www.lilygo.cc/products/t-deck), currently it is experimental & buggy while we are in beta status.
I am terrible at C++ and still am trying to wrap my head around the language's logic, so most of this code has been shit out and written like a cowboy.
If you are familiar with or use [Internet Relay Chat](https://en.wikipedia.org/wiki/IRC), we have a team of developers working on this project in **#comms** on **[irc.supernets.org](irc://irc.supernets.org)**, join us if you have ideas, bugs, or want to get your hands dirty & develope this project with us.
The project started out with as IRC bridge for [Meshtastic](https://meshtastic.org/) over MQTT, just so I could say "we have IRC over radio", which later led to me modifying the official Meshtastic [firmware](https://github.com/meshtastic/firmware) to add extended features. I am deep in the rabbit hole of embedded development and am starting from scratch making this an entirely custom firmware.
Consider sponsoring our project, all the money goes towards motivation to develope on this, we also like buying T-Decks for people who want to learn about this stuff!
This is being developed in my free time as a fun project. It is no where near being useful.
![](./.screens/preview.png)
A compiled "release" will be done once I finish somoe fo the basic features, but feel free t compile this on your own to test it out!
# Previews
![](./.screens/preview1.png) ![](./.screens/preview2.png)
![](./.screens/lvgl.png)
# Flashing the Firmware
###### Using VS Code
@ -25,23 +21,26 @@ A compiled "release" will be done once I finish somoe fo the basic features, but
4. Hold down the trackball on the device, turn it on, and plug it in to the computer.
5. Press **F1** and select `PlatformIO: Build`
6. Press **F1** and select `PlatformIO: Upload`
7. Press the RST *(reset)* button ont he device.
7. Press the RST *(reset)* button on the device.
###### Using ESP Tool
1. Take the `firmware.bin` file from the release page and download it. *(no releases yet, we still building)*
1. Take the `firmware.bin` file from the release page and download it.
2. Install [esptool](https://pypi.org/project/esptool/): `pip install esptool`
3. Hold down the trackball on the device, turn it on, and plug it in to the computer.
4. Confirm the serial device in your `/dev` directory *(Your device will likely be `/dev/ttyAMC0` or `/dev/ttyUSB0`)*
5. Flash the device: `esptool.py --chip esp32-s3 --port /dev/ttyUSB0 --baud 115200 write_flash -z 0x1000 firmware.bin`
6. Press the RST *(reset)* button ont he device.
6. Press the RST *(reset)* button on the device.
# Connecting to WiFi
The device will scan for WiFi networks on boot. Once the list is displayed, you can scroll up and down the list with the "u" key for UP and the "d" key for down.
# Command & Control
###### Menu controls
On boot, if you press the `w` key, it will wipe all of the stored preferences.
# Commands
The device will scan for WiFi networks on boot. Once the list is displayed, you can scroll up and down the list with the `u` key for UP and the `d` key for down.
###### IRC commands
| Command | Description |
| --------------- | --------------------------- |
| `/debug` | Show hardware information |
| `/info` | Show hardware information |
| `/me <message>` | Send an ACTION message |
| `/nick <new>` | Change your NICK on IRC |
| `/raw <data>` | Send RAW data to the server |
@ -54,13 +53,20 @@ The device will scan for WiFi networks on boot. Once the list is displayed, you
# Roapmap
###### Device functionality
- [X] Screen timeout on inactivity *(default 30 seconds)*
- [ ] Keyboard backlight timeout on 10 seconds oof inactivity.
- [ ] Keyboard backlight timeout with screen timeout
- [ ] Trackball support
- [ ] Speaker support
- [X] Speaker support
- [X] Bootup sounds
- [X] IRC mention sounds
- [ ] GPS support
- [ ] Lora support
- [ ] BLE support
- [ ] SD card support
###### Features
- [X] LVGL used for enhanced UI
- [X] Wifi scanning & selection menu
- [ ] Saved wifi profiles
- [x] Saved wifi profiles
- [ ] Wifi Hotspot
- [ ] Notifcations Window *(All notifications will go here, from IRC, Gotify, Meshtastic, or anything)*
- [X] Status bar *(Time, Date, Notification, Wifi, and Battery)*
@ -70,33 +76,38 @@ The device will scan for WiFi networks on boot. Once the list is displayed, you
- [X] Serial debug logs
###### Applications
- [ ] Rubber Ducky
- [X] IRC Client
- [X] `/raw` command for IRC client to send raw data to the server
- [ ] Add scrolling backlog for IRC to see the last 200 messages
- [ ] Multi-buffer support *(`/join` & `/part` support with switching between buffers with `/0`, `/1`, `/2`, etc)* *(`/close` also for PM buffers or kicked from channels)*
- [ ] Status window for network to show RAW lines from the IRC server *(buffer 0)*
- [ ] Hilight monitor buffer
- [X] Hilight support *(so we can see when people mention our NICK)*
- [X] 99 color support
- [ ] `/pm` support *(it should open a buffer for pms)*
- [ ] NickServ support
- [ ] ChatGPT
- [ ] SSH Client
- [ ] Wardriving
- [ ] Evil Portal AP
- [ ] Local Network Probe *(Scans for devices on the wifi network you are connected to, add port scanning)*
- [ ] Gotify
- [ ] Meshtastic
- [ ] Gotify *(in progress)*
- [ ] Meshtastic *(in progress)*
- [ ] Spotify/Music player *(can we play audio throuigh Bluetoth headphones or the on-board speaker?)*
- [ ] Syslog *(All serial logs will be displayed here for on-device debugging)*
# Known issues
- Messages that exceed the screen width and wrap to the next line will throw off thje logic of calculating the max lines able to be displayed on the screen. Messages eventually go off screen.
- Input messages exceeding the screen width go off screen, and need to wrap and create a new input line, pushing up the backlog of messages.
- Some colors are not working when its only a background color or only contains a space
- Sometimes it will not connect to IRC even from reconnections, and you have to reboot and try again.
- Screen timeout can occur during an IRC reconnection and blck keyboard input
# Ideas
- Replace the `ESP32-S3FN16R8` with a `ESP32-S3-WROOM-1U` which has an iPex connector for an external WiFi antenna.
# Contributors
Join us in **#comms** on **[irc.supernets.org](irc://irc.supernets.org)** if you want to get your hands dirty.
# More screens..
# Previews
###### 99 Color support
![](./.screens/99colors.png)
###### Full ASCII support for PUMPERS
![](./.screens/ascii.png)
###### Support for /HUEG
![](./.screens/hueg.png)
___

Binary file not shown.

View File

@ -1,3 +1,24 @@
// Added by acidvegas to hide warnings during build
#ifdef TFT_MISO
#undef TFT_MISO
#endif
#ifdef TFT_MOSI
#undef TFT_MOSI
#endif
#ifdef TFT_CS
#undef TFT_CS
#endif
#ifdef TFT_DC
#undef TFT_DC
#endif
#ifdef TFT_RST
#undef TFT_RST
#endif
#ifdef TFT_BL
#undef TFT_BL
#endif
// ST7789 240 x 280 display with no chip select line
#define USER_SETUP_ID 210

View File

@ -22,7 +22,9 @@ board_build.partitions = default_16MB.csv
build_flags =
-DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1
-DDISABLE_ALL_LIBRARY_WARNINGS
lib_deps =
;mikalhart/TinyGPSPlus@^1.0.2
marian-craciunescu/ESP32Ping@^1.7.0
;sandeepmistry/LoRa
ArduinoWebsockets
Wireguard-ESP32
earlephilhower/ESP8266Audio
lib_extra_dirs = lib

721
src/Display.cpp Normal file
View File

@ -0,0 +1,721 @@
#include "bootScreen.h"
#include "Display.h"
#include "IRC.h"
#include "pins.h"
#include "Speaker.h"
#include "Storage.h"
#include "Utilities.h"
bool infoScreen = false;
bool configScreen = false;
bool screenOn = true;
const char* channel = "#comms";
unsigned long infoScreenStartTime = 0;
unsigned long configScreenStartTime = 0;
unsigned long lastStatusUpdateTime = 0;
unsigned long lastActivityTime = 0;
String inputBuffer = "";
std::vector<String> lines;
std::vector<bool> mentions;
std::map<String, uint32_t> nickColors;
TFT_eSPI tft = TFT_eSPI();
void addLine(String senderNick, String message, String type, bool mention, uint16_t errorColor, uint16_t reasonColor) {
if (type != "error" && nickColors.find(senderNick) == nickColors.end())
nickColors[senderNick] = generateRandomColor();
String formattedMessage;
if (type == "join") {
formattedMessage = "JOIN " + senderNick + " has joined " + String(channel);
} else if (type == "part") {
formattedMessage = "PART " + senderNick + " has EMO-QUIT " + String(channel);
} else if (type == "quit") {
formattedMessage = "QUIT " + senderNick;
} else if (type == "nick") {
int arrowPos = message.indexOf(" -> ");
String oldNick = senderNick;
String newNick = message.substring(arrowPos + 4);
if (nickColors.find(newNick) == nickColors.end()) {
nickColors[newNick] = generateRandomColor();
}
formattedMessage = "NICK " + oldNick + " -> " + newNick;
} else if (type == "kick") {
formattedMessage = "KICK " + senderNick + message;
} else if (type == "mode") {
formattedMessage = "MODE " + message;
tft.setTextColor(TFT_BLUE);
} else if (type == "action") {
formattedMessage = "* " + senderNick + " " + message;
} else if (type == "error") {
formattedMessage = "ERROR " + message;
senderNick = "ERROR";
} else {
formattedMessage = senderNick + ": " + message;
}
int linesRequired = calculateLinesRequired(formattedMessage);
while (lines.size() + linesRequired > MAX_LINES) {
lines.erase(lines.begin());
mentions.erase(mentions.begin());
}
if (type == "error") {
lines.push_back("ERROR " + message);
mentions.push_back(false);
} else {
lines.push_back(formattedMessage);
mentions.push_back(mention);
}
displayLines();
}
int calculateLinesRequired(String message) {
int linesRequired = 1;
int lineWidth = 0;
for (unsigned int i = 0; i < message.length(); i++) {
char c = message[i];
if (c == '\x03') {
if (i + 1 < message.length() && isdigit(message[i + 1])) {
i++;
if (i + 1 < message.length() && isdigit(message[i + 1]))
i++;
}
if (i + 1 < message.length() && message[i + 1] == ',' && isdigit(message[i + 2])) {
i += 2;
if (i + 1 < message.length() && isdigit(message[i + 1]))
i++;
}
} else if (c != '\x02' && c != '\x0F' && c != '\x1F') {
lineWidth += tft.textWidth(String(c));
if (lineWidth > SCREEN_WIDTH) {
linesRequired++;
lineWidth = tft.textWidth(String(c));
}
}
}
return linesRequired;
}
void displayCenteredText(String text) {
tft.fillScreen(TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.drawString(text, SCREEN_WIDTH / 2, (SCREEN_HEIGHT + STATUS_BAR_HEIGHT) / 2);
}
void displayInputLine() {
tft.fillRect(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT, SCREEN_WIDTH, INPUT_LINE_HEIGHT, TFT_BLACK);
tft.setCursor(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT);
tft.setTextColor(TFT_WHITE);
tft.setTextSize(1);
String displayInput = inputBuffer;
int displayWidth = tft.textWidth(displayInput);
int inputWidth = SCREEN_WIDTH - tft.textWidth("> ");
while (displayWidth > inputWidth) {
displayInput = displayInput.substring(1);
displayWidth = tft.textWidth(displayInput);
}
if (inputBuffer.length() >= 510)
tft.setTextColor(TFT_RED);
tft.print("> " + displayInput);
tft.setTextColor(TFT_WHITE);
}
void displayLines() {
tft.fillRect(0, STATUS_BAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - STATUS_BAR_HEIGHT - INPUT_LINE_HEIGHT, TFT_BLACK);
int cursorY = STATUS_BAR_HEIGHT;
int totalLinesHeight = 0;
std::vector<int> lineHeights;
for (const String& line : lines) {
int lineHeight = calculateLinesRequired(line) * CHAR_HEIGHT;
lineHeights.push_back(lineHeight);
totalLinesHeight += lineHeight;
}
while (totalLinesHeight > SCREEN_HEIGHT - STATUS_BAR_HEIGHT - INPUT_LINE_HEIGHT) {
totalLinesHeight -= lineHeights.front();
lines.erase(lines.begin());
mentions.erase(mentions.begin());
lineHeights.erase(lineHeights.begin());
}
for (size_t i = 0; i < lines.size(); ++i) {
const String& line = lines[i];
bool mention = mentions[i];
tft.setCursor(0, cursorY);
if (line.startsWith("JOIN ")) {
tft.setTextColor(TFT_GREEN);
tft.print("JOIN ");
int startIndex = 5;
int endIndex = line.indexOf(" has joined ");
String senderNick = line.substring(startIndex, endIndex);
tft.setTextColor(nickColors[senderNick]);
tft.print(senderNick);
tft.setTextColor(TFT_WHITE);
tft.print(" has joined ");
tft.setTextColor(TFT_CYAN);
tft.print(channel);
cursorY += CHAR_HEIGHT;
} else if (line.startsWith("PART ")) {
tft.setTextColor(TFT_RED);
tft.print("PART ");
int startIndex = 5;
int endIndex = line.indexOf(" has EMO-QUIT ");
String senderNick = line.substring(startIndex, endIndex);
tft.setTextColor(nickColors[senderNick]);
tft.print(senderNick);
tft.setTextColor(TFT_WHITE);
tft.print(" has EMO-QUIT ");
tft.setTextColor(TFT_CYAN);
tft.print(channel);
cursorY += CHAR_HEIGHT;
} else if (line.startsWith("QUIT ")) {
tft.setTextColor(TFT_RED);
tft.print("QUIT ");
String senderNick = line.substring(5);
tft.setTextColor(nickColors[senderNick]);
tft.print(senderNick);
cursorY += CHAR_HEIGHT;
} else if (line.startsWith("NICK ")) {
tft.setTextColor(TFT_BLUE);
tft.print("NICK ");
int startIndex = 5;
int endIndex = line.indexOf(" -> ");
String oldNick = line.substring(startIndex, endIndex);
String newNick = line.substring(endIndex + 4);
tft.setTextColor(nickColors[oldNick]);
tft.print(oldNick);
tft.setTextColor(TFT_WHITE);
tft.print(" -> ");
tft.setTextColor(nickColors[newNick]);
tft.print(newNick);
cursorY += CHAR_HEIGHT;
} else if (line.startsWith("KICK ")) {
tft.setTextColor(TFT_RED);
tft.print("KICK ");
int startIndex = 5;
int endIndex = line.indexOf(" by ");
String kickedNick = line.substring(startIndex, endIndex);
String kicker = line.substring(endIndex + 4);
tft.setTextColor(nickColors[kickedNick]);
tft.print(kickedNick);
tft.setTextColor(TFT_WHITE);
tft.print(" by ");
tft.setTextColor(nickColors[kicker]);
tft.print(kicker);
cursorY += CHAR_HEIGHT;
} else if (line.startsWith("MODE ")) {
tft.setTextColor(TFT_BLUE);
tft.print("MODE ");
String modeChange = line.substring(5);
tft.setTextColor(TFT_WHITE);
tft.print(modeChange);
cursorY += CHAR_HEIGHT;
} else if (line.startsWith("ERROR ")) {
tft.setTextColor(TFT_RED);
tft.print("ERROR ");
String errorReason = line.substring(6);
tft.setTextColor(TFT_DARKGREY);
tft.print(errorReason);
cursorY += CHAR_HEIGHT;
} else if (line.startsWith("* ")) {
tft.setTextColor(TFT_MAGENTA);
tft.print("* ");
int startIndex = 2;
int endIndex = line.indexOf(' ', startIndex);
String senderNick = line.substring(startIndex, endIndex);
String actionMessage = line.substring(endIndex + 1);
tft.setTextColor(nickColors[senderNick]);
tft.print(senderNick);
tft.setTextColor(TFT_MAGENTA);
tft.print(" " + actionMessage);
cursorY += CHAR_HEIGHT;
} else {
int colonIndex = line.indexOf(':');
String senderNick = line.substring(0, colonIndex);
String message = line.substring(colonIndex + 1);
tft.setTextColor(nickColors[senderNick]);
tft.print(senderNick);
tft.setTextColor(TFT_WHITE);
cursorY = renderFormattedMessage(":" + message, cursorY, CHAR_HEIGHT, mention);
}
}
displayInputLine();
}
void displayXBM() {
tft.fillScreen(TFT_BLACK);
int x = (SCREEN_WIDTH - logo_width) / 2;
int y = (SCREEN_HEIGHT - logo_height) / 2;
tft.drawXBitmap(x, y, logo_bits, logo_width, logo_height, TFT_GREEN);
unsigned long startTime = millis();
bool wipeInitiated = false;
while (millis() - startTime < 3000) {
if (getKeyboardInput() == 'w') {
wipeNVS();
tft.fillScreen(TFT_BLACK);
displayCenteredText("NVS WIPED");
delay(2000);
wipeInitiated = true;
break;
}
}
}
uint32_t generateRandomColor() {
return tft.color565(random(0, 255), random(0, 255), random(0, 255));
}
uint16_t getColorFromCode(int colorCode) {
switch (colorCode) {
case 0: return TFT_WHITE;
case 1: return TFT_BLACK;
case 2: return tft.color565(0, 0, 128); // Dark Blue (Navy)
case 3: return TFT_GREEN;
case 4: return TFT_RED;
case 5: return tft.color565(128, 0, 0); // Brown (Maroon)
case 6: return tft.color565(128, 0, 128); // Purple
case 7: return tft.color565(255, 165, 0); // Orange
case 8: return TFT_YELLOW;
case 9: return tft.color565(144, 238, 144); // Light Green
case 10: return tft.color565(0, 255, 255); // Cyan (Light Blue)
case 11: return tft.color565(224, 255, 255); // Light Cyan (Aqua)
case 12: return TFT_BLUE;
case 13: return tft.color565(255, 192, 203); // Pink (Light Purple)
case 14: return tft.color565(128, 128, 128); // Grey
case 15: return tft.color565(211, 211, 211); // Light Grey
case 16: return 0x4000;
case 17: return 0x4100;
case 18: return 0x4220;
case 19: return 0x3220;
case 20: return 0x0220;
case 21: return 0x0225;
case 22: return 0x0228;
case 23: return 0x0128;
case 24: return 0x0008;
case 25: return 0x2808;
case 26: return 0x4008;
case 27: return 0x4005;
case 28: return 0x7000;
case 29: return 0x71C0;
case 30: return 0x73A0;
case 31: return 0x53A0;
case 32: return 0x03A0;
case 33: return 0x03A9;
case 34: return 0x03AE;
case 35: return 0x020E;
case 36: return 0x000E;
case 37: return 0x480E;
case 38: return 0x700E;
case 39: return 0x7008;
case 40: return 0xB000;
case 41: return 0xB300;
case 42: return 0xB5A0;
case 43: return 0x7DA0;
case 44: return 0x05A0;
case 45: return 0x05AE;
case 46: return 0x05B6;
case 47: return 0x0316;
case 48: return 0x0016;
case 49: return 0x7016;
case 50: return 0xB016;
case 51: return 0xB00D;
case 52: return 0xF800;
case 53: return 0xFC60;
case 54: return 0xFFE0;
case 55: return 0xB7E0;
case 56: return 0x07E0;
case 57: return 0x07F4;
case 58: return 0x07FF;
case 59: return 0x047F;
case 60: return 0x001F;
case 61: return 0xA01F;
case 62: return 0xF81F;
case 63: return 0xF813;
case 64: return 0xFACB;
case 65: return 0xFDAB;
case 66: return 0xFFEE;
case 67: return 0xCFEC;
case 68: return 0x6FED;
case 69: return 0x67F9;
case 70: return 0x6FFF;
case 71: return 0x5DBF;
case 72: return 0x5ADF;
case 73: return 0xC2DF;
case 74: return 0xFB3F;
case 75: return 0xFAD7;
case 76: return 0xFCF3;
case 77: return 0xFE93;
case 78: return 0xFFF3;
case 79: return 0xE7F3;
case 80: return 0x9FF3;
case 81: return 0x9FFB;
case 82: return 0x9FFF;
case 83: return 0x9E9F;
case 84: return 0x9CFF;
case 85: return 0xDCFF;
case 86: return 0xFCFF;
case 87: return 0xFCBA;
case 88: return 0x0000;
case 89: return 0x1082;
case 90: return 0x2945;
case 91: return 0x31A6;
case 92: return 0x4A69;
case 93: return 0x632C;
case 94: return 0x8410;
case 95: return 0x9CF3;
case 96: return 0xBDF7;
case 97: return 0xE71C;
case 98: return 0xFFFF;
default: return TFT_WHITE;
}
}
uint16_t getColorFromPercentage(int percentage) {
if (percentage > 75) return TFT_GREEN;
else if (percentage > 50) return TFT_YELLOW;
else if (percentage > 25) return TFT_ORANGE;
else return TFT_RED;
}
void handleKeyboardInput(char key) {
lastActivityTime = millis();
if (!screenOn) {
turnOnScreen();
screenOn = true;
return;
}
static bool altPressed = false;
if (key == '\n' || key == '\r') {
if (inputBuffer.startsWith("/nick ")) {
String newNick = inputBuffer.substring(6);
sendIRC("NICK " + newNick);
inputBuffer = "";
} else if (inputBuffer.startsWith("/config")) {
configScreen = true;
configScreenStartTime = millis();
inputBuffer = "";
} else if (inputBuffer.startsWith("/info")) {
infoScreen = true;
infoScreenStartTime = millis();
tft.fillScreen(TFT_BLACK);
printDeviceInfo();
inputBuffer = "";
} else if (inputBuffer.startsWith("/raw ")) {
String rawCommand = inputBuffer.substring(5);
sendIRC(rawCommand);
} else if (inputBuffer.startsWith("/me ")) {
String actionMessage = inputBuffer.substring(4);
sendIRC("PRIVMSG " + String(channel) + " :\001ACTION " + actionMessage + "\001");
addLine(irc_nickname, actionMessage, "action");
inputBuffer = "";
} else {
sendIRC("PRIVMSG " + String(channel) + " :" + inputBuffer);
addLine(irc_nickname, inputBuffer, "message");
}
inputBuffer = "";
displayInputLine();
} else if (key == '\b') {
if (inputBuffer.length() > 0) {
inputBuffer.remove(inputBuffer.length() - 1);
displayInputLine();
}
} else {
inputBuffer += key;
displayInputLine();
}
}
void parseAndDisplay(String line) {
int firstSpace = line.indexOf(' ');
int secondSpace = line.indexOf(' ', firstSpace + 1);
if (firstSpace != -1 && secondSpace != -1) {
String command = line.substring(firstSpace + 1, secondSpace);
if (command == "PRIVMSG") {
int thirdSpace = line.indexOf(' ', secondSpace + 1);
String target = line.substring(secondSpace + 1, thirdSpace);
if (target == String(channel)) {
int colonPos = line.indexOf(':', thirdSpace);
String message = line.substring(colonPos + 1);
String senderNick = line.substring(1, line.indexOf('!'));
bool mention = message.indexOf(irc_nickname) != -1;
if (mention)
playNotificationSound();
if (message.startsWith(String("\x01") + "ACTION ") && message.endsWith("\x01")) {
String actionMessage = message.substring(8, message.length() - 1);
addLine(senderNick, actionMessage, "action");
} else {
addLine(senderNick, message, "message", mention);
}
}
} else if (command == "JOIN" && line.indexOf(channel) != -1) {
String senderNick = line.substring(1, line.indexOf('!'));
addLine(senderNick, " has joined " + String(channel), "join");
} else if (command == "PART" && line.indexOf(channel) != -1) {
String senderNick = line.substring(1, line.indexOf('!'));
addLine(senderNick, " has EMO-QUIT " + String(channel), "part");
} else if (command == "QUIT") {
String senderNick = line.substring(1, line.indexOf('!'));
addLine(senderNick, "", "quit");
} else if (command == "NICK") {
String prefix = line.startsWith(":") ? line.substring(1, firstSpace) : "";
String newNick = line.substring(line.lastIndexOf(':') + 1);
if (prefix.indexOf('!') == -1) {
addLine(irc_nickname, " -> " + newNick, "nick");
irc_nickname = newNick;
} else {
String oldNick = prefix.substring(0, prefix.indexOf('!'));
addLine(oldNick, " -> " + newNick, "nick");
if (oldNick == irc_nickname) {
irc_nickname = newNick;
}
}
} else if (command == "KICK") {
int thirdSpace = line.indexOf(' ', secondSpace + 1);
int fourthSpace = line.indexOf(' ', thirdSpace + 1);
String kicker = line.substring(1, line.indexOf('!'));
String kicked = line.substring(thirdSpace + 1, fourthSpace);
addLine(kicked, " by " + kicker, "kick");
} else if (command == "MODE") {
String modeChange = line.substring(secondSpace + 1);
addLine("", modeChange, "mode");
} else if (command == "432") {
addLine("ERROR", "ERR_ERRONEUSNICKNAME", "error", TFT_RED, TFT_DARKGREY);
} else if (command == "433") {
addLine("ERROR", "ERR_NICKNAMEINUSE", "error", TFT_RED, TFT_DARKGREY);
irc_nickname = "ACID_" + String(random(1000, 9999));
sendIRC("NICK " + irc_nickname);
}
}
}
int renderFormattedMessage(String message, int cursorY, int lineHeight, bool highlightNick) {
uint16_t fgColor = TFT_WHITE;
uint16_t bgColor = TFT_BLACK;
bool bold = false;
bool underline = false;
bool nickHighlighted = false;
int nickPos = -1;
if (highlightNick) {
nickPos = message.indexOf(irc_nickname);
}
for (unsigned int i = 0; i < message.length(); i++) {
char c = message[i];
if (c == '\x02') {
bold = !bold;
} else if (c == '\x1F') {
underline = !underline;
} else if (c == '\x03') {
fgColor = TFT_WHITE;
bgColor = TFT_BLACK;
if (i + 1 < message.length() && (isdigit(message[i + 1]) || message[i + 1] == ',')) {
int colorCode = -1;
if (isdigit(message[i + 1])) {
colorCode = message[++i] - '0';
if (i + 1 < message.length() && isdigit(message[i + 1]))
colorCode = colorCode * 10 + (message[++i] - '0');
}
if (colorCode != -1)
fgColor = getColorFromCode(colorCode);
if (i + 1 < message.length() && message[i + 1] == ',') {
i++;
int bgColorCode = -1;
if (isdigit(message[i + 1])) {
bgColorCode = message[++i] - '0';
if (i + 1 < message.length() && isdigit(message[i + 1]))
bgColorCode = bgColorCode * 10 + (message[++i] - '0');
}
if (bgColorCode != -1)
bgColor = getColorFromCode(bgColorCode);
}
tft.setTextColor(fgColor, bgColor);
}
} else if (c == '\x0F') {
fgColor = TFT_WHITE;
bgColor = TFT_BLACK;
bold = false;
underline = false;
tft.setTextColor(fgColor, bgColor);
tft.setTextFont(1);
} else {
if (highlightNick && !nickHighlighted && nickPos != -1 && i == nickPos) {
tft.setTextColor(TFT_YELLOW, bgColor);
for (char nc : irc_nickname) {
tft.print(nc);
i++;
}
i--;
tft.setTextColor(TFT_WHITE, bgColor);
nickHighlighted = true;
} else {
if (tft.getCursorX() + tft.textWidth(String(c)) > SCREEN_WIDTH) {
cursorY += lineHeight;
tft.setCursor(0, cursorY);
}
if (c == ' ') {
int spaceWidth = tft.textWidth(" ");
tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
} else {
tft.fillRect(tft.getCursorX(), tft.getCursorY(), tft.textWidth(String(c)), lineHeight, bgColor);
tft.setTextColor(fgColor, bgColor);
tft.print(c);
}
}
}
}
if (message.endsWith(" ")) {
int trailingSpaces = 0;
for (int i = message.length() - 1; i >= 0 && message[i] == ' '; i--) {
trailingSpaces++;
}
for (int i = 0; i < trailingSpaces; i++) {
int spaceWidth = tft.textWidth(" ");
tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
}
}
cursorY += lineHeight;
return cursorY;
}
void setupScreen() {
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH);
setBrightness(8);
tft.begin();
tft.setRotation(1);
tft.invertDisplay(1);
Serial.println("TFT initialized");
}
void turnOffScreen() {
Serial.println("Screen turned off");
tft.writecommand(TFT_DISPOFF);
tft.writecommand(TFT_SLPIN);
digitalWrite(TFT_BL, LOW);
screenOn = false;
}
void turnOnScreen() {
Serial.println("Screen turned on");
digitalWrite(TFT_BL, HIGH);
tft.writecommand(TFT_SLPOUT);
tft.writecommand(TFT_DISPON);
screenOn = true;
}
void updateStatusBar() {
Serial.println("Updating status bar...");
uint16_t darkerGrey = tft.color565(25, 25, 25);
tft.fillRect(0, 0, SCREEN_WIDTH, STATUS_BAR_HEIGHT, darkerGrey);
struct tm timeinfo;
char timeStr[9];
if (!getLocalTime(&timeinfo)) {
sprintf(timeStr, "12:00 AM");
} else {
int hour = timeinfo.tm_hour;
char ampm[] = "AM";
if (hour == 0) {
hour = 12;
} else if (hour >= 12) {
if (hour > 12)
hour -= 12;
strcpy(ampm, "PM");
}
sprintf(timeStr, "%02d:%02d %s", hour, timeinfo.tm_min, ampm);
}
tft.setTextDatum(ML_DATUM);
tft.setTextColor(TFT_WHITE, darkerGrey);
tft.drawString(timeStr, 0, STATUS_BAR_HEIGHT / 2);
char wifiStr[15];
int wifiSignal = 0;
if (WiFi.status() != WL_CONNECTED) {
sprintf(wifiStr, "WiFi: N/A");
tft.setTextColor(TFT_PINK, darkerGrey);
tft.setTextDatum(MR_DATUM);
tft.drawString(wifiStr, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
} else {
int32_t rssi = WiFi.RSSI();
if (rssi > -50) wifiSignal = 100;
else if (rssi > -60) wifiSignal = 80;
else if (rssi > -70) wifiSignal = 60;
else if (rssi > -80) wifiSignal = 40;
else if (rssi > -90) wifiSignal = 20;
else wifiSignal = 0;
sprintf(wifiStr, "WiFi: %d%%", wifiSignal);
tft.setTextDatum(MR_DATUM);
tft.setTextColor(TFT_PINK, darkerGrey);
tft.drawString("WiFi:", SCREEN_WIDTH - 120, STATUS_BAR_HEIGHT / 2);
tft.setTextColor(getColorFromPercentage(wifiSignal), darkerGrey);
tft.drawString(wifiStr + 6, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
}
int batteryLevel = BL.getBatteryChargeLevel();
char batteryStr[15];
sprintf(batteryStr, "Batt: %d%%", batteryLevel);
tft.setTextDatum(MR_DATUM);
tft.setTextColor(TFT_CYAN, darkerGrey);
tft.drawString("Batt:", SCREEN_WIDTH - 40, STATUS_BAR_HEIGHT / 2);
tft.setTextColor(getColorFromPercentage(batteryLevel), darkerGrey);
tft.drawString(batteryStr + 5, SCREEN_WIDTH - 5, STATUS_BAR_HEIGHT / 2);
}

47
src/Display.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include <map>
#include <vector>
#include <time.h>
#include <TFT_eSPI.h>
#include <WiFi.h>
#define CHAR_HEIGHT 10
#define LINE_SPACING 0
#define STATUS_BAR_HEIGHT 10
#define INPUT_LINE_HEIGHT (CHAR_HEIGHT + LINE_SPACING)
#define MAX_LINES ((SCREEN_HEIGHT - INPUT_LINE_HEIGHT - STATUS_BAR_HEIGHT) / (CHAR_HEIGHT + LINE_SPACING))
extern bool infoScreen;
extern bool configScreen;
extern bool screenOn;
extern const char* channel;
extern unsigned long infoScreenStartTime;
extern unsigned long configScreenStartTime;
extern unsigned long lastStatusUpdateTime;
extern unsigned long lastActivityTime;
extern String inputBuffer;
extern std::vector<String> lines;
extern std::vector<bool> mentions;
extern std::map<String, uint32_t> nickColors;
extern TFT_eSPI tft;
void addLine(String senderNick, String message, String type, bool mention = false, uint16_t errorColor = TFT_WHITE, uint16_t reasonColor = TFT_WHITE);
int calculateLinesRequired(String message);
void displayCenteredText(String text);
void displayInputLine();
void displayLines();
void displayXBM();
uint32_t generateRandomColor();
uint16_t getColorFromCode(int colorCode);
uint16_t getColorFromPercentage(int percentage);
void handleKeyboardInput(char key);
void parseAndDisplay(String line);
int renderFormattedMessage(String message, int cursorY, int lineHeight, bool highlightNick = false);
void setupScreen();
void turnOffScreen();
void turnOnScreen();
void updateStatusBar();

47
src/Gotify.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "Gotify.h"
WebsocketsClient gotifyClient;
const char* gotify_server = "ws://your.gotify.server.com/stream"; // Use ws:// or wss:// based on your server configuration
const char* gotify_token = "your_gotify_app_token";
unsigned long lastAttemptTime = 0;
const unsigned long reconnectInterval = 60000; // 1 minute
void onMessageCallback(WebsocketsMessage message) {
Serial.println("Gotify Notification:");
Serial.println(message.data());
playNotificationSound();
}
void connectToGotify() {
String url = String(gotify_server) + "?token=" + gotify_token;
gotifyClient.onMessage(onMessageCallback);
gotifyClient.connect(url);
if (gotifyClient.available())
Serial.println("Connected to Gotify WebSocket");
else
Serial.println("Failed to connect to Gotify WebSocket");
}
void loopGotifyWebSocket() {
while (true) {
if (!gotifyClient.available()) {
unsigned long currentTime = millis();
if (currentTime - lastAttemptTime > reconnectInterval) {
Serial.println("Attempting to reconnect to Gotify WebSocket...");
lastAttemptTime = currentTime;
connectToGotify();
}
} else {
gotifyClient.poll();
}
delay(10);
}
}

17
src/Gotify.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <ArduinoWebsockets.h>
#include "Speaker.h"
using namespace websockets;
extern WebsocketsClient gotifyClient;
extern const char* gotify_server;
extern const char* gotify_token;
extern unsigned long lastAttemptTime;
extern const unsigned long reconnectInterval;
void onMessageCallback(WebsocketsMessage message);
void connectToGotify();
void loopGotifyWebSocket();

81
src/IRC.cpp Normal file
View File

@ -0,0 +1,81 @@
#include "IRC.h"
unsigned long joinChannelTime = 0;
bool readyToJoinChannel = false;
WiFiClient* client;
void action(String target, String message) {
sendIRC("PRIVMSG " + String(target) + " :\001ACTION " + message + "\001");
}
bool connectToIRC() {
if (irc_tls) {
Serial.println("Connecting to IRC with TLS: " + String(irc_server) + ":" + String(irc_port));
client = new WiFiClientSecure();
static_cast<WiFiClientSecure*>(client)->setInsecure();
return static_cast<WiFiClientSecure*>(client)->connect(irc_server.c_str(), irc_port);
} else {
Serial.println("Connecting to IRC: " + String(irc_server) + ":" + String(irc_port));
client = new WiFiClient();
return client->connect(irc_server.c_str(), irc_port);
}
}
void handleIRC() {
while (client->available()) {
String line = client->readStringUntil('\n');
if (line.length() > 512) {
Serial.println("WARNING: IRC line length exceeds 512 characters!");
line = line.substring(0, 512);
}
Serial.println("IRC: " + line);
int firstSpace = line.indexOf(' ');
int secondSpace = line.indexOf(' ', firstSpace + 1);
if (firstSpace != -1 && secondSpace != -1) {
String prefix = line.substring(0, firstSpace);
String command = line.substring(firstSpace + 1, secondSpace);
if (command == "001") {
joinChannelTime = millis() + 2500;
readyToJoinChannel = true;
}
}
if (line.startsWith("PING")) {
String pingResponse = "PONG " + line.substring(line.indexOf(' ') + 1);
sendIRC(pingResponse);
} else {
parseAndDisplay(line);
lastActivityTime = millis();
}
}
}
void sendIRC(String command) {
if (command.length() > 510) {
Serial.println("Failed to send: Command too long");
return;
}
if (client->connected()) {
if (client->println(command)) {
Serial.println("IRC: >>> " + command);
} else {
Serial.println("Failed to send: " + command);
}
} else {
Serial.println("Failed to send: Not connected to IRC");
}
}

15
src/IRC.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <WiFiClientSecure.h>
#include "Display.h"
#include "Storage.h"
extern WiFiClient* client;
extern unsigned long joinChannelTime;
extern bool readyToJoinChannel;
void action(String target, String message);
bool connectToIRC();
void handleIRC();
void sendIRC(String command);

125
src/Lora.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "Lora.h"
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
void recvLoop() {
String recv;
while (true) {
if (radio.available()) {
int state = radio.readData(recv);
if (state == RADIOLIB_ERR_NONE) {
playNotificationSound();
Serial.print(F("[RADIO] Received packet!"));
Serial.print(F(" Data:"));
Serial.print(recv);
Serial.print(F(" RSSI:"));
Serial.print(radio.getRSSI());
Serial.print(F(" dBm"));
Serial.print(F(" SNR:"));
Serial.print(radio.getSNR());
Serial.println(F(" dB"));
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
Serial.println(F("CRC error!"));
} else {
Serial.print(F("Failed, code "));
Serial.println(state);
}
} else {
Serial.println(F("Radio became unavailable!"));
break;
}
}
}
bool setupRadio() {
pinMode(RADIO_CS_PIN, OUTPUT);
digitalWrite(RADIO_CS_PIN, HIGH);
int state = radio.begin(RADIO_FREQ);
if (state == RADIOLIB_ERR_NONE) {
Serial.println("Start Radio success!");
} else {
Serial.print("Start Radio failed, code: ");
Serial.println(state);
return false;
}
if (radio.setFrequency(RADIO_FREQ) == RADIOLIB_ERR_INVALID_FREQUENCY) {
Serial.println(F("Selected frequency is invalid for this module!"));
return false;
}
if (radio.setBandwidth(125.0) == RADIOLIB_ERR_INVALID_BANDWIDTH) {
Serial.println(F("Selected bandwidth is invalid for this module!"));
return false;
}
if (radio.setSpreadingFactor(10) == RADIOLIB_ERR_INVALID_SPREADING_FACTOR) {
Serial.println(F("Selected spreading factor is invalid for this module!"));
return false;
}
if (radio.setCodingRate(6) == RADIOLIB_ERR_INVALID_CODING_RATE) {
Serial.println(F("Selected coding rate is invalid for this module!"));
return false;
}
if (radio.setSyncWord(0xAB) != RADIOLIB_ERR_NONE) {
Serial.println(F("Unable to set sync word!"));
return false;
}
// Set output power to 17 dBm (accepted range is -17 - 22 dBm)
if (radio.setOutputPower(17) == RADIOLIB_ERR_INVALID_OUTPUT_POWER) {
Serial.println(F("Selected output power is invalid for this module!"));
return false;
}
// Set over current protection limit to 140 mA (accepted range is 45 - 140 mA)
if (radio.setCurrentLimit(140) == RADIOLIB_ERR_INVALID_CURRENT_LIMIT) {
Serial.println(F("Selected current limit is invalid for this module!"));
return false;
}
// Set LoRa preamble length to 15 symbols (accepted range is 0 - 65535)
if (radio.setPreambleLength(15) == RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH) {
Serial.println(F("Selected preamble length is invalid for this module!"));
return false;
}
if (radio.setCRC(false) == RADIOLIB_ERR_INVALID_CRC_CONFIGURATION) {
Serial.println(F("Selected CRC is invalid for this module!"));
return false;
}
return true;
}
bool transmit() {
int state = radio.transmit("Hello World!");
if (state == RADIOLIB_ERR_NONE) {
Serial.println(F("Radio transmission successful!"));
Serial.print(F("[SX1262] Datarate:\t"));
Serial.print(radio.getDataRate());
Serial.println(F(" bps"));
return true;
} else if (state == RADIOLIB_ERR_PACKET_TOO_LONG) {
Serial.println(F("Radio packet too long")); // 256 bytes is the maximum packet length
return false;
} else if (state == RADIOLIB_ERR_TX_TIMEOUT) {
Serial.println(F("Radio timeout"));
return false;
} else {
Serial.print(F("Radio error failed, code "));
Serial.println(state);
return false;
}
}

12
src/Lora.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <RadioLib.h>
#include "pins.h"
#include "Speaker.h"
extern SX1262 radio;
void recvLoop();
bool setupRadio();
bool transmit();

261
src/Network.cpp Normal file
View File

@ -0,0 +1,261 @@
#include "Network.h"
std::vector<WiFiNetwork> wifiNetworks;
int selectedNetworkIndex = 0;
WireGuard wg;
void connectToWiFi(String ssid, String password) {
wifiNetworks.clear();
Serial.println("Connecting to WiFi network: " + ssid);
WiFi.begin(ssid.c_str(), password.c_str());
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
displayCenteredText("CONNECTING TO " + ssid);
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
displayCenteredText("CONNECTED TO " + ssid);
updateTimeFromNTP();
preferences.begin("config", false);
preferences.putString("wifi_ssid", ssid);
preferences.putString("wifi_password", password);
preferences.end();
Serial.println("Stored WiFi credentials updated");
wifi_ssid = ssid;
wifi_password = password;
} else {
Serial.println("Failed to connect to WiFi network: " + ssid);
displayCenteredText("WIFI CONNECTION FAILED");
preferences.begin("config", false);
preferences.putString("wifi_ssid", "");
preferences.putString("wifi_password", "");
preferences.end();
Serial.println("Stored WiFi credentials removed");
wifi_ssid = "";
wifi_password = "";
scanWiFiNetworks();
}
}
void displayPasswordInputLine() {
tft.fillRect(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT, SCREEN_WIDTH, INPUT_LINE_HEIGHT, TFT_BLACK);
tft.setCursor(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT);
tft.setTextColor(TFT_WHITE);
tft.setTextSize(1);
tft.print("> " + inputBuffer);
}
void displayWiFiNetworks() {
tft.fillRect(0, STATUS_BAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - STATUS_BAR_HEIGHT, TFT_BLACK);
tft.setTextSize(1);
tft.setTextColor(TFT_CYAN);
tft.setCursor(0, STATUS_BAR_HEIGHT);
tft.printf("# CH RSSI ENC SSID\n");
tft.setTextColor(TFT_WHITE);
int maxDisplayedLines = (SCREEN_HEIGHT - STATUS_BAR_HEIGHT - CHAR_HEIGHT) / (CHAR_HEIGHT + LINE_SPACING);
int startIdx = selectedNetworkIndex >= maxDisplayedLines ? selectedNetworkIndex - maxDisplayedLines + 1 : 0;
for (int i = startIdx; i < wifiNetworks.size() && i < startIdx + maxDisplayedLines; ++i) {
displayWiFiNetwork(i, i - startIdx + 1);
}
}
void displayWiFiNetwork(int index, int displayIndex) {
int y = STATUS_BAR_HEIGHT + displayIndex * (CHAR_HEIGHT + LINE_SPACING);
tft.setCursor(0, y);
if (index == selectedNetworkIndex)
tft.setTextColor(TFT_GREEN, TFT_BLACK);
else
tft.setTextColor(TFT_WHITE, TFT_BLACK);
WiFiNetwork net = wifiNetworks[index];
uint16_t rssiColor = getColorFromPercentage((int32_t)net.rssi);
tft.printf("%-2d %-3d ", net.index, net.channel);
tft.setTextColor(rssiColor, TFT_BLACK);
tft.printf("%-5d ", net.rssi);
tft.setTextColor(index == selectedNetworkIndex ? TFT_GREEN : TFT_WHITE, TFT_BLACK);
tft.printf("%-8s %s", net.encryption.c_str(), net.ssid.c_str());
}
String getEncryptionType(wifi_auth_mode_t encryptionType) {
switch (encryptionType) {
case (WIFI_AUTH_OPEN):
return "Open";
case (WIFI_AUTH_WEP):
return "WEP";
case (WIFI_AUTH_WPA_PSK):
return "WPA_PSK";
case (WIFI_AUTH_WPA2_PSK):
return "WPA2_PSK";
case (WIFI_AUTH_WPA_WPA2_PSK):
return "WPA_WPA2_PSK";
case (WIFI_AUTH_WPA2_ENTERPRISE):
return "WPA2_ENTERPRISE";
default:
return "Unknown";
}
}
void handlePasswordInput(char key) {
if (key == '\n' || key == '\r') {
wifi_password = inputBuffer;
inputBuffer = "";
connectToWiFi(wifi_ssid, wifi_password);
} else if (key == '\b') {
if (inputBuffer.length() > 0) {
inputBuffer.remove(inputBuffer.length() - 1);
displayPasswordInputLine();
}
} else if (inputBuffer.length() < 63) {
inputBuffer += key;
displayPasswordInputLine();
}
}
void handleWiFiSelection(char key) {
if (key == 'u') {
updateSelectedNetwork(-1);
} else if (key == 'd') {
updateSelectedNetwork(1);
} else if (key == '\n' || key == '\r') {
wifi_ssid = wifiNetworks[selectedNetworkIndex].ssid;
if (wifiNetworks[selectedNetworkIndex].encryption == "Secured") {
inputBuffer = "";
tft.fillScreen(TFT_BLACK);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE);
tft.setCursor(0, STATUS_BAR_HEIGHT);
tft.println("ENTER PASSWORD:");
displayPasswordInputLine();
while (true) {
char incoming = getKeyboardInput();
if (incoming != 0) {
handlePasswordInput(incoming);
if (incoming == '\n' || incoming == '\r')
break;
}
}
} else {
wifi_password = "";
connectToWiFi(wifi_ssid, wifi_password);
}
}
}
void initializeNetwork() {
WiFi.mode(WIFI_STA);
WiFi.setHostname("acid-drop"); // Turn into a preference
WiFi.onEvent(WiFiEvent);
randomizeMacAddress();
}
void randomizeMacAddress() {
Serial.println("Current MAC Address: " + WiFi.macAddress());
uint8_t new_mac[6];
for (int i = 0; i < 6; ++i)
new_mac[i] = random(0x00, 0xFF);
if (esp_wifi_set_mac(WIFI_IF_STA, new_mac) == ESP_OK)
Serial.println("New MAC Address: " + WiFi.macAddress());
else
Serial.println("Failed to set new MAC Address");
}
void scanWiFiNetworks() {
Serial.println("Scanning for WiFi networks...");
displayCenteredText("SCANNING WIFI");
delay(1000);
wifiNetworks.clear();
int n = WiFi.scanNetworks();
if (n == 0) {
Serial.println("No WiFi networks found");
displayCenteredText("NO NETWORKS FOUND");
delay(2000);
scanWiFiNetworks();
return;
}
Serial.print("Total number of networks found: ");
Serial.println(n);
for (int i = 0; i < n && i < 100; i++) {
WiFiNetwork net;
net.index = i + 1;
net.channel = WiFi.channel(i);
net.rssi = WiFi.RSSI(i);
net.encryption = (WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? "Open" : "Secured";
String ssid = WiFi.SSID(i).substring(0, 32);
net.ssid = ssid;
wifiNetworks.push_back(net);
}
displayWiFiNetworks();
}
void updateSelectedNetwork(int delta) {
int newIndex = selectedNetworkIndex + delta;
if (newIndex >= 0 && newIndex < wifiNetworks.size()) {
selectedNetworkIndex = newIndex;
displayWiFiNetworks();
}
}
void wgConnect(const IPAddress& localIp, const char* privateKey, const char* endpointAddress, const char* publicKey, uint16_t endpointPort) {
wg.begin(localIp, privateKey, endpointAddress, publicKey, endpointPort);
}
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) {
case SYSTEM_EVENT_STA_CONNECTED:
Serial.println("WiFi connected");
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("WiFi disconnected");
break;
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("WiFi got IP address: " + WiFi.localIP().toString());
break;
case SYSTEM_EVENT_STA_LOST_IP:
Serial.println("WiFi lost IP address");
break;
default:
break;
}
}

37
src/Network.h Normal file
View File

@ -0,0 +1,37 @@
#pragma once
#include <vector>
#include <esp_wifi.h>
#include <WireGuard-ESP32.h>
#include "Display.h"
#include "Storage.h"
#include "Utilities.h"
#include "WiFi.h"
struct WiFiNetwork {
int index;
int channel;
int rssi;
String encryption;
String ssid;
};
extern std::vector<WiFiNetwork> wifiNetworks;
extern int selectedNetworkIndex;
extern WireGuard wg;
void connectToWiFi(String ssid, String password);
void displayPasswordInputLine();
void displayWiFiNetworks();
void displayWiFiNetwork(int index, int displayIndex);
String getEncryptionType(wifi_auth_mode_t encryptionType);
void handlePasswordInput(char key);
void handleWiFiSelection(char key);
void initializeNetwork();
void randomizeMacAddress();
void scanWiFiNetworks();
void updateSelectedNetwork(int delta);
void wgConnect(const IPAddress& localIp, const char* privateKey, const char* endpointAddress, const char* publicKey, uint16_t endpointPort);
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);

75
src/Speaker.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "Speaker.h"
void playNotificationSound() {
playTone(1000, 50);
delay(100);
playTone(1500, 50);
delay(100);
playTone(2000, 50);
delay(100);
playTone(500, 50);
}
void playRTTTL(const char* rtttl) {
static AudioGeneratorRTTTL *rtttlGenerator = new AudioGeneratorRTTTL();
static AudioOutputI2S *audioOutput = new AudioOutputI2S();
static AudioFileSourcePROGMEM *fileSource = new AudioFileSourcePROGMEM(rtttl, strlen(rtttl));
audioOutput->begin();
rtttlGenerator->begin(fileSource, audioOutput);
while (rtttlGenerator->isRunning())
rtttlGenerator->loop();
rtttlGenerator->stop();
fileSource->close();
}
void playTone(float frequency, int duration, int volume) {
volume = constrain(volume, 0, 32767);
const int wave_period = SAMPLE_RATE / frequency;
int16_t sample_buffer[wave_period];
for (int i = 0; i < wave_period; ++i)
sample_buffer[i] = (i < wave_period / 2) ? volume : -volume;
int total_samples = SAMPLE_RATE * duration / 1000;
int samples_written = 0;
while (samples_written < total_samples) {
int to_write = min(wave_period, total_samples - samples_written);
i2s_write(BOARD_I2S_PORT, sample_buffer, to_write * sizeof(int16_t), (size_t *)&to_write, portMAX_DELAY);
samples_written += to_write;
}
}
void setupI2S() {
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = 0,
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0
};
i2s_pin_config_t pin_config = {
.bck_io_num = BOARD_I2S_BCK,
.ws_io_num = BOARD_I2S_WS,
.data_out_num = BOARD_I2S_DOUT,
.data_in_num = I2S_PIN_NO_CHANGE
};
i2s_driver_install(BOARD_I2S_PORT, &i2s_config, 0, NULL);
i2s_set_pin(BOARD_I2S_PORT, &pin_config);
i2s_set_clk(BOARD_I2S_PORT, SAMPLE_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
}

17
src/Speaker.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <Arduino.h>
#include <AudioFileSourcePROGMEM.h>
#include <AudioGeneratorRTTTL.h>
#include <AudioOutputI2S.h>
#include <driver/i2s.h>
#include "pins.h"
#define BOARD_I2S_PORT I2S_NUM_0
#define SAMPLE_RATE 44100
void playNotificationSound();
void playRTTTL(const char* rtttl);
void playTone(float frequency, int duration, int volume = 16383);
void setupI2S();

121
src/Storage.cpp Normal file
View File

@ -0,0 +1,121 @@
#include "Storage.h"
Preferences preferences;
// Config variables
String irc_nickname;
String irc_username;
String irc_realname;
String irc_server;
int irc_port;
bool irc_tls;
String irc_channel;
String irc_nickserv;
String wifi_ssid;
String wifi_password;
void loadPreferences() {
preferences.begin("config", false);
// IRC preferences
if (!preferences.isKey("irc_nickname"))
preferences.putString("irc_nickname", "ACID_" + String(random(1000, 10000)));
irc_nickname = preferences.getString("irc_nickname");
if (!preferences.isKey("irc_username"))
preferences.putString("irc_username", "tdeck");
irc_username = preferences.getString("irc_username");
if (!preferences.isKey("irc_realname"))
preferences.putString("irc_realname", "ACID DROP Firmware");
irc_realname = preferences.getString("irc_realname");
if (!preferences.isKey("irc_server"))
preferences.putString("irc_server", "irc.supernets.org");
irc_server = preferences.getString("irc_server");
if (!preferences.isKey("irc_port"))
preferences.putInt("irc_port", 6667);
irc_port = preferences.getInt("irc_port");
if (!preferences.isKey("irc_tls"))
preferences.putBool("irc_tls", false);
irc_tls = preferences.getBool("irc_tls");
if (!preferences.isKey("irc_channel"))
preferences.putString("irc_channel", "#comms");
irc_channel = preferences.getString("irc_channel");
if (!preferences.isKey("irc_nickserv"))
preferences.putString("irc_nickserv", "");
irc_nickserv = preferences.getString("irc_nickserv");
// WiFi preferences
if (!preferences.isKey("wifi_ssid"))
preferences.putString("wifi_ssid", "");
wifi_ssid = preferences.getString("wifi_ssid");
if (!preferences.isKey("wifi_password"))
preferences.putString("wifi_password", "");
wifi_password = preferences.getString("wifi_password");
preferences.end();
}
bool mountSD() {
if (SD.begin(BOARD_SDCARD_CS, SPI, 800000U)) {
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return false;
} else {
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC)
Serial.println("MMC");
else if (cardType == CARD_SD)
Serial.println("SDSC");
else if (cardType == CARD_SDHC)
Serial.println("SDHC");
else
Serial.println("UNKNOWN");
uint32_t cardSize = SD.cardSize() / (1024 * 1024);
uint32_t cardTotal = SD.totalBytes() / (1024 * 1024);
uint32_t cardUsed = SD.usedBytes() / (1024 * 1024);
Serial.printf("SD Card Size: %lu MB\n", cardSize);
Serial.printf("Total space: %lu MB\n", cardTotal);
Serial.printf("Used space: %lu MB\n", cardUsed);
return true;
}
}
return false;
}
void setupSD() {
pinMode(BOARD_SDCARD_CS, OUTPUT);
digitalWrite(BOARD_SDCARD_CS, HIGH);
pinMode(BOARD_SPI_MISO, INPUT_PULLUP);
SPI.begin(BOARD_SPI_SCK, BOARD_SPI_MISO, BOARD_SPI_MOSI);
}
void wipeNVS() {
esp_err_t err = nvs_flash_erase();
if (err == ESP_OK)
Serial.println("NVS flash erase successful.");
else
Serial.println("Error erasing NVS flash!");
err = nvs_flash_init();
if (err == ESP_OK)
Serial.println("NVS flash init successful.");
else
Serial.println("Error initializing NVS flash!");
}

26
src/Storage.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <Arduino.h>
#include "nvs_flash.h"
#include <Preferences.h>
#include <SD.h>
#include "pins.h"
extern Preferences preferences;
extern String irc_nickname;
extern String irc_username;
extern String irc_realname;
extern String irc_server;
extern int irc_port;
extern bool irc_tls;
extern String irc_channel;
extern String irc_nickserv;
extern String wifi_ssid;
extern String wifi_password;
void loadPreferences();
bool mountSD();
void setupSD();
void wipeNVS();

148
src/Utilities.cpp Normal file
View File

@ -0,0 +1,148 @@
#include "Utilities.h"
Pangodream_18650_CL BL(BOARD_BAT_ADC, CONV_FACTOR, READS);
String formatBytes(size_t bytes) {
if (bytes < 1024)
return String(bytes) + " B";
else if (bytes < (1024 * 1024))
return String(bytes / 1024.0, 2) + " KB";
else if (bytes < (1024 * 1024 * 1024))
return String(bytes / 1024.0 / 1024.0, 2) + " MB";
else
return String(bytes / 1024.0 / 1024.0 / 1024.0, 2) + " GB";
}
char getKeyboardInput() {
char incoming = 0;
Wire.requestFrom(LILYGO_KB_SLAVE_ADDRESS, 1);
if (Wire.available()) {
incoming = Wire.read();
if (incoming != (char)0x00) {
return incoming;
}
}
return 0;
}
void setBrightness(uint8_t value) {
static uint8_t level = 16;
static uint8_t steps = 16;
value = constrain(value, 0, steps); // Ensure the brightness value is within the valid range
if (value == 0) {
digitalWrite(BOARD_BL_PIN, 0);
delay(3);
level = 0;
return;
}
if (level == 0) {
digitalWrite(BOARD_BL_PIN, 1);
level = steps;
delayMicroseconds(30);
}
int from = steps - level;
int to = steps - value;
int num = (steps + to - from) % steps;
for (int i = 0; i < num; i++) {
digitalWrite(BOARD_BL_PIN, 0);
digitalWrite(BOARD_BL_PIN, 1);
}
level = value;
}
void updateTimeFromNTP() {
Serial.println("Syncing time with NTP server...");
configTime(-5 * 3600, 3600, "pool.ntp.org", "north-america.pool.ntp.org", "time.nist.gov");
for (int i = 0; i < 10; ++i) { // Try up to 10 times
delay(2000);
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
Serial.println(&timeinfo, "Time synchronized: %A, %B %d %Y %H:%M:%S");
return;
} else {
Serial.println("Failed to synchronize time, retrying...");
}
}
Serial.println("Failed to synchronize time after multiple attempts."); // What do we do if we can't sync with the NTP server?
}
void printDeviceInfo() {
Serial.println("Gathering device info...");
// Get MAC Address
uint8_t mac[6];
esp_efuse_mac_get_default(mac);
String macAddress = String(mac[0], HEX) + ":" + String(mac[1], HEX) + ":" + String(mac[2], HEX) + ":" + String(mac[3], HEX) + ":" + String(mac[4], HEX) + ":" + String(mac[5], HEX);
// Get Chip Info
uint32_t chipId = ESP.getEfuseMac();
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
String chipInfo = String(chip_info.model) + " Rev " + String(chip_info.revision) + ", " + String(chip_info.cores) + " cores, " + String(ESP.getCpuFreqMHz()) + " MHz";
// Get Flash Info
size_t flashSize = spi_flash_get_chip_size();
size_t flashUsed = ESP.getFlashChipSize() - ESP.getFreeSketchSpace();
String flashInfo = formatBytes(flashUsed) + " / " + formatBytes(flashSize);
// Get PSRAM Info
size_t total_psram = ESP.getPsramSize();
size_t free_psram = ESP.getFreePsram();
String psramInfo = formatBytes(total_psram - free_psram) + " / " + formatBytes(total_psram);
// Get Heap Info
size_t total_heap = ESP.getHeapSize();
size_t free_heap = ESP.getFreeHeap();
String heapInfo = formatBytes(total_heap - free_heap) + " / " + formatBytes(total_heap);
// Get WiFi Info
String wifiInfo = "Not connected";
String wifiSSID = "";
String wifiChannel = "";
String wifiSignal = "";
String wifiLocalIP = "";
String wifiGatewayIP = "";
if (WiFi.status() == WL_CONNECTED) {
wifiSSID = WiFi.SSID();
wifiChannel = String(WiFi.channel());
wifiSignal = String(WiFi.RSSI()) + " dBm";
wifiLocalIP = WiFi.localIP().toString();
wifiGatewayIP = WiFi.gatewayIP().toString();
}
Serial.println("MCU: ESP32-S3FN16R8");
Serial.println("LoRa Tranciever: Semtech SX1262 (915 MHz)"); // Need to set the frequency in pins.h
Serial.println("LCD: ST7789 SPI"); // Update this
Serial.println("Chip ID: " + String(chipId, HEX));
Serial.println("MAC Address: " + macAddress);
Serial.println("Chip Info: " + chipInfo);
Serial.println("Battery:");
Serial.println(" Pin Value: " + String(analogRead(34)));
//Serial.println(" Average Pin Value: " + String(BL.pinRead()));
//Serial.println(" Volts: " + String(BL.getBatteryVolts()));
//Serial.println(" Charge Level: " + String(BL.getBatteryChargeLevel()) + "%");
Serial.println("Memory:");
Serial.println(" Flash: " + flashInfo);
Serial.println(" PSRAM: " + psramInfo);
Serial.println(" Heap: " + heapInfo);
if (WiFi.status() == WL_CONNECTED) {
Serial.println("WiFi Info: " + wifiInfo);
Serial.println(" SSID: " + wifiSSID);
Serial.println(" Channel: " + wifiChannel);
Serial.println(" Signal: " + wifiSignal);
Serial.println(" Local IP: " + wifiLocalIP);
Serial.println(" Gateway IP: " + wifiGatewayIP);
}
}

17
src/Utilities.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include "Arduino.h"
#include "WiFi.h"
#include "Wire.h"
#include <Pangodream_18650_CL.h> // Power management
#include "pins.h"
extern Pangodream_18650_CL BL;
String formatBytes(size_t bytes);
char getKeyboardInput();
void printDeviceInfo();
void setBrightness(uint8_t value);
void updateTimeFromNTP();

View File

@ -1,5 +1,6 @@
#define logo_width 199
#define logo_height 165
static unsigned char logo_bits[] = {
0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
#pragma once
// board peripheral power control pin needs to be set to HIGH when using the peripheral
// Board pin definitions ------------------------
#define BOARD_POWERON 10
// Speaker
#define BOARD_I2S_WS 5
#define BOARD_I2S_BCK 7
#define BOARD_I2S_DOUT 6
#define BOARD_I2S_BCK 7
#define BOARD_I2C_SDA 18
#define BOARD_I2C_SCL 8
@ -46,9 +46,18 @@
#define BOARD_BL_PIN 42
#define GPS_RX_PIN 44
#define GPS_TX_PIN 43
// Other definitions ----------------------------
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
// Battery definitions
#define CONV_FACTOR 1.8 // Conversion factor for the ADC to voltage conversion
#define READS 20 // Number of readings for averaging
#define LILYGO_KB_SLAVE_ADDRESS 0x55
#define GPS_RX_PIN 44
#define GPS_TX_PIN 43
#define RADIO_FREQ 903.0