Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/puara_basic_template/puara-application-settings.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

inline struct puara_application_settings
{
std::string ip = "192.168.2.12";
int port = 1234;
} settings;
60 changes: 35 additions & 25 deletions examples/puara_basic_template/puara_basic_template.ino
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#include <puara.h>
#include <puara-application-settings.hpp>
#include <format>
#include <iostream>
#include <reflect>

// Initialize Puara's module manager
Puara puara;

Expand All @@ -11,45 +15,51 @@ void onPuaraSettingsChanged()
// Process here any change of settings by the user, OSC port, etc.
}

void setup() {
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200);

puara.set_settings_changed_handler(onPuaraSettingsChanged);
/*
Serial.begin(115200);

puara.set_settings_changed_handler(onPuaraSettingsChanged);
/*
* the Puara start function initializes the filesystem, reads json configuration and
* settings, start the wi-fi AP or connects to SSID, starts the webserver, inits serial
* listening, MDNS service, and scans for WiFi networks.
*/
puara.start();
puara.start();

/*
/*
* Printing custom settings stored. The data/config.json values will print during
* Initialization (puara.start)
*/
Serial.println("\nSettings stored in data/settings.json:");
Serial.println("\nSettings stored in data/settings.json:");

Serial.print("Hitchhiker: ");
Serial.println(puara.getVarText("Hitchhiker").c_str());
Serial.print("Hitchhiker: ");
Serial.println(puara.getVarText("Hitchhiker").c_str());

Serial.print("answer_to_everything: ");
Serial.println(puara.getVarNumber("answer_to_everything"));
Serial.print("answer_to_everything: ");
Serial.println(puara.getVarNumber("answer_to_everything"));

Serial.print("variable3: ");
Serial.println(puara.getVarNumber("variable3"));
Serial.print("variable3: ");
Serial.println(puara.getVarNumber("variable3"));
}

void loop() {
void loop()
{

// put your main code here, to run repeatedly:
// Update the dummy sensor variable with random number
sensor = static_cast <float> (rand()) / (static_cast <float> (RAND_MAX/10));

// print the dummy sensor data
Serial.println();
Serial.print("Dummy sensor value: ");
Serial.println(sensor);

// run at 1 Hz (1 message per second)
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
// Update the dummy sensor variable with random number
sensor = static_cast<float>(rand()) / (static_cast<float>(RAND_MAX / 10));

// print the dummy sensor data
Serial.println();
Serial.print("Dummy sensor value: ");
Serial.println(sensor);
Serial.print("Dummy settings value: ");
Serial.println(settings.port);


// run at 1 Hz (1 message per second)
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
2 changes: 2 additions & 0 deletions src/puara.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <string>
#include <string_view>
#include <functional>
#include <reflect>


typedef void* httpd_handle_t;
class Puara
Expand Down
11 changes: 11 additions & 0 deletions src/puara_app.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#if !__has_include(<puara-application-settings.hpp>)
#error The project must have a puara-application-config.hpp file that defines the puara_application_settings structure.
struct puara_application_settings
{

} settings;
#endif

#include <puara-application-settings.hpp>
30 changes: 3 additions & 27 deletions src/puara_filesystem.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include "puara_filesystem.hpp"

#include "puara_app.hpp"
#include "puara_config.hpp"
#include "puara_logger.hpp"
#include "puara_settings.hpp"

#include <cJSON.h>

Expand Down Expand Up @@ -220,39 +222,13 @@ void JSONSettings::write_config_json()

void JSONSettings::write_settings_json()
{
cJSON* root = cJSON_CreateObject();
cJSON* settings = cJSON_CreateArray();
cJSON* setting = NULL;
cJSON* data = NULL;
cJSON_AddItemToObject(root, "settings", settings);

for(auto it : variables)
{
setting = cJSON_CreateObject();
cJSON_AddItemToArray(settings, setting);
data = cJSON_CreateString(it.name.c_str());
cJSON_AddItemToObject(setting, "name", data);
if(it.type == "text")
{
data = cJSON_CreateString(it.textValue.c_str());
}
else if(it.type == "number")
{
data = cJSON_CreateNumber(it.numberValue);
}
cJSON_AddItemToObject(setting, "value", data);
}

// Save to settings.json
LOG("write_settings_json: Serializing json");
std::string contents = cJSON_Print(root);
std::string contents = as_json(::settings);
LOG("Filesystem: Saving file");

fs.write_file("/settings.json", contents);

LOG("write_settings_json: Delete json entity");
cJSON_Delete(root);

if(this->on_settings_changed)
this->on_settings_changed();
}
Expand Down
4 changes: 2 additions & 2 deletions src/puara_serial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ void Serial::uart_monitor()
uart_num0, serial_data, PUARA_SERIAL_BUFSIZE, 500 / portTICK_RATE_MS);
if(serial_data_length > 0)
{
serial_data_str = convertToString(serial_data);
serial_data_str = serial_data;
memset(serial_data, 0, sizeof serial_data);
uart_flush(uart_num0);
}
Expand All @@ -168,7 +168,7 @@ void Serial::jtag_monitor()
serial_data, PUARA_SERIAL_BUFSIZE, 500 / portTICK_RATE_MS);
if(serial_data_length > 0)
{
serial_data_str = convertToString(serial_data);
serial_data_str = serial_data;
// remove new line character at end
if(serial_data_str[serial_data_str.size() - 1] == '\n')
serial_data_str.erase(serial_data_str.size() - 1);
Expand Down
176 changes: 176 additions & 0 deletions src/puara_settings.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#pragma once
#include <puara_utils.hpp>

#include <reflect>
#include <string>
#include <string_view>

namespace PuaraAPI
{
// Takes an arbitrary struct and returns a corresponding JSON
static inline std::string as_json(const auto& f)
{
int sz = 2;
int count = 0;

// 1. Compute how much space we need for our json
reflect::for_each([&sz, &count, &f](auto I) {
using member_type = std::decay_t<decltype(reflect::get<I>(f))>;

sz += 3; // "":
sz += reflect::member_name<I>(f).size();
if constexpr(std::is_same_v<member_type, bool>)
{
sz += 5;
}
else if constexpr(std::is_integral_v<member_type>)
{
sz += 8;
}
else if constexpr(requires { std::string_view{reflect::get<I>(f)}; })
{
sz += std::string_view{reflect::get<I>(f)}.size();
sz += 2;
}
sz += 1; // ,
count++;
}, f);

// 2. Write our json to a string
std::string res;
res.reserve(sz);
res += '{';
reflect::for_each([&count, &res, &f](auto I) {
using member_type = std::decay_t<decltype(reflect::get<I>(f))>;

res += '"';
res += reflect::member_name<I>(f);
res += "\":";

// 2. Append the value
if constexpr(std::is_same_v<member_type, bool>)
{
res += (reflect::get<I>(f) ? "true" : "false");
}
else if constexpr(std::is_integral_v<member_type>)
{
res += std::to_string(reflect::get<I>(f));
}
else if constexpr(requires { std::string_view{reflect::get<I>(f)}; })
{
res += '"';
res += reflect::get<I>(f);
res += '"';
}
else
{
static_assert(I.is_not_a_supported_type);
}

if(--count > 0)
{
res += ',';
}
}, f);
res += '}';
return res;
}

// Matches a HTTP request list of arguments to a structure and update it
static inline void settings_from_http_request(auto& settings, std::string_view request)
{
static constexpr std::string_view delimiter = "&";
static constexpr std::string_view field_delimiter = "=";

size_t pos = 0;

// Iterate over the request string view
while(!request.empty())
{
// 1. Extract the current "key=value" token
pos = request.find(delimiter);
std::string_view token
= (pos == std::string_view::npos) ? request : request.substr(0, pos);

// Advance the view for the next iteration
if(pos != std::string_view::npos)
{
request.remove_prefix(pos + delimiter.size());
}
else
{
request = {}; // Reached the end
}

// 2. Parse Key and Value
size_t field_pos = token.find(field_delimiter);
if(field_pos != std::string_view::npos)
{
std::string_view key = token.substr(0, field_pos);
std::string_view raw_value = token.substr(field_pos + field_delimiter.size());

// Decode URL-encoded value (converting view to string for the decoder)
std::string value = urlDecode(std::string(raw_value));

// 3. Reflect over struct members to find the match
bool found = false;
reflect::for_each([&](auto I) {
// Optimization: stop checking if we already found the member
if(found)
return;

// Check if struct member name matches the HTTP key
if(reflect::member_name<I>(settings) == key)
{
using member_type = std::decay_t<decltype(reflect::get<I>(settings))>;
auto& member = reflect::get<I>(settings);

// 4. Assign value based on type
if constexpr(std::is_same_v<member_type, bool>)
{
// Handle "true", "1", "on" vs everything else
member = (value == "true" || value == "1" || value == "on");
}
else if constexpr(std::is_integral_v<member_type>)
{
if(!value.empty())
{
try
{
if constexpr(std::is_signed_v<member_type>)
member = static_cast<member_type>(std::stoll(value));
else
member = static_cast<member_type>(std::stoull(value));
}
catch(...)
{ /* Handle parsing error or keep default */
}
}
}
else if constexpr(std::is_floating_point_v<member_type>)
{
if(!value.empty())
{
try
{
member = static_cast<member_type>(std::stod(value));
}
catch(...)
{
}
}
}
else if constexpr(requires {
member = std::string{};
}) // Check if assignable from string
{
member = value;
}

found = true;
}
}, settings);
}
}
}
}
12 changes: 4 additions & 8 deletions src/puara_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,7 @@ void checkmark(std::string old_text, bool value, std::string& str)
}
}

std::string convertToString(char* a)
{
std::string s(a);
return s;
}

std::string urlDecode(std::string text)
std::string urlDecode(std::string_view text)
{
std::string escaped;
for(auto i = text.begin(), nd = text.end(); i < nd; ++i)
Expand All @@ -81,14 +75,16 @@ std::string urlDecode(std::string text)
case '%':
if(i[1] && i[2])
{
char hs[]{i[1], i[2]};
char hs[]{i[1], i[2], 0};
escaped += static_cast<char>(strtol(hs, nullptr, 16));
i += 2;
}
break;
case '+':
escaped += ' ';
break;
case 0:
break;
default:
escaped += c;
}
Expand Down
Loading
Loading