From d07b5e537e64b1784db7faf4a39350cddb1c0a40 Mon Sep 17 00:00:00 2001 From: Matti Airas Date: Thu, 19 Sep 2024 23:38:49 +0300 Subject: [PATCH 1/7] Rename signalk_position to signalk_types Implement ENUVector and AttitudeVector and provide serializers for them. --- src/sensesp/signalk/signalk_position.cpp | 30 ---------------- src/sensesp/signalk/signalk_position.h | 18 ---------- src/sensesp/signalk/signalk_types.cpp | 46 ++++++++++++++++++++++++ src/sensesp/signalk/signalk_types.h | 26 ++++++++++++++ 4 files changed, 72 insertions(+), 48 deletions(-) delete mode 100644 src/sensesp/signalk/signalk_position.cpp delete mode 100644 src/sensesp/signalk/signalk_position.h create mode 100644 src/sensesp/signalk/signalk_types.cpp create mode 100644 src/sensesp/signalk/signalk_types.h diff --git a/src/sensesp/signalk/signalk_position.cpp b/src/sensesp/signalk/signalk_position.cpp deleted file mode 100644 index 498cf6123..000000000 --- a/src/sensesp/signalk/signalk_position.cpp +++ /dev/null @@ -1,30 +0,0 @@ - -#include "signalk_position.h" - -namespace sensesp { - -/** - * @brief Template specialization for SKOutputPosition::as_signalk() - * - * This specialization allows `Position` objects to be output as Signal K - * deltas. - * - * @tparam - * @return String - */ -template <> -String SKOutput::as_signalk() { - JsonDocument json_doc; - String json; - json_doc["path"] = this->get_sk_path(); - JsonObject value = json_doc["value"].to(); - value["latitude"] = output.latitude; - value["longitude"] = output.longitude; - if (output.altitude != kPositionInvalidAltitude) { - value["altitude"] = output.altitude; - } - serializeJson(json_doc, json); - return json; -} - -} // namespace sensesp diff --git a/src/sensesp/signalk/signalk_position.h b/src/sensesp/signalk/signalk_position.h deleted file mode 100644 index 0cc9312a3..000000000 --- a/src/sensesp/signalk/signalk_position.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SENSESP_SRC_SENSESP_SIGNALK_SIGNALK_POSITION_H_ -#define SENSESP_SRC_SENSESP_SIGNALK_SIGNALK_POSITION_H_ - -#include - -#include "sensesp/signalk/signalk_output.h" -#include "sensesp/types/position.h" - -namespace sensesp { - -template <> -String SKOutput::as_signalk(); - -typedef SKOutput SKOutputPosition; - -} // namespace sensesp - -#endif diff --git a/src/sensesp/signalk/signalk_types.cpp b/src/sensesp/signalk/signalk_types.cpp new file mode 100644 index 000000000..69f4bd3da --- /dev/null +++ b/src/sensesp/signalk/signalk_types.cpp @@ -0,0 +1,46 @@ + +#include "signalk_types.h" + +namespace sensesp { + +/** + * @brief Template specialization for SKOutputPosition::as_signalk_json() + * + * This specialization allows `Position` objects to be output as Signal K + * deltas. + * + * @tparam + * @return String + */ +template <> +void SKOutput::as_signalk_json(JsonDocument& doc) { + doc["path"] = this->get_sk_path(); + JsonObject value = doc["value"].to(); + value["latitude"] = output.latitude; + value["longitude"] = output.longitude; + if (output.altitude != kPositionInvalidAltitude) { + value["altitude"] = output.altitude; + } +} + +template <> +void SKOutput::as_signalk_json(JsonDocument& doc) { + doc["path"] = this->get_sk_path(); + JsonObject value = doc["value"].to(); + value["east"] = output.east; + value["north"] = output.north; + if (output.up != kPositionInvalidAltitude) { + value["up"] = output.up; + } +} + +template <> +void SKOutput::as_signalk_json(JsonDocument& doc) { + doc["path"] = this->get_sk_path(); + JsonObject value = doc["value"].to(); + value["roll"] = output.roll; + value["pitch"] = output.pitch; + value["yaw"] = output.yaw; +} + +} // namespace sensesp diff --git a/src/sensesp/signalk/signalk_types.h b/src/sensesp/signalk/signalk_types.h new file mode 100644 index 000000000..c41ad9218 --- /dev/null +++ b/src/sensesp/signalk/signalk_types.h @@ -0,0 +1,26 @@ +#ifndef SENSESP_SRC_SENSESP_SIGNALK_SIGNALK_TYPES_H_ +#define SENSESP_SRC_SENSESP_SIGNALK_SIGNALK_TYPES_H_ + +#include + +#include "sensesp/signalk/signalk_output.h" +#include "sensesp/types/position.h" + +namespace sensesp { + +template <> +void SKOutput::as_signalk_json(JsonDocument& doc); + +template <> +void SKOutput::as_signalk_json(JsonDocument& doc); + +template <> +void SKOutput::as_signalk_json(JsonDocument& doc); + +typedef SKOutput SKOutputPosition; +typedef SKOutput SKOutputENUVector; +typedef SKOutput SKOutputAttitudeVector; + +} // namespace sensesp + +#endif From 3428f73e646ecb29c6dbe079205d5053cbfea65f Mon Sep 17 00:00:00 2001 From: Matti Airas Date: Thu, 19 Sep 2024 23:36:32 +0300 Subject: [PATCH 2/7] Output free memory in system status screen. --- src/sensesp_app.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sensesp_app.h b/src/sensesp_app.h index b498d2208..21142507c 100644 --- a/src/sensesp_app.h +++ b/src/sensesp_app.h @@ -146,8 +146,11 @@ class SensESPApp : public SensESPBaseApp { new UIOutput("MAC Address", WiFi.macAddress(), "Network", 1100); UILambdaOutput* wifi_ssid_ui_output_ = new UILambdaOutput( "SSID", [this]() { return WiFi.SSID(); }, "Network", 1200); + UILambdaOutput* free_memory_ui_output_ = new UILambdaOutput( + "Free memory (bytes)", []() { return String(ESP.getFreeHeap()); }, "System", + 1250); UILambdaOutput* wifi_rssi_ui_output_ = new UILambdaOutput( - "WiFi signal strength", [this]() { return WiFi.RSSI(); }, "Network", + "WiFi signal strength (dB)", [this]() { return WiFi.RSSI(); }, "Network", 1300); UILambdaOutput* sk_server_address_ui_output_ = new UILambdaOutput( From 632d276bbdc987555821a202c768eaeeaf81b2b3 Mon Sep 17 00:00:00 2001 From: Matti Airas Date: Thu, 19 Sep 2024 23:38:07 +0300 Subject: [PATCH 3/7] Remove last remaining ReactESP singleton refs --- src/sensesp/system/async_response_handler.h | 22 +++++----- src/sensesp/system/stream_producer.h | 47 +++++++++++---------- src/sensesp/transforms/repeat.h | 12 +++--- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/sensesp/system/async_response_handler.h b/src/sensesp/system/async_response_handler.h index 2f2ad142c..bffb59976 100644 --- a/src/sensesp/system/async_response_handler.h +++ b/src/sensesp/system/async_response_handler.h @@ -6,6 +6,7 @@ #include #include +#include "sensesp_base_app.h" #include "valueproducer.h" namespace sensesp { @@ -49,17 +50,18 @@ class AsyncResponseHandler : public ValueConsumer, this->emit(status_); if (timeout_event_ != nullptr) { - reactesp::EventLoop::app->remove(timeout_event_); + SensESPBaseApp::get_event_loop()->remove(timeout_event_); timeout_event_ = nullptr; } - timeout_event_ = reactesp::EventLoop::app->onDelay(timeout_, [this]() { - if (status_ == AsyncResponseStatus::kPending) { - ESP_LOGV("AsyncResponseHandler", "Timeout"); - status_ = AsyncResponseStatus::kTimeout; - this->emit(status_); - } - this->timeout_event_ = nullptr; - }); + timeout_event_ = + SensESPBaseApp::get_event_loop()->onDelay(timeout_, [this]() { + if (status_ == AsyncResponseStatus::kPending) { + ESP_LOGV("AsyncResponseHandler", "Timeout"); + status_ = AsyncResponseStatus::kTimeout; + this->emit(status_); + } + this->timeout_event_ = nullptr; + }); } void set(const bool& success) override { @@ -72,7 +74,7 @@ class AsyncResponseHandler : public ValueConsumer, // Clear the timeout event if (timeout_event_ != nullptr) { - reactesp::EventLoop::app->remove(timeout_event_); + SensESPBaseApp::get_event_loop()->remove(timeout_event_); timeout_event_ = nullptr; } diff --git a/src/sensesp/system/stream_producer.h b/src/sensesp/system/stream_producer.h index e9bbac6b4..f600a9a01 100644 --- a/src/sensesp/system/stream_producer.h +++ b/src/sensesp/system/stream_producer.h @@ -5,6 +5,7 @@ #include "ReactESP.h" #include "sensesp/system/valueproducer.h" +#include "sensesp_base_app.h" namespace sensesp { @@ -14,12 +15,13 @@ namespace sensesp { class StreamCharProducer : public ValueProducer { public: StreamCharProducer(Stream* stream) : stream_{stream} { - read_event_ = reactesp::EventLoop::app->onAvailable(*stream_, [this]() { - while (stream_->available()) { - char c = stream_->read(); - this->emit(c); - } - }); + read_event_ = + SensESPBaseApp::get_event_loop()->onAvailable(*stream_, [this]() { + while (stream_->available()) { + char c = stream_->read(); + this->emit(c); + } + }); } protected: @@ -36,23 +38,24 @@ class StreamLineProducer : public ValueProducer { : stream_{stream}, max_line_length_{max_line_length} { static int buf_pos = 0; buf_ = new char[max_line_length_ + 1]; - read_event_ = reactesp::EventLoop::app->onAvailable(*stream_, [this]() { - while (stream_->available()) { - char c = stream_->read(); - if (c == '\n') { - // Include the newline character in the output - buf_[buf_pos++] = c; - buf_[buf_pos] = '\0'; - this->emit(buf_); - buf_pos = 0; - } else { - buf_[buf_pos++] = c; - if (buf_pos >= max_line_length_ - 1) { - buf_pos = 0; + read_event_ = + SensESPBaseApp::get_event_loop()->onAvailable(*stream_, [this]() { + while (stream_->available()) { + char c = stream_->read(); + if (c == '\n') { + // Include the newline character in the output + buf_[buf_pos++] = c; + buf_[buf_pos] = '\0'; + this->emit(buf_); + buf_pos = 0; + } else { + buf_[buf_pos++] = c; + if (buf_pos >= max_line_length_ - 1) { + buf_pos = 0; + } + } } - } - } - }); + }); } protected: diff --git a/src/sensesp/transforms/repeat.h b/src/sensesp/transforms/repeat.h index 585ddbf4d..2e7504b27 100644 --- a/src/sensesp/transforms/repeat.h +++ b/src/sensesp/transforms/repeat.h @@ -33,7 +33,7 @@ class Repeat : public SymmetricTransform { // Delete the old repeat event repeat_event_->remove(); } - repeat_event_ = reactesp::EventLoop::app->onRepeat( + repeat_event_ = SensESPBaseApp::get_event_loop()->onRepeat( interval_, [this]() { this->notify(); }); } @@ -63,7 +63,7 @@ class RepeatStopping : public Repeat { // Delete the old repeat event this->repeat_event_->remove(); } - this->repeat_event_ = reactesp::EventLoop::app->onRepeat( + this->repeat_event_ = SensESPBaseApp::get_event_loop()->onRepeat( this->interval_, [this]() { this->repeat_function(); }); } @@ -74,7 +74,7 @@ class RepeatStopping : public Repeat { // Delete the old repeat event this->repeat_event_->remove(); } - this->repeat_event_ = reactesp::EventLoop::app->onRepeat( + this->repeat_event_ = SensESPBaseApp::get_event_loop()->onRepeat( this->interval_, [this]() { this->repeat_function(); }); } @@ -113,7 +113,7 @@ class RepeatExpiring : public Repeat { // Delete the old repeat event this->repeat_event_->remove(); } - this->repeat_event_ = reactesp::EventLoop::app->onRepeat( + this->repeat_event_ = SensESPBaseApp::get_event_loop()->onRepeat( this->interval_, [this]() { this->repeat_function(); }); } @@ -124,7 +124,7 @@ class RepeatExpiring : public Repeat { // Delete the old repeat event this->repeat_event_->remove(); } - this->repeat_event_ = reactesp::EventLoop::app->onRepeat( + this->repeat_event_ = SensESPBaseApp::get_event_loop()->onRepeat( this->interval_, [this]() { this->repeat_function(); }); } @@ -166,7 +166,7 @@ class RepeatConstantRate : public RepeatExpiring { this->repeat_event_->remove(); } - this->repeat_event_ = reactesp::EventLoop::app->onRepeat( + this->repeat_event_ = SensESPBaseApp::get_event_loop()->onRepeat( interval, [this]() { this->repeat_function(); }); } From d9ef15d5d993353f0b25b98807f7c4dc3b9bb435 Mon Sep 17 00:00:00 2001 From: Matti Airas Date: Thu, 19 Sep 2024 23:36:04 +0300 Subject: [PATCH 4/7] Utilize ArduinoJson's automatic serialization Provide serializers and deserializers for key classes. --- README.md | 4 +++- src/sensesp/signalk/signalk_delta_queue.cpp | 9 +++++++-- src/sensesp/signalk/signalk_emitter.h | 8 ++++++-- src/sensesp/signalk/signalk_output.h | 20 ++++++-------------- src/sensesp/signalk/signalk_time.cpp | 10 +++------- src/sensesp/signalk/signalk_time.h | 2 +- 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 79254bda8..64c464b43 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,9 @@ - Implement stream producers that emit characters or lines from a stream (e.g., a serial port) -- +- SKEmitter::as_signalk() has been renamed to SKEmitter::as_signalk_json(). + It now writes its output to a JsonDocument reference instead + of returning a String. ### Migrating Existing Projects diff --git a/src/sensesp/signalk/signalk_delta_queue.cpp b/src/sensesp/signalk/signalk_delta_queue.cpp index 604a3f91c..b8a850265 100644 --- a/src/sensesp/signalk/signalk_delta_queue.cpp +++ b/src/sensesp/signalk/signalk_delta_queue.cpp @@ -39,8 +39,13 @@ void SKDeltaQueue::append(const String& val) { void SKDeltaQueue::connect_emitters() { for (auto const& sk_source : SKEmitter::get_sources()) { if (sk_source->get_sk_path() != "") { - sk_source->attach( - [sk_source, this]() { this->append(sk_source->as_signalk()); }); + sk_source->attach([sk_source, this]() { + String output; + JsonDocument doc; + sk_source->as_signalk_json(doc); + serializeJson(doc, output); + this->append(output); + }); } } } diff --git a/src/sensesp/signalk/signalk_emitter.h b/src/sensesp/signalk/signalk_emitter.h index 431ade2d4..ed5e05ae0 100644 --- a/src/sensesp/signalk/signalk_emitter.h +++ b/src/sensesp/signalk/signalk_emitter.h @@ -29,9 +29,13 @@ class SKEmitter : virtual public Observable { /** * Returns the data to be reported to the server as - * a Signal K json string. + * a Signal K json object. */ - virtual String as_signalk() { return "not implemented"; } + virtual void as_signalk_json(JsonDocument& doc) { + ESP_LOGE("SKEmitter", "as_signalk_json() not implemented"); + JsonVariant obj = doc.as(); + obj.set("ERROR! Not implemented"); + } /** * Returns a Metadata structure that describes the sk_path this SKEmitter diff --git a/src/sensesp/signalk/signalk_output.h b/src/sensesp/signalk/signalk_output.h index c095ef8b2..247705deb 100644 --- a/src/sensesp/signalk/signalk_output.h +++ b/src/sensesp/signalk/signalk_output.h @@ -46,13 +46,9 @@ class SKOutput : public SKEmitter, public SymmetricTransform { this->ValueProducer::emit(new_value); } - virtual String as_signalk() override { - JsonDocument json_doc; - String json; - json_doc["path"] = this->get_sk_path(); - json_doc["value"] = ValueProducer::output; - serializeJson(json_doc, json); - return json; + virtual void as_signalk_json(JsonDocument& doc) override { + doc["path"] = this->get_sk_path(); + doc["value"] = ValueProducer::output; } virtual void get_configuration(JsonObject& root) override { @@ -92,13 +88,9 @@ class SKOutputRawJson : public SKOutput { SKMetadata* meta = NULL) : SKOutput(sk_path, config_path, meta) {} - virtual String as_signalk() override { - JsonDocument json_doc; - String json; - json_doc["path"] = this->get_sk_path(); - json_doc["value"] = serialized(ValueProducer::output); - serializeJson(json_doc, json); - return json; + virtual void as_signalk_json(JsonDocument& doc) override { + doc["path"] = this->get_sk_path(); + doc["value"] = serialized(ValueProducer::output); } }; diff --git a/src/sensesp/signalk/signalk_time.cpp b/src/sensesp/signalk/signalk_time.cpp index 95dae64ff..cbfda878f 100644 --- a/src/sensesp/signalk/signalk_time.cpp +++ b/src/sensesp/signalk/signalk_time.cpp @@ -8,13 +8,9 @@ SKOutputTime::SKOutputTime(const String& sk_path, const String& config_path) load_configuration(); } -String SKOutputTime::as_signalk() { - JsonDocument json_doc; - String json; - json_doc["path"] = this->sk_path_; - json_doc["value"] = output; - serializeJson(json_doc, json); - return json; +void SKOutputTime::as_signalk_json(JsonDocument& doc){ + doc["path"] = this->sk_path_; + doc["value"] = output; } void SKOutputTime::get_configuration(JsonObject& doc) { diff --git a/src/sensesp/signalk/signalk_time.h b/src/sensesp/signalk/signalk_time.h index dda0e32a5..90fd021c0 100644 --- a/src/sensesp/signalk/signalk_time.h +++ b/src/sensesp/signalk/signalk_time.h @@ -9,7 +9,7 @@ namespace sensesp { class SKOutputTime : public TimeString, public SKEmitter { public: SKOutputTime(const String& sk_path, const String& config_path = ""); - virtual String as_signalk() override; + virtual void as_signalk_json(JsonDocument& doc) override; virtual void get_configuration(JsonObject& doc) override; virtual bool set_configuration(const JsonObject& config) override; virtual String get_config_schema() override; From 9dfa98b02ae18048d6e1841ce7a97cbad5c522e8 Mon Sep 17 00:00:00 2001 From: Matti Airas Date: Thu, 19 Sep 2024 23:39:23 +0300 Subject: [PATCH 5/7] Implement serializers for std::vector and position types --- src/sensesp/types/json.h | 31 ++++++++++++++++++++ src/sensesp/types/position.cpp | 52 ++++++++++++++++++++++++++++++++++ src/sensesp/types/position.h | 38 ++++++++++++++++++++++++- 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/sensesp/types/json.h diff --git a/src/sensesp/types/json.h b/src/sensesp/types/json.h new file mode 100644 index 000000000..131a66159 --- /dev/null +++ b/src/sensesp/types/json.h @@ -0,0 +1,31 @@ +#ifndef SENSESP_SRC_SENSESP_TYPES_JSON_H_ +#define SENSESP_SRC_SENSESP_TYPES_JSON_H_ + +#include + +#include "ArduinoJson.h" + +namespace ArduinoJson { +template +struct Converter > { + static void toJson(const std::vector& src, JsonVariant dst) { + JsonArray array = dst.to(); + for (T item : src) array.add(item); + } + + static std::vector fromJson(JsonVariantConst src) { + std::vector dst; + for (T item : src.as()) dst.push_back(item); + return dst; + } + + static bool checkJson(JsonVariantConst src) { + JsonArrayConst array = src; + bool result = array; + for (JsonVariantConst item : array) result &= item.is(); + return result; + } +}; +} // namespace ARDUINOJSON_NAMESPACE + +#endif // SENSESP_SRC_SENSESP_TYPES_JSON_H_ diff --git a/src/sensesp/types/position.cpp b/src/sensesp/types/position.cpp index 873f5a5bc..16df87fac 100644 --- a/src/sensesp/types/position.cpp +++ b/src/sensesp/types/position.cpp @@ -13,8 +13,60 @@ void convertFromJson(JsonVariantConst src, Position &dst) { } } +void convertFromJson(JsonVariantConst src, ENUVector &dst) { + dst.east = src["east"].as(); + dst.north = src["north"].as(); + + if (src.containsKey("up")) { + dst.up = src["up"].as(); + } +} + +void convertFromJson(JsonVariantConst src, AttitudeVector &dst) { + dst.roll = src["roll"].as(); + dst.pitch = src["pitch"].as(); + dst.yaw = src["yaw"].as(); +} + bool canConvertFromJson(JsonVariantConst src, const Position & /*position*/) { return src.containsKey("latitude") && src.containsKey("longitude"); } +bool canConvertFromJson(JsonVariantConst src, const ENUVector & /*enu*/) { + return src.containsKey("east") && src.containsKey("north"); +} + +bool canConvertFromJson(JsonVariantConst src, + const AttitudeVector & /*attitude*/) { + return src.containsKey("roll") && src.containsKey("pitch") && + src.containsKey("yaw"); +} + +void convertToJson(const Position &src, JsonVariant dst) { + JsonObject obj = dst.to(); + obj["latitude"] = src.latitude; + obj["longitude"] = src.longitude; + + if (src.altitude != kPositionInvalidAltitude) { + obj["altitude"] = src.altitude; + } +} + +void convertToJson(const ENUVector &src, JsonVariant dst) { + JsonObject obj = dst.to(); + obj["east"] = src.east; + obj["north"] = src.north; + + if (src.up != kPositionInvalidAltitude) { + obj["up"] = src.up; + } +} + +void convertToJson(const AttitudeVector &src, JsonVariant dst) { + JsonObject obj = dst.to(); + obj["roll"] = src.roll; + obj["pitch"] = src.pitch; + obj["yaw"] = src.yaw; +} + } // namespace sensesp diff --git a/src/sensesp/types/position.h b/src/sensesp/types/position.h index 88a27e737..28af88e96 100644 --- a/src/sensesp/types/position.h +++ b/src/sensesp/types/position.h @@ -7,6 +7,7 @@ namespace sensesp { /// Value used to indicate an invalid or missing altitude +constexpr double kInvalidDouble = std::numeric_limits::lowest(); constexpr float kPositionInvalidAltitude = std::numeric_limits::lowest(); /** @@ -20,6 +21,11 @@ struct Position { double latitude; double longitude; float altitude = kPositionInvalidAltitude; + + Position() : latitude(kInvalidDouble), longitude(kInvalidDouble) {} + Position(double latitude, double longitude, + float altitude = kPositionInvalidAltitude) + : latitude(latitude), longitude(longitude), altitude(altitude) {} }; /** @@ -33,13 +39,35 @@ struct ENUVector { float east; float north; float up = kPositionInvalidAltitude; + + ENUVector() : east(kInvalidDouble), north(kInvalidDouble) {} + ENUVector(float east, float north, float up = kPositionInvalidAltitude) + : east(east), north(north), up(up) {} +}; + +/** + * @brief Container for attitude data. + * + * Roll, pitch, and yaw (heading) angles, in radians. + * + */ +struct AttitudeVector { + // Ordering chosen to match the order of fields in Signal K + float roll; + float pitch; + float yaw; // heading + + AttitudeVector() + : roll(kInvalidDouble), pitch(kInvalidDouble), yaw(kInvalidDouble) {} + AttitudeVector(float roll, float pitch, float yaw) + : roll(roll), pitch(pitch), yaw(yaw) {} }; /** * @brief Adds support in ArduinoJson to deserialize Position type data. * * This is an ArduinoJson custom converter to convert position type data from - * JSON into a Postion struct. The JSON data must at least contain "latitude" + * JSON into a Position struct. The JSON data must at least contain "latitude" * and "longitude", "altitude" is optional. ArduinoJson will automatically * call this function whenever it has to convert a JSON to a Position struct. * More info here: https://arduinojson.org/news/2021/05/04/version-6-18-0/ @@ -48,6 +76,8 @@ struct ENUVector { * @param dst The address to a Position struct to write the data to */ void convertFromJson(JsonVariantConst src, Position &dst); +void convertFromJson(JsonVariantConst src, ENUVector &dst); +void convertFromJson(JsonVariantConst src, AttitudeVector &dst); /** * @brief Tells ArduinoJson whether the given JSON is a Position or not @@ -61,6 +91,12 @@ void convertFromJson(JsonVariantConst src, Position &dst); * keys */ bool canConvertFromJson(JsonVariantConst src, const Position &); +bool canConvertFromJson(JsonVariantConst src, const ENUVector &); +bool canConvertFromJson(JsonVariantConst src, const AttitudeVector &); + +void convertToJson(const Position &src, JsonVariant dst); +void convertToJson(const ENUVector &src, JsonVariant dst); +void convertToJson(const AttitudeVector &src, JsonVariant dst); } // namespace sensesp From 0b56948f316f89208dce6a128bd1a5940b5fbcde Mon Sep 17 00:00:00 2001 From: Matti Airas Date: Thu, 19 Sep 2024 23:39:56 +0300 Subject: [PATCH 6/7] Implement a nullable class for types that hold a special "invalid" value --- src/sensesp/types/nullable.cpp | 14 +++++++ src/sensesp/types/nullable.h | 76 ++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/sensesp/types/nullable.cpp create mode 100644 src/sensesp/types/nullable.h diff --git a/src/sensesp/types/nullable.cpp b/src/sensesp/types/nullable.cpp new file mode 100644 index 000000000..0da4a40a4 --- /dev/null +++ b/src/sensesp/types/nullable.cpp @@ -0,0 +1,14 @@ +#include "nullable.h" + +#include +#include + +namespace sensesp { + +template T Nullable::invalid_value_ = T{}; +template <> int Nullable::invalid_value_ = std::numeric_limits::lowest(); +template <> float Nullable::invalid_value_ = std::numeric_limits::lowest(); +template <> double Nullable::invalid_value_ = std::numeric_limits::lowest(); +template <> char Nullable::invalid_value_ = 255; + +} // namespace sensesp diff --git a/src/sensesp/types/nullable.h b/src/sensesp/types/nullable.h new file mode 100644 index 000000000..dbb422cf0 --- /dev/null +++ b/src/sensesp/types/nullable.h @@ -0,0 +1,76 @@ +#ifndef SENSESP_SRC_SENSESP_TYPES_NULLABLE_H_ +#define SENSESP_SRC_SENSESP_TYPES_NULLABLE_H_ + +#include "ArduinoJson.h" + +namespace sensesp { + +/** + * @brief Template class that supports a special invalid magic value for a type. + * + * The invalid value is always provided by the type's default constructor. + * + */ +template +class Nullable { + public: + Nullable() : value_{} {} + Nullable(T value) : value_{value} {} + Nullable& operator=(T& value) { + value_ = value; + return *this; + } + Nullable& operator=(const Nullable& other) { + value_ = other.value_; + return *this; + } + operator T() const { + return value_; + } + + bool is_valid() const { + return value_ != invalid_value_; + } + + T* ptr() { + return &value_; + } + + static T invalid() { + return invalid_value_; + } + + T value() const { + return value_; + } + + private: + T value_; + static T invalid_value_; +}; + +typedef Nullable NullableInt; +typedef Nullable NullableFloat; +typedef Nullable NullableDouble; + +template +void convertFromJson(JsonVariantConst src, Nullable &dst) { + if (src.isNull()) { + dst = NullableInt::invalid(); + } else { + dst = src.as(); + } +} + +template +void convertToJson(const Nullable &src, JsonVariant dst) { + if (src.is_valid()) { + dst.set(src.value()); + } else { + dst.clear(); + } +} + +} // namespace sensesp + +#endif // SENSESP_SRC_SENSESP_TYPES_NULLABLE_H_ From 9eae6292ab93fd70734ac9957d967aed7cf76da7 Mon Sep 17 00:00:00 2001 From: Matti Airas Date: Thu, 19 Sep 2024 23:41:51 +0300 Subject: [PATCH 7/7] Make repeat transforms return nullable values --- src/sensesp/transforms/repeat.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sensesp/transforms/repeat.h b/src/sensesp/transforms/repeat.h index 2e7504b27..0fe0f592a 100644 --- a/src/sensesp/transforms/repeat.h +++ b/src/sensesp/transforms/repeat.h @@ -3,6 +3,7 @@ #include +#include "sensesp/types/nullable.h" #include "transform.h" namespace sensesp { @@ -103,10 +104,10 @@ class RepeatStopping : public Repeat { * @tparam T */ template -class RepeatExpiring : public Repeat { +class RepeatExpiring : public Repeat> { public: - RepeatExpiring(long interval, long max_age, T expired_value) - : Repeat(interval), max_age_{max_age}, expired_value_{expired_value} { + RepeatExpiring(long interval, long max_age) + : Repeat(interval), max_age_{max_age} { age_ = max_age; if (this->repeat_event_ != nullptr) { @@ -131,7 +132,6 @@ class RepeatExpiring : public Repeat { protected: elapsedMillis age_; long max_age_; - T expired_value_; protected: void repeat_function() { @@ -140,7 +140,7 @@ class RepeatExpiring : public Repeat { if (age_ < max_age_) { this->notify(); } else { - this->emit(expired_value_); + this->emit(Nullable::invalid()); } }; }; @@ -159,8 +159,8 @@ class RepeatExpiring : public Repeat { template class RepeatConstantRate : public RepeatExpiring { public: - RepeatConstantRate(long interval, long max_age, T expired_value) - : RepeatExpiring(interval, max_age, expired_value) { + RepeatConstantRate(long interval, long max_age) + : RepeatExpiring(interval, max_age) { if (this->repeat_event_ != nullptr) { // Delete the old repeat event this->repeat_event_->remove();