Compare commits
10 Commits
52b0d6642f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e00821802 | |||
| 025cd56500 | |||
| 147fa762c8 | |||
| eca47d1596 | |||
| 84bc7f5d40 | |||
| 2e7899c701 | |||
| 23e2fbd995 | |||
| fe8346925a | |||
| 90bfbda9c7 | |||
| 5ae6fb711c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
server
|
||||
*.json
|
||||
images/*
|
||||
|
||||
1
Makefile
1
Makefile
@@ -1,5 +1,6 @@
|
||||
srcs += main.cpp
|
||||
srcs += helpers.cpp
|
||||
srcs += TRMNL.cpp
|
||||
|
||||
all:
|
||||
g++ ${srcs} -o server
|
||||
|
||||
@@ -20,5 +20,5 @@ Use file named `devices.json` in working directory or supply your own via config
|
||||
* From: https://github.com/yhirose/cpp-httplib
|
||||
* Version: 0.47.0
|
||||
|
||||
# Info on API
|
||||
# Info on TRMNL API
|
||||
* https://github.com/usetrmnl/trmnl-firmware
|
||||
|
||||
207
TRMNL.cpp
Normal file
207
TRMNL.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "TRMNL.h"
|
||||
#include "helpers.h"
|
||||
|
||||
using std::list;
|
||||
using std::map;
|
||||
using std::optional;
|
||||
using std::string;
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
TRMNL::TRMNL(const string& id,
|
||||
const string& api_key,
|
||||
const string& friendly_id,
|
||||
int refresh_rate)
|
||||
: m_id(id),
|
||||
m_api_key(api_key),
|
||||
m_friendly_id(friendly_id),
|
||||
m_refresh_rate(refresh_rate),
|
||||
m_update_handler()
|
||||
{}
|
||||
|
||||
TRMNL::TRMNL(const nlohmann::json& j)
|
||||
: m_id(""),
|
||||
m_api_key(""),
|
||||
m_friendly_id(""),
|
||||
m_refresh_rate(DEFAULT_REFRESH_RATE),
|
||||
m_update_handler()
|
||||
{
|
||||
json_extract(j, "ID", m_id);
|
||||
json_extract(j, "api_key", m_api_key);
|
||||
json_extract(j, "friendly_id", m_friendly_id);
|
||||
json_extract(j, "refresh_rate", m_refresh_rate);
|
||||
}
|
||||
|
||||
const string& TRMNL::id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
const string& TRMNL::api_key() const
|
||||
{
|
||||
return m_api_key;
|
||||
}
|
||||
|
||||
const string& TRMNL::friendly_id() const
|
||||
{
|
||||
return m_friendly_id;
|
||||
}
|
||||
|
||||
int TRMNL::refresh_rate() const
|
||||
{
|
||||
return m_refresh_rate;
|
||||
}
|
||||
|
||||
void TRMNL::id(const std::string& id)
|
||||
{
|
||||
m_id = id;
|
||||
if (m_update_handler)
|
||||
{
|
||||
m_update_handler(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void TRMNL::api_key(const std::string& api_key)
|
||||
{
|
||||
m_api_key = api_key;
|
||||
if (m_update_handler)
|
||||
{
|
||||
m_update_handler(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void TRMNL::friendly_id(const std::string& friendly_id)
|
||||
{
|
||||
m_friendly_id = friendly_id;
|
||||
if (m_update_handler)
|
||||
{
|
||||
m_update_handler(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void TRMNL::refresh_rate(int refresh_rate)
|
||||
{
|
||||
m_refresh_rate = refresh_rate;
|
||||
if (m_update_handler)
|
||||
{
|
||||
m_update_handler(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void TRMNL::set_update_handler(std::function<void (const TRMNL& trmnl)> handler)
|
||||
{
|
||||
m_update_handler = handler;
|
||||
}
|
||||
|
||||
string TRMNL::friendly_from_id(string id)
|
||||
{
|
||||
size_t pos = id.find(':');
|
||||
while (pos != string::npos)
|
||||
{
|
||||
id.erase(pos, 1);
|
||||
pos = id.find(':');
|
||||
}
|
||||
|
||||
if (id.size() <= 6)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
else
|
||||
{
|
||||
return id.substr(id.size() - 6);
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& j, const TRMNL& trmnl)
|
||||
{
|
||||
j = json{
|
||||
{"ID", trmnl.m_id},
|
||||
{"api_key", trmnl.m_api_key},
|
||||
{"friendly_id", trmnl.m_friendly_id},
|
||||
{"refresh_rate", trmnl.m_refresh_rate},
|
||||
};
|
||||
}
|
||||
|
||||
void TRMNLContainer::TRMNL_update_handler(const TRMNL& trmnl)
|
||||
{
|
||||
auto it = m_by_id.find(trmnl.m_id);
|
||||
if (it == m_by_id.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// An update is needed only if friendly ID changes
|
||||
if (0 == m_by_friendly.count(trmnl.m_friendly_id))
|
||||
{
|
||||
m_by_friendly[trmnl.m_friendly_id] = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
TRMNLContainer& TRMNLContainer::operator=(const TRMNLContainer& other)
|
||||
{
|
||||
m_devices = other.m_devices;
|
||||
m_by_id = other.m_by_id;
|
||||
m_by_friendly = other.m_by_friendly;
|
||||
|
||||
for (TRMNL& device : m_devices)
|
||||
{
|
||||
device.set_update_handler([this](const TRMNL& trmnl){ TRMNL_update_handler(trmnl); });
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TRMNLContainer::add_device(TRMNL trmnl)
|
||||
{
|
||||
if (trmnl.m_id.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = m_devices.insert(m_devices.end(), trmnl);
|
||||
|
||||
m_by_id[trmnl.m_id] = it;
|
||||
|
||||
if (!trmnl.m_friendly_id.empty())
|
||||
{
|
||||
m_by_friendly[trmnl.m_friendly_id] = it;
|
||||
}
|
||||
|
||||
it->set_update_handler([this](const TRMNL& trmnl){ TRMNL_update_handler(trmnl); });
|
||||
}
|
||||
|
||||
TRMNL* TRMNLContainer::get_device_by_id(const string& id)
|
||||
{
|
||||
TRMNL* result = nullptr;
|
||||
|
||||
if (0 != m_by_id.count(id))
|
||||
{
|
||||
result = &(*m_by_id[id]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TRMNL* TRMNLContainer::get_device_by_friendly(const string& friendly)
|
||||
{
|
||||
TRMNL* result = nullptr;
|
||||
|
||||
if (0 != m_by_friendly.count(friendly))
|
||||
{
|
||||
result = &(*m_by_friendly[friendly]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TRMNLContainer::clear()
|
||||
{
|
||||
m_by_friendly.clear();
|
||||
m_by_id.clear();
|
||||
m_devices.clear();
|
||||
}
|
||||
|
||||
void to_json(json& j, const TRMNLContainer& cont)
|
||||
{
|
||||
j = cont.m_devices;
|
||||
}
|
||||
76
TRMNL.h
Normal file
76
TRMNL.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef TRMNL_H_
|
||||
#define TRMNL_H_
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
constexpr int DEFAULT_REFRESH_RATE = 300;
|
||||
|
||||
class TRMNL
|
||||
{
|
||||
private:
|
||||
std::string m_id;
|
||||
std::string m_api_key;
|
||||
std::string m_friendly_id;
|
||||
int m_refresh_rate;
|
||||
|
||||
std::function<void (const TRMNL& trmnl)> m_update_handler;
|
||||
|
||||
public:
|
||||
// Constructors
|
||||
TRMNL(const std::string& id = "",
|
||||
const std::string& api_key = "",
|
||||
const std::string& friendly_id = "",
|
||||
int refresh_rate = DEFAULT_REFRESH_RATE);
|
||||
|
||||
TRMNL(const nlohmann::json& j);
|
||||
|
||||
// Getters
|
||||
const std::string& id() const;
|
||||
const std::string& api_key() const;
|
||||
const std::string& friendly_id() const;
|
||||
int refresh_rate() const;
|
||||
|
||||
// Setters
|
||||
void id(const std::string& id);
|
||||
void api_key(const std::string& api_key);
|
||||
void friendly_id(const std::string& friendly_id);
|
||||
void refresh_rate(int refresh_rate);
|
||||
|
||||
void set_update_handler(std::function<void (const TRMNL& trmnl)> handler);
|
||||
|
||||
static std::string friendly_from_id(std::string id);
|
||||
|
||||
friend void to_json(nlohmann::json& j, const TRMNL& trmnl);
|
||||
friend class TRMNLContainer;
|
||||
};
|
||||
|
||||
class TRMNLContainer
|
||||
{
|
||||
private:
|
||||
std::list<TRMNL> m_devices;
|
||||
std::map<std::string, std::list<TRMNL>::iterator> m_by_id;
|
||||
std::map<std::string, std::list<TRMNL>::iterator> m_by_friendly;
|
||||
|
||||
void TRMNL_update_handler(const TRMNL& trmnl);
|
||||
|
||||
public:
|
||||
TRMNLContainer() = default;
|
||||
TRMNLContainer& operator=(const TRMNLContainer& other);
|
||||
|
||||
void add_device(TRMNL trmnl);
|
||||
|
||||
TRMNL* get_device_by_id(const std::string& id);
|
||||
TRMNL* get_device_by_friendly(const std::string& friendly);
|
||||
|
||||
void clear();
|
||||
|
||||
friend void to_json(nlohmann::json& j, const TRMNLContainer& cont);
|
||||
};
|
||||
|
||||
#endif // TRMNL_H_
|
||||
@@ -2,9 +2,11 @@
|
||||
// Required
|
||||
"host": "localhost",
|
||||
"port": 0,
|
||||
"base_url": "https://trmnl.com/",
|
||||
|
||||
// Optional
|
||||
"devices_filename": "devices.json",
|
||||
"folder_images": "images",
|
||||
"cert_file": "path/to/file",
|
||||
"key_file": "path/to/file"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
// Required
|
||||
"ID": "MAC ADDRESS",
|
||||
|
||||
// Auto managed
|
||||
"api_key": "api key",
|
||||
"friendly_id": "917F0B",
|
||||
"refresh_rate": 600
|
||||
}
|
||||
]
|
||||
33
helpers.cpp
33
helpers.cpp
@@ -6,32 +6,33 @@ using nlohmann::json;
|
||||
|
||||
using std::endl;
|
||||
using std::ifstream;
|
||||
using std::ofstream;
|
||||
using std::istream;
|
||||
using std::ostream;
|
||||
using std::string;
|
||||
|
||||
bool read_config_json(json& cfg, const string& filename, ostream* log)
|
||||
bool read_file_json(json& j, const string& filename, ostream* log)
|
||||
{
|
||||
ifstream cfg_file(filename);
|
||||
ifstream file(filename);
|
||||
|
||||
if (!cfg_file.is_open())
|
||||
if (!file.is_open())
|
||||
{
|
||||
if (nullptr != log)
|
||||
{
|
||||
*log << "Could not open config file" << endl;
|
||||
*log << "Could not open file to read - " << filename << endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return read_config_json(cfg, cfg_file, log);
|
||||
return read_stream_json(j, file, log);
|
||||
}
|
||||
|
||||
bool read_config_json(json& cfg, istream& in, ostream* log)
|
||||
bool read_stream_json(json& j, istream& in, ostream* log)
|
||||
{
|
||||
// Parse with comments
|
||||
try
|
||||
{
|
||||
cfg = json::parse(in, nullptr, true, true);
|
||||
j = json::parse(in, nullptr, true, true);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
@@ -45,6 +46,24 @@ bool read_config_json(json& cfg, istream& in, ostream* log)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool write_file_json(const json& j, const string& filename, ostream* log)
|
||||
{
|
||||
ofstream file(filename);
|
||||
|
||||
if (!file.is_open())
|
||||
{
|
||||
if (nullptr != log)
|
||||
{
|
||||
*log << "Could not open file to write - " << filename << endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
file << j.dump(4) << endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool json_extract(const json& j, const string& key, string& out)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
17
helpers.h
17
helpers.h
@@ -9,19 +9,26 @@
|
||||
#include "json.hpp"
|
||||
|
||||
|
||||
// Reads configuration json from supplied filename
|
||||
// cfg - output json
|
||||
// Reads json from supplied filename
|
||||
// j - output json
|
||||
// filename - filepath from which to read json
|
||||
// log - ostream to log human readable errors to - can be nullptr
|
||||
// Returns - true if read and parse is successful
|
||||
bool read_config_json(nlohmann::json& cfg, const std::string& filename, std::ostream* log);
|
||||
bool read_file_json(nlohmann::json& j, const std::string& filename, std::ostream* log);
|
||||
|
||||
// Reads configuration json from supplied istream
|
||||
// cfg - output json
|
||||
// j - output json
|
||||
// in - istream from which to read json
|
||||
// log - ostream to log human readable errors to - can be nullptr
|
||||
// Returns - true if read and parse is successful
|
||||
bool read_config_json(nlohmann::json& cfg, std::istream& in, std::ostream* log);
|
||||
bool read_stream_json(nlohmann::json& j, std::istream& in, std::ostream* log);
|
||||
|
||||
// Writes json to supplied filename
|
||||
// j - input json
|
||||
// filename - filepath to which to write json
|
||||
// log - ostream to log human readable errors to - can be nullptr
|
||||
// Returns - true if write is successful
|
||||
bool write_file_json(const nlohmann::json& j, const std::string& filename, std::ostream* log);
|
||||
|
||||
// JSON Extraction Helpers
|
||||
// out value is modified only if an extraction happened
|
||||
|
||||
393
main.cpp
393
main.cpp
@@ -1,45 +1,221 @@
|
||||
// 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"
|
||||
|
||||
#include "helpers.h"
|
||||
#include "TRMNL.h"
|
||||
|
||||
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)
|
||||
{
|
||||
string result = "";
|
||||
std::random_device rand;
|
||||
std::uniform_int_distribution distribution(0, 63);
|
||||
int single;
|
||||
char symbol;
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
len = API_KEY_LENGHT;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; ++i)
|
||||
{
|
||||
single = distribution(rand);
|
||||
if (single <= 9)
|
||||
{
|
||||
symbol = '0' + single;
|
||||
}
|
||||
else if ((single >= 10) && (single <= 35))
|
||||
{
|
||||
symbol = 'A' + single - 10;
|
||||
}
|
||||
else if ((single >= 36) && (single <= 61))
|
||||
{
|
||||
symbol = 'a' + single - 36;
|
||||
}
|
||||
else if (62 == single)
|
||||
{
|
||||
symbol = '+';
|
||||
}
|
||||
else if (63 == single)
|
||||
{
|
||||
symbol = '/';
|
||||
}
|
||||
// This should never happen
|
||||
else
|
||||
{
|
||||
symbol = '?';
|
||||
}
|
||||
|
||||
result += symbol;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool reload_container(TRMNLContainer& container, const string& filename)
|
||||
{
|
||||
json j;
|
||||
bool ok = read_file_json(j, filename, &cout);
|
||||
if (!ok)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
container.clear();
|
||||
if (j.is_array())
|
||||
{
|
||||
for (int i = 0; i < j.size(); ++i)
|
||||
{
|
||||
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 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;
|
||||
json cfg;
|
||||
json devs;
|
||||
TRMNLContainer container;
|
||||
|
||||
if (argc > 2)
|
||||
{
|
||||
config_filename = argv[1];
|
||||
}
|
||||
|
||||
ok = read_config_json(cfg, config_filename, &cout);
|
||||
ok = read_file_json(cfg, config_filename, &cout);
|
||||
if (!ok)
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -55,6 +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
|
||||
@@ -64,22 +258,197 @@ int main(int argc, char **argv)
|
||||
server = make_shared<httplib::Server>();
|
||||
}
|
||||
|
||||
server->Get("/api/setup/", [](const httplib::Request& req, httplib::Response& res)
|
||||
ok = reload_container(container, devices_filename);
|
||||
if (!ok)
|
||||
{
|
||||
cout << req.headers.size() << endl;
|
||||
for (auto header : req.headers)
|
||||
{
|
||||
cout << header.first << ": " << header.second << endl;
|
||||
cout << "Could not read devices file" << endl;
|
||||
return -1;
|
||||
}
|
||||
res.status = 400;
|
||||
});
|
||||
|
||||
server->Post("/api/log/", [](const httplib::Request& req, httplib::Response& res)
|
||||
auto setup_handler = [&container, &devices_filename](const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
json response;
|
||||
|
||||
if (!req.has_header("ID"))
|
||||
{
|
||||
// Bad Request - No ID header
|
||||
res.status = 400;
|
||||
|
||||
response["status"] = 400;
|
||||
response["error"] = "No ID header";
|
||||
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.body = response.dump();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh data from file
|
||||
// Someone might have put a new device in
|
||||
reload_container(container, devices_filename);
|
||||
|
||||
string id = req.get_header_value("ID");
|
||||
TRMNL* trmnl = container.get_device_by_id(id);
|
||||
|
||||
if (nullptr == trmnl)
|
||||
{
|
||||
res.status = 404;
|
||||
|
||||
response["status"] = 404;
|
||||
response["api_key"] = nullptr;
|
||||
response["friendly_id"] = nullptr;
|
||||
response["image_url"] = nullptr;
|
||||
response["filename"] = nullptr;
|
||||
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.body = response.dump();
|
||||
return;
|
||||
}
|
||||
|
||||
bool should_dump = false;
|
||||
|
||||
res.status = 200;
|
||||
if (trmnl->friendly_id().empty())
|
||||
{
|
||||
should_dump = true;
|
||||
trmnl->friendly_id(TRMNL::friendly_from_id(id));
|
||||
}
|
||||
|
||||
if (trmnl->api_key().empty())
|
||||
{
|
||||
should_dump = true;
|
||||
trmnl->api_key(generate_api_key());
|
||||
}
|
||||
|
||||
response["status"] = 200;
|
||||
response["api_key"] = trmnl->api_key();
|
||||
response["friendly_id"] = trmnl->friendly_id();
|
||||
response["image_url"] = DEFAULT_IMAGE_URL;
|
||||
response["filename"] = DEFAULT_IMAGE_FNAME;
|
||||
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.body = response.dump();
|
||||
|
||||
if (should_dump)
|
||||
{
|
||||
write_file_json(container, devices_filename, &cout);
|
||||
}
|
||||
};
|
||||
|
||||
auto display_handler = [&container, &devices_filename, &folder_images, &base_url]
|
||||
(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
json response;
|
||||
|
||||
if (!req.has_header("ID") || !req.has_header("Access-Token"))
|
||||
{
|
||||
// Bad Request
|
||||
res.status = 400;
|
||||
|
||||
response["status"] = 400;
|
||||
response["error"] = "No ID and Access-Token headers set";
|
||||
|
||||
res.body = response.dump();
|
||||
res.set_header("Content-Type", "application/json");
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh data from file
|
||||
// Someone might have put a new device in
|
||||
reload_container(container, devices_filename);
|
||||
|
||||
string id = req.get_header_value("ID");
|
||||
string api_key = req.get_header_value("Access-Token");
|
||||
TRMNL* trmnl = container.get_device_by_id(id);
|
||||
|
||||
if (nullptr == trmnl)
|
||||
{
|
||||
// Not Found
|
||||
res.status = 404;
|
||||
|
||||
response["status"] = 404;
|
||||
response["error"] = "Device not found";
|
||||
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.body = response.dump();
|
||||
return;
|
||||
}
|
||||
|
||||
if (trmnl->api_key() != api_key)
|
||||
{
|
||||
// Forbidden
|
||||
res.status = 403;
|
||||
|
||||
response["status"] = 403;
|
||||
response["error"] = "Wrong credentials for this device";
|
||||
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.body = response.dump();
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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["reset_firmware"] = false;
|
||||
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.body = response.dump();
|
||||
};
|
||||
|
||||
auto log_handler = [](const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
try
|
||||
{
|
||||
json j = json::parse(req.body);
|
||||
cout << j.dump(4) << endl;
|
||||
}
|
||||
catch (const exception& e)
|
||||
{
|
||||
cout << req.body << endl;
|
||||
}
|
||||
res.status = 200;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
server->Get("/api/setup/*", setup_handler);
|
||||
server->Get("/api/display/*", display_handler);
|
||||
server->Post("/api/log/*", log_handler);
|
||||
|
||||
ok = server->set_mount_point("/images", folder_images);
|
||||
if (!ok)
|
||||
{
|
||||
cout << "Could not mount images folder" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
cout << "Server listening on " << host << ":" << port << endl;
|
||||
server->listen(host, port);
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user