diff --git a/config.hpp b/config.hpp index 35157d7..f5483ed 100644 --- a/config.hpp +++ b/config.hpp @@ -24,6 +24,22 @@ using ::boost::system::errc::make_error_code; // if(auto _res = co_await (expr); !_res) \ // co_return std::unexpected(_res.error()) +#define SYNC_TRY(failable) \ +({ \ + auto _result = failable; \ + if(!_result) \ + return unexpected{_result.error()}; \ + *_result; \ +}) + +#define TRY(failable) \ + ({ \ + auto _result = failable; \ + if(!_result) \ + co_return unexpected{_result.error()}; \ + *_result; \ + }) + #define ASYNC_TRY(failable) \ ({ \ auto _result = co_await (failable); \ @@ -36,7 +52,7 @@ using ::boost::system::errc::make_error_code; ({ \ auto _result = co_await (failable); \ if(!_result) \ - co_return std::unexpected{transform(_result.error())}; \ + co_return unexpected{transform(_result.error())}; \ *_result; \ }) @@ -45,7 +61,7 @@ using ::boost::system::errc::make_error_code; auto _result = co_await (failable); \ if(!_result) { \ spdlog::error(__VA_ARGS__); \ - co_return std::unexpected{_result.error()}; \ + co_return unexpected{_result.error()}; \ } \ *_result; \ }) @@ -61,7 +77,7 @@ using ::boost::system::errc::make_error_code; do { \ auto _result = co_await (failable); \ if(!_result) \ - co_return std::unexpected(_result.error()); \ + co_return unexpected(_result.error()); \ } while(0) #define ASYNC_CHECK_MSG(failable, ...) \ @@ -69,7 +85,7 @@ using ::boost::system::errc::make_error_code; auto _result = co_await (failable); \ if(!_result) { \ spdlog::error(__VA_ARGS__); \ - co_return std::unexpected{_result.error()}; \ + co_return unexpected{_result.error()}; \ } \ } while(0) diff --git a/libs/mqtt_client.cpp b/libs/mqtt_client.cpp index 7545ea1..74a236a 100644 --- a/libs/mqtt_client.cpp +++ b/libs/mqtt_client.cpp @@ -221,6 +221,10 @@ struct AsyncMqttClient::AsyncMqttClientImpl { spdlog::trace("co_spawn mqtt client"); boost::asio::co_spawn(_mqtt_client.get_executor(), listen(), boost::asio::detached); } + + void cancel(){ + _mqtt_client.cancel(); + } }; AsyncMqttClient::AsyncMqttClient(const boost::asio::any_io_executor & executor) @@ -249,6 +253,14 @@ awaitable_expected AsyncMqttClient::listen() const noexcept{ co_return mqtt_expected_void{}; } +void AsyncMqttClient::cancel() +{ + BOOST_ASSERT(_impl); + spdlog::info("MQTT client cancel"); + + _impl->cancel(); +} + AsyncMqttClient::~AsyncMqttClient() = default; } // namespace ranczo diff --git a/libs/ranczo-io/utils/mqtt_client.hpp b/libs/ranczo-io/utils/mqtt_client.hpp index 84b3ce5..b678631 100644 --- a/libs/ranczo-io/utils/mqtt_client.hpp +++ b/libs/ranczo-io/utils/mqtt_client.hpp @@ -18,34 +18,42 @@ using executor = boost::asio::any_io_executor; namespace ranczo { +class IAsyncMqttClient{ + public: + + + +}; + class AsyncMqttClient { public: struct CallbackData { /* topic */ std::string_view topic; - + /* response topic */ std::optional< std::string_view > responseTopic; - + /* value assosiated to request */ const boost::json::value & value; }; using callback_t = std::function< awaitable_expected< void >(const boost::json::value & value) >; - + struct AsyncMqttClientImpl; std::unique_ptr< AsyncMqttClientImpl > _impl; AsyncMqttClient(const executor & executor); ~AsyncMqttClient(); - + /* subscribes to a topic, topic can contain wildcards */ - awaitable_expected< void > subscribe(std::string_view topic, callback_t &&cb) noexcept; + awaitable_expected< void > subscribe(std::string_view topic, callback_t && cb) noexcept; awaitable_expected< const boost::json::value & > request(std::string_view topic, const boost::json::value & value) noexcept; awaitable_expected< void > publish(std::string_view topic, const boost::json::value & value) noexcept; awaitable_expected< void > listen() const noexcept; + void cancel(); }; } // namespace ranczo diff --git a/services/floorheat_svc/heater_controller.cpp b/services/floorheat_svc/heater_controller.cpp index f522a80..90db0f9 100644 --- a/services/floorheat_svc/heater_controller.cpp +++ b/services/floorheat_svc/heater_controller.cpp @@ -1,6 +1,9 @@ #include "heater_controller.hpp" #include "config.hpp" +#include +#include +#include #include #include @@ -141,21 +144,36 @@ struct ResistiveFloorHeater::Impl : private boost::noncopyable { co_return heater_expected_void{}; } - inline double to_double(const boost::json::value & v) const { + inline expected< double, boost::system::error_code > to_double(const boost::json::value & v) const { if(v.is_double()) return v.as_double(); - else if(v.is_int64()) + if(v.is_int64()) return static_cast< double >(v.as_int64()); - else if(v.is_uint64()) + if(v.is_uint64()) return static_cast< double >(v.as_uint64()); - throw std::runtime_error("Invalid type for double conversion"); + if(v.is_string()) { + const auto & s = v.as_string(); + double out{}; + if(auto res = std::from_chars(s.data(), s.data() + s.size(), out); res.ec == std::errc{}) + return out; + } + return unexpected{make_error_code(boost::system::errc::invalid_argument)}; + } + + inline expected< double, boost::system::error_code > get_value(const boost::json::value & jv, std::string_view key) { + if(auto * obj = jv.if_object()) { + if(auto * pv = obj->if_contains(key)) { + return SYNC_TRY(to_double(*pv)); + } + } + return unexpected{make_error_code(boost::system::errc::invalid_argument)}; } awaitable_expected< void > subscribeToTemperatureUpdate() { auto topic = topic::temperature::floor(_room); auto cb = [=, this](const boost::json::value & object) -> awaitable_expected< void > { - temperature = to_double(object.at("value")); + temperature = TRY(get_value(object, "value")); spdlog::trace("Heater temperature update {} for {}", temperature, _room); _measurements.push_back({std::chrono::system_clock::now(), temperature}); update = true; @@ -163,42 +181,32 @@ struct ResistiveFloorHeater::Impl : private boost::noncopyable { }; ASYNC_CHECK(subscribe(topic, std::move(cb))); - co_return heater_expected_void{}; } awaitable_expected< void > subscribeToCommandUpdate() { auto topic = topic::heating::command(_room); - auto cb = [=, this](const boost::json::value & object) -> awaitable_expected< void > { - targetTemperature = to_double(object.at("value")); + auto cb = [=, this](const boost::json::value & jv) -> awaitable_expected< void > { + targetTemperature = TRY(get_value(jv, "value")); spdlog::trace("Heater target temperature update {} for {}", targetTemperature, _room); update = true; co_return heater_expected_void{}; }; ASYNC_CHECK(subscribe(topic, std::move(cb))); - - co_return heater_expected_void{}; - } - - awaitable_expected< void > subscribeToTargetProfileUpdate() { - auto topic = std::format("home/{}/floor/heating/profile/set", _room); - - auto cb = [=, this](const boost::json::value & object) -> awaitable_expected< void > { // - spdlog::warn("not implemented"); - co_return heater_expected_void{}; - }; - - ASYNC_CHECK(subscribe(topic, std::move(cb))); - co_return heater_expected_void{}; } awaitable_expected< void > start() { + // subscribe to a thermometer ASYNC_CHECK_MSG(subscribeToTemperatureUpdate(), "subscribe to temp update failed"); + + // subscribe to a thermostat ASYNC_CHECK_MSG(subscribeToCommandUpdate(), "subscribe to temp update failed"); - ASYNC_CHECK_MSG(subscribeToTargetProfileUpdate(), "subscribe to profile update failed"); + + /// TODO subscribe to energy measurements + /// ASYNC_CHECK_MSG(_tickTimer.start(), "failed to start timer"); ASYNC_CHECK_MSG(_mqtt_client.listen(), "failed to listen"); diff --git a/services/floorheat_svc/heater_controller.hpp b/services/floorheat_svc/heater_controller.hpp index d56ebd0..bedd70b 100644 --- a/services/floorheat_svc/heater_controller.hpp +++ b/services/floorheat_svc/heater_controller.hpp @@ -11,17 +11,17 @@ class any_io_executor; namespace ranczo { -class IHeater { +class IHeaterController { public: template < typename T > using awaitable = boost::asio::awaitable< T >; - virtual ~IHeater() = default; + virtual ~IHeaterController() = default; virtual awaitable_expected< void > start() noexcept = 0; }; -class ResistiveFloorHeater : public IHeater { +class ResistiveFloorHeater : public IHeaterController { struct Impl; std::unique_ptr< Impl > _impl; diff --git a/services/floorheat_svc/main.cpp b/services/floorheat_svc/main.cpp index e7bc266..46da8e6 100644 --- a/services/floorheat_svc/main.cpp +++ b/services/floorheat_svc/main.cpp @@ -44,7 +44,7 @@ void signal_handler(int signum) { int main() { spdlog::set_level(spdlog::level::trace); - std::vector< std::shared_ptr< ranczo::IHeater > > _heaters; + std::vector< std::shared_ptr< ranczo::IHeaterController > > _heaters; boost::asio::io_context io_context; g_io = &io_context;