Compare commits

...

2 Commits

Author SHA1 Message Date
9e00821802 Added image filename implementation 2026-06-25 17:44:58 +03:00
025cd56500 Some restructuring. Added structure for image finding 2026-06-25 17:01:30 +03:00
2 changed files with 144 additions and 17 deletions

View File

@@ -2,6 +2,7 @@
// Required
"host": "localhost",
"port": 0,
"base_url": "https://trmnl.com/",
// Optional
"devices_filename": "devices.json",

160
main.cpp
View File

@@ -1,12 +1,23 @@
// Start using chrono when C++20 becomes available
// #include <chrono>
#include <filesystem>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <random>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <stdio.h>
#include <stdint.h>
#include <time.h>
// Stop using these when C++20 becomes available
#include <fcntl.h>
#include <sys/stat.h>
#include "httplib.h"
#include "json.hpp"
@@ -17,8 +28,11 @@
using nlohmann::json;
using namespace std;
namespace fs = std::filesystem;
constexpr int API_KEY_LENGHT = 16;
constexpr char DEFAULT_IMAGE_URL[] = "https://trmnl.com/images/setup/setup-logo.bmp";
constexpr char DEFAULT_IMAGE_FNAME[] = "2024-09-20T00:00:00";
string generate_api_key(int len = API_KEY_LENGHT)
{
@@ -68,13 +82,13 @@ string generate_api_key(int len = API_KEY_LENGHT)
return result;
}
void reload_container(TRMNLContainer& container, const string& filename)
bool reload_container(TRMNLContainer& container, const string& filename)
{
json j;
bool ok = read_file_json(j, filename, &cout);
if (!ok)
{
return;
return false;
}
container.clear();
if (j.is_array())
@@ -84,17 +98,98 @@ void reload_container(TRMNLContainer& container, const string& filename)
container.add_device(j[i]);
}
}
return true;
}
string get_timestamp_from_filename(const string& filepath)
{
char result[128] = "";
struct stat status;
stat(filepath.c_str(), &status);
// Time of last modification
time_t file_time = status.st_mtim.tv_sec;
tm* file_tm;
file_tm = gmtime(&file_time);
snprintf(result, sizeof(result) - 1, "%04d-%02d-%02dT%02d:%02d:%02d",
file_tm->tm_year + 1900,
file_tm->tm_mon + 1,
file_tm->tm_mday,
file_tm->tm_hour,
file_tm->tm_min,
file_tm->tm_sec
);
return result;
}
// Use this when C++20 becomes available
#if 0
string get_timestamp_from_file_time(const fs::file_time_type& ftime)
{
char result[128] = "";
time_t file_time = std::chrono::system_clock::to_time_t(std::chrono::file_clock::to_sys(ftime));
tm* file_tm;
file_tm = gmtime(&file_time);
snprintf(result, sizeof(result) - 1, "%04d-%02d-%02dT%02d:%02d:%02d",
file_tm->tm_year + 1900,
file_tm->tm_mon + 1,
file_tm->tm_mday,
file_tm->tm_hour,
file_tm->tm_min,
file_tm->tm_sec
);
return result;
}
#endif
// First string is image filename for URL
// Second string is timestamp for TRMNL filename
pair<string, string> find_image_for_friendly(const string& folder_images, const string& friendly_id)
{
pair<string, string> result = {"", ""};
set<string> permitted_extensions = { ".bmp", ".png" };
if (!fs::is_directory(folder_images))
{
return result;
}
for (const fs::directory_entry& entry : fs::directory_iterator(folder_images))
{
if ((entry.path().stem() == friendly_id) &&
(0 != permitted_extensions.count(entry.path().extension())))
{
result.first = entry.path().filename();
result.second = get_timestamp_from_filename(entry.path());
// Change to this when C++20 becomes available
// result.second = get_timestamp_from_file_time(entry.last_write_time());
}
}
return result;
}
int main(int argc, char **argv)
{
string config_filename = "config.json";
string devices_filename = "devices.json";
string host = "";
string folder_images = "images";
string host = "";
uint16_t port = 0;
string base_url = "";
string cert_file = "";
string key_file = "";
uint16_t port = 0;
shared_ptr<httplib::Server> server = nullptr;
bool ok;
@@ -113,10 +208,14 @@ int main(int argc, char **argv)
return -1;
}
json_extract(cfg, "devices_filename", devices_filename);
json_extract(cfg, "host", host);
json_extract(cfg, "port", port);
json_extract(cfg, "base_url", base_url);
json_extract(cfg, "devices_filename", devices_filename);
json_extract(cfg, "folder_images", folder_images);
// TODO: Extract when SSL is ready
// json_extract(cfg, "cert_file", cert_file);
// json_extract(cfg, "key_file", cert_file);
@@ -132,12 +231,24 @@ int main(int argc, char **argv)
return -1;
}
if (base_url.empty())
{
cout << "base url not provided" << endl;
return -1;
}
if (folder_images.empty())
{
cout << "folder for images is empty" << endl;
return -1;
}
if (!fs::is_directory(folder_images))
{
cout << "filepath for images is not a folder" << endl;
return -1;
}
if (!cert_file.empty() && !key_file.empty())
{
// TODO: Implement SSL Server Properly
@@ -147,14 +258,13 @@ int main(int argc, char **argv)
server = make_shared<httplib::Server>();
}
ok = read_file_json(devs, devices_filename, &cout);
ok = reload_container(container, devices_filename);
if (!ok)
{
cout << "Could not read devices file" << endl;
return -1;
}
reload_container(container, devices_filename);
auto setup_handler = [&container, &devices_filename](const httplib::Request& req, httplib::Response& res)
{
json response;
@@ -213,9 +323,8 @@ int main(int argc, char **argv)
response["status"] = 200;
response["api_key"] = trmnl->api_key();
response["friendly_id"] = trmnl->friendly_id();
// TODO: Check for image in folder
response["image_url"] = "https://trmnl.com/images/setup/setup-logo.bmp";
response["filename"] = "welcome";
response["image_url"] = DEFAULT_IMAGE_URL;
response["filename"] = DEFAULT_IMAGE_FNAME;
res.set_header("Content-Type", "application/json");
res.body = response.dump();
@@ -226,7 +335,8 @@ int main(int argc, char **argv)
}
};
auto display_handler = [&container, &devices_filename](const httplib::Request& req, httplib::Response& res)
auto display_handler = [&container, &devices_filename, &folder_images, &base_url]
(const httplib::Request& req, httplib::Response& res)
{
json response;
@@ -277,19 +387,35 @@ int main(int argc, char **argv)
return;
}
// TODO: Check for image in folder
// string image;
string image_fname;
string image_timestamp;
tie(image_fname, image_timestamp) = find_image_for_friendly(folder_images, trmnl->friendly_id());
// The ID and api_key match here
res.status = 200;
// From docs: will be 202 if no user_id is attached to device
response["status"] = 0;
response["image_url"] = "https://trmnl.com/images/setup/setup-logo.bmp";
response["filename"] = "2024-09-20T00:00:00";
if (image_fname.empty())
{
response["image_url"] = DEFAULT_IMAGE_URL;
response["filename"] = DEFAULT_IMAGE_FNAME;
}
else
{
string full_url = base_url;
full_url += "/images/";
full_url += image_fname;
response["image_url"] = full_url;
response["filename"] = image_timestamp;
}
response["refresh_rate"] = to_string(trmnl->refresh_rate());
// TODO: Handle firmware updating
response["update_firmware"] = false;
response["firmware_url"] = nullptr;
response["refresh_rate"] = to_string(trmnl->refresh_rate());
response["reset_firmware"] = false;
res.set_header("Content-Type", "application/json");