Skip to content
Merged
13 changes: 8 additions & 5 deletions src/decoders/gif.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,24 @@ void DecoderGIF::renderFrame(MatrixPanel_I2S_DMA *display) {
};

void* DecoderGIF::openFile(const char *fname, int32_t *pSize) {
File *file = new File();
*file = LittleFS.open(fname);
if (file) {
*pSize = file->size();
File *f = new File();
*f = LittleFS.open(fname);
if (*f) {
*pSize = f->size();

return static_cast<void*>(file);
return static_cast<void*>(f);
}

delete f;

return NULL;
};

void DecoderGIF::closeFile(void *pHandle) {
File *f = static_cast<File *>(pHandle);
if (f != NULL) {
f->close();
delete f;
}
};

Expand Down
13 changes: 8 additions & 5 deletions src/decoders/jpeg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,24 @@ void DecoderJPEG::renderFrame(MatrixPanel_I2S_DMA *display) {
};

void* DecoderJPEG::openFile(const char *path, int32_t *size) {
File *file = new File();
*file = LittleFS.open(path);
if (file) {
*size = file->size();
File *f = new File();
*f = LittleFS.open(path);
if (*f) {
*size = f->size();

return static_cast<void*>(file);
return static_cast<void*>(f);
}

delete f;

return NULL;
};

void DecoderJPEG::closeFile(void *pHandle) {
File *f = static_cast<File *>(pHandle);
if (f != NULL) {
f->close();
delete f;
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ void Filesystem::tree(const char *path, uint8_t depth) {

// see readme "Note on PlatformIO"
bool Filesystem::pathExists(const char *path) {
static struct stat pathStat;
struct stat pathStat;
char fullPath[64];
snprintf(fullPath, sizeof(fullPath), "/littlefs%s", path);

Expand Down
42 changes: 32 additions & 10 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ HUB75_I2S_CFG ledConfig(
);
MatrixPanel_I2S_DMA *display = nullptr;

bool setupOk = false;
OTA ota(1000);
draw_mode drawMode = DRAW_MODE_NONE;
WiFiController wifi;
Expand All @@ -30,6 +31,12 @@ Renderer *currentMode = nullptr;

void updateQrText() {
const char* url = wifi.getUrl();
if (url == nullptr) {
Log::instance()->error("Error getting URL for QR code\n");

return;
}

Log::instance()->info("QR code URL: %s\n", url);
if (url[0] != '\0' && !qr.setText(url)) {
Log::instance()->error("Error encoding IP to QR code\n");
Expand Down Expand Up @@ -70,7 +77,9 @@ bool changeMode(draw_mode newMode) {

drawMode = newMode;
Log::instance()->info("Changed mode to %d\n", drawMode);
display->clearScreen();
if (display != nullptr) {
display->clearScreen();
}

if (newMode == DRAW_MODE_QR) {
updateQrText();
Expand All @@ -80,16 +89,16 @@ bool changeMode(draw_mode newMode) {
};

void updateConfig(JsonDocument& newConfig) {
const char* newSsid = newConfig["wifi"]["ssid"].as<const char*>();
const char* newPassword = newConfig["wifi"]["password"].as<const char*>();
const char* currentSsid = conf.current["wifi"]["ssid"].as<const char*>();
const char* currentPassword = conf.current["wifi"]["password"].as<const char*>();
String newSsid = newConfig["wifi"]["ssid"].as<String>();
String newPassword = newConfig["wifi"]["password"].as<String>();
String currentSsid = conf.current["wifi"]["ssid"].as<String>();
String currentPassword = conf.current["wifi"]["password"].as<String>();

if (strcmp(newSsid, currentSsid) != 0 || strcmp(newPassword, currentPassword) != 0) {
if (!newSsid.equals(currentSsid) || !newPassword.equals(currentPassword)) {
Log::instance()->info("WiFi configuration changed, reconnecting\n");
wifi.connect(newSsid, newPassword);
wifi.connect(newSsid.c_str(), newPassword.c_str());
}
if (newConfig["panel"]["brightness"].as<uint8_t>() != conf.current["panel"]["brightness"].as<uint8_t>()) {
if (display != nullptr && newConfig["panel"]["brightness"].as<uint8_t>() != conf.current["panel"]["brightness"].as<uint8_t>()) {
Log::instance()->info("Panel brightness changed: %d\n", newConfig["panel"]["brightness"].as<uint8_t>());
display->setBrightness(newConfig["panel"]["brightness"].as<uint8_t>());
}
Expand Down Expand Up @@ -122,6 +131,11 @@ void setup() {

// Initialize LED matrix
display = setupLED(ledConfig);
if (display == nullptr) {
Log::instance()->error("Error setting up LED matrix\n");

return;
}
display->setBrightness(conf.current["panel"]["brightness"].as<uint8_t>());

// Initialize gyroscope
Expand All @@ -130,9 +144,12 @@ void setup() {
}

// Initialize WiFi
if (!wifi.connect(conf.current["wifi"]["ssid"].as<const char*>(), conf.current["wifi"]["password"].as<const char*>())) {
String ssid = conf.current["wifi"]["ssid"].as<String>();
String password = conf.current["wifi"]["password"].as<String>();
if (!wifi.connect(ssid.c_str(), password.c_str())) {
return;
}

updateQrText();

// Initialize log WebSocket
Expand Down Expand Up @@ -179,14 +196,19 @@ void setup() {

server.begin();
// Filesystem::tree("/", 0);
setupOk = true;
};

ulong lastModePoll = 0;

void loop() {
if (!setupOk) {
return;
}

ota.loop();

if (lastModePoll + 1000 < millis()) {
if (millis() - lastModePoll >= 1000) {
lastModePoll = millis();
if (changeMode(selector.getMode())) {
return;
Expand Down
9 changes: 8 additions & 1 deletion src/modes/life.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ bool Life::open() {
return true;
}

world = (Cell*)malloc(width * height * sizeof(Cell));
world = (Cell*)calloc(width * height, sizeof(Cell));
if (world == nullptr) {
Log::instance()->error("Error allocating world\n");

return false;
}

generation = 0;
isOpen = true;

Expand All @@ -29,6 +35,7 @@ bool Life::close() {
isOpen = false;
delay(50); // wait for possible render() execution to finish
free(world);
world = nullptr;
generation = 0;

return true;
Expand Down
84 changes: 49 additions & 35 deletions src/modes/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ void Map::initServer(AsyncWebServer *server) {
[&](AsyncWebServerRequest *request) {
Log::instance()->info("Map received\n");
const AsyncWebHeader *contentType = request->getHeader("Content-Type");
if (contentType == nullptr) {
Log::instance()->error("Uploaded map has no content-type header");
request->send(400, "text/plain", "no content-type header");

return;
}
if (contentType->value() != "image/jpeg") {
Log::instance()->error("Uploaded map has unhandled content-type: %s\n", contentType->value());
request->send(400, "text/plain", "unhandled content type");
Expand All @@ -97,6 +103,12 @@ void Map::initServer(AsyncWebServer *server) {
nullptr,
[&](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
const AsyncWebHeader *contentType = request->getHeader("Content-Type");
if (contentType == nullptr) {
Log::instance()->error("Uploaded map has no content-type header");
request->send(400, "text/plain", "no content-type header");

return;
}
if (contentType->value() != "image/jpeg") {
Log::instance()->error("Uploaded map has unhandled content-type: %s\n", contentType->value());
request->send(400, "text/plain", "unhandled content type");
Expand Down Expand Up @@ -218,52 +230,47 @@ std::array<double, 2> Map::getPositionFromAPI() {
http.setReuse(false);
http.setConnectTimeout(10000);
http.setTimeout(60000);
const char *u = config["url"].as<const char*>();
const char *m = config["method"].as<const char*>();
const char *r = config["regex"].as<const char*>();
const char *b = config["body"].as<const char*>();
String u = config["url"].as<String>();
String m = config["method"].as<String>();
String r = config["regex"].as<String>();
String b = config["body"].as<String>();

if (u == nullptr || strcmp(u, "") == 0) {
if (u.isEmpty()) {
Log::instance()->error("Position API URL is not set\n");

return pos;
}
if (r == nullptr || strcmp(r, "") == 0) {
if (r.isEmpty()) {
Log::instance()->error("Position API regexp is not set\n");

return pos;
}
if (m == nullptr || strcmp(m, "") == 0) {
if (m.isEmpty()) {
m = "GET";
}

// Apply query params for GET requests
if (strcasecmp(m, "GET") == 0 && b != nullptr && strcmp(b, "") != 0) {
char urlBuffer[128];

if (m.equalsIgnoreCase("GET") && !b.isEmpty()) {
// Need a "/" after hostname, otherwise the query parameters end up in the DNS query
size_t urlLen = strlen(u);
if (u[urlLen - 1] == '/') {
snprintf(urlBuffer, sizeof(urlBuffer), "%s%s", u, b);
if (u.endsWith("/")) {
u += b;
} else {
snprintf(urlBuffer, sizeof(urlBuffer), "%s/%s", u, b);
u += "/" + b;
}

u = urlBuffer;
}

http.begin(client, u);
http.begin(client, u.c_str());

// Apply headers
const char *hs = config["headers"].as<const char*>();
if (hs != nullptr && strcmp(hs, "") != 0 && !setHeaders(http, hs)) {
String hs = config["headers"].as<String>();
if (!hs.isEmpty() && !setHeaders(http, hs.c_str())) {
return pos;
}

// Send request
int code;
if (strcasecmp(m, "POST") == 0) {
code = http.POST((b != nullptr && strcmp(b, "") != 0) ? b : "");
if (m.equalsIgnoreCase("POST")) {
code = http.POST(b);
} else {
code = http.GET();
}
Expand All @@ -274,29 +281,33 @@ std::array<double, 2> Map::getPositionFromAPI() {
}

// Match regex
pos = matchRegex(http.getString().c_str(), r);
String resp = http.getString();
pos = matchRegex(resp.c_str(), r.c_str());

return pos;
};

std::array<double, 2> Map::getPosition() {
std::array<double, 2> pos = { 0, 0 };
if (
strcmp(config["url"].as<const char*>(), "") != 0
&& strcmp(config["method"].as<const char*>(), "") != 0
&& strcmp(config["regex"].as<const char*>(), "") != 0
config["url"].as<String>() != ""
&& config["method"].as<String>() != ""
&& config["regex"].as<String>() != ""
) {
pos = getPositionFromAPI();
Log::instance()->info("Position from API (%s): %f, %f\n", config["url"].as<const char*>(), pos[0], pos[1]);
String url = config["url"].as<String>();
Log::instance()->info("Position from API (%s): %f, %f\n", url.c_str(), pos[0], pos[1]);
}
// default to static position, if available
if (
pos[0] == 0 && pos[1] == 0
&& strcmp(config["latitude"].as<const char*>(), "") != 0
&& strcmp(config["longitude"].as<const char*>(), "") != 0
&& config["latitude"].as<String>() != ""
&& config["longitude"].as<String>() != ""
) {
pos[0] = strtod(config["latitude"].as<const char*>(), NULL);
pos[1] = strtod(config["longitude"].as<const char*>(), NULL);
String lat = config["latitude"].as<String>();
String lon = config["longitude"].as<String>();
pos[0] = strtod(lat.c_str(), NULL);
pos[1] = strtod(lon.c_str(), NULL);
Log::instance()->info("Position from configuration: %f, %f\n", pos[0], pos[1]);
}

Expand Down Expand Up @@ -379,21 +390,24 @@ void Map::render(MatrixPanel_I2S_DMA *display) {
};

void* Map::openFile(const char *path, int32_t *size) {
File *file = new File();
*file = LittleFS.open(path);
if (file) {
*size = file->size();
File *f = new File();
*f = LittleFS.open(path);
if (*f) {
*size = f->size();

return static_cast<void*>(file);
return static_cast<void*>(f);
}

delete f;

return NULL;
};

void Map::closeFile(void *pHandle) {
File *f = static_cast<File *>(pHandle);
if (f != NULL) {
f->close();
delete f;
}
};

Expand Down
4 changes: 2 additions & 2 deletions src/modes/map.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Map : public Renderer {
private:
typedef struct drawUserData {
MatrixPanel_I2S_DMA *display;
uint16_t *position;
uint8_t *position;
uint8_t size;
uint16_t color;
uint16_t track;
Expand All @@ -28,7 +28,7 @@ class Map : public Renderer {
ulong lastRender = 0;
ulong lastCropUpdate = 0;
uint16_t cropArea[2];
uint16_t cropPosition[2];
uint8_t cropPosition[2];
bool isUpdating = false;
bool isOpen = false;
JsonDocument config;
Expand Down
5 changes: 5 additions & 0 deletions src/modes/media.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,12 @@ void Media::initServer(AsyncWebServer *server) {
if (index == 0) {
Log::instance()->debug("Receiving file: %d bytes\n", total);
const AsyncWebHeader *contentType = request->getHeader("Content-Type");
if (contentType == nullptr) {
Log::instance()->error("Uploaded file has no content-type header");
request->send(400, "text/plain", "no content-type header");

return;
}
if (contentType->value() != "image/jpeg" && contentType->value() != "image/gif") {
Log::instance()->error("Uploaded file has unhandled content-type: %s\n", contentType->value());
request->send(400, "text/plain", "unhandled content type");
Expand Down
Loading
Loading