diff --git a/CMakeLists.txt b/CMakeLists.txt index 10ba288..d5d2f44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,24 @@ set(BOOST_ROOT /usr/local/boost-1.89) find_package(Boost 1.89 REQUIRED COMPONENTS json mqtt5) # spdlog -set(SPDLOG_USE_STD_FORMAT ON) + +include(CheckCXXSourceCompiles) + +set(CHECK_STD_FORMAT_SRC " +#include +int main() { std::string s = std::format(\"{} {}\", 1, 2); return 0; } " +) + +check_cxx_source_compiles("${CHECK_STD_FORMAT_SRC}" HAS_STD_FORMAT) + +if (HAS_STD_FORMAT) + message(STATUS "Compiler supports → SPDLOG_USE_STD_FORMAT=ON") + set(SPDLOG_USE_STD_FORMAT ON CACHE BOOL "" FORCE) +else() + message(STATUS "Compiler does NOT support → SPDLOG_USE_STD_FORMAT=OFF") + set(SPDLOG_USE_STD_FORMAT OFF CACHE BOOL "" FORCE) +endif() + FetchContent_Declare( spdlog GIT_REPOSITORY https://github.com/gabime/spdlog diff --git a/libs/mqtt_client.cpp b/libs/mqtt_client.cpp index 88e3857..8cb7e65 100644 --- a/libs/mqtt_client.cpp +++ b/libs/mqtt_client.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -304,15 +305,15 @@ struct AsyncMqttClient::AsyncMqttClientImpl { if(responseTopic) { _log.trace("got response topic, handling it properly"); - // --- PMR dla response --- - std::optional< boost::json::value > response; + // --- PMR dla response --- + ResponseData response; std::array< std::uint8_t, 2048 > responseBuffer{0}; std::pmr::monotonic_buffer_resource mr_resp{responseBuffer.data(), responseBuffer.size()}; json::pmr_memory_resource_adapter adapter_resp(&mr_resp); boost::json::storage_ptr sp_resp(&adapter_resp); - response.emplace(boost::json::object_kind, sp_resp); - + boost::json::value r(sp_resp); + response = std::ref(r); // make a reference wrapper object // wywołanie callbacka z możliwością wypełnienia response if(auto result = co_await handler(cbdata, response); !result) { _log.warn("callback error: {}", result.error().message()); @@ -328,7 +329,7 @@ struct AsyncMqttClient::AsyncMqttClientImpl { } }else{ // wywołanie callbacka z pustym response - std::optional< boost::json::value > response; + ResponseData response; if(auto result = co_await handler(cbdata, response); !result) { _log.warn("callback error: {}", result.error().message()); } diff --git a/libs/ranczo-io/utils/mqtt_client.hpp b/libs/ranczo-io/utils/mqtt_client.hpp index 5003494..8c46da7 100644 --- a/libs/ranczo-io/utils/mqtt_client.hpp +++ b/libs/ranczo-io/utils/mqtt_client.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -31,8 +32,8 @@ class AsyncMqttClient { }; struct SubscribtionToken; - using ResponseData = std::optional< boost::json::value >; - + using ResponseData = std::optional< std::reference_wrapper< boost::json::value > >; + using callback_t = std::function< awaitable_expected< void >(const CallbackData & value, ResponseData & response) >; struct AsyncMqttClientImpl; diff --git a/libs/ranczo-io/utils/mqtt_topic_builder.hpp b/libs/ranczo-io/utils/mqtt_topic_builder.hpp index b97cc7f..bb6d932 100644 --- a/libs/ranczo-io/utils/mqtt_topic_builder.hpp +++ b/libs/ranczo-io/utils/mqtt_topic_builder.hpp @@ -1,35 +1,56 @@ #pragma once +#include + #include -#include #include #include #include -/* - "routing": { - "topics": { - // Full topic: home///// - "light_set": "home/{room}/output/light/{N}/set", - "light_status": "home/{room}/output/light/{N}/status", - "floor_set": "home/{room}/heating/floor/{N}/set", - "floor_status": "home/{room}/heating/floor/{N}/status", - "floor_temp": "home/{room}/sensor/floor/{N}/temperature", - "air_temp": "home/{room}/sensor/air/{N}/temperature" - } -*/ + +/** + * Topic layout assumptions + * + * General pattern: + * home/////[/command|/state] + * + * - Urządzenia sterowane (heating, relay, thermostat, etc.): + * - /state – aktualny stan publikowany przez urządzenie + * - /command – polecenia z zewnątrz (HA, Node-RED, itp.) + * + * - Sensory tylko-do-odczytu (temperature, humidity, etc.): + * - home//sensor///temperature + * – publikowane wyłącznie przez sensor, brak /command. + * + * Przykłady: + * + * 1) Termostat – zadana temperatura: + * home/office/heating/floor/1/setpoint/state + * – termostat publikuje aktualny setpoint + * + * home/office/heating/floor/1/setpoint/command + * – dowolny byt (HA, Node-RED) może ustawić nowy setpoint + * + * 2) Sensor temperatury podłogi: + * home/office/sensor/floor/2/temperature + * – sensor publikuje bieżącą temperaturę + * – nie istnieje topic .../temperature/command + * + * Uwaga: + * - Topic ...//state dostępny jedynie dla urządzeń które moge być sterowane + * Jest wyłącznie kanałem, na którym urządzenie publikuje swój stan. + */ + namespace ranczo { template < typename... Args > constexpr size_t length(const Args &... args) { return (args.size() + ...); } - static_assert(length(std::string_view{"asdf"}, std::string_view{"asdfgh"}) == 10); -// Główna funkcja 'join' template < typename... Args > -std::string make_topic(const Args &... args) { +std::pmr::string make_topic(std::pmr::memory_resource & mr, const Args &... args) { using namespace std::string_literals; constexpr size_t count = sizeof...(Args); if constexpr(count == 0) { @@ -41,182 +62,85 @@ std::string make_topic(const Args &... args) { std::array< std::string_view, count > segments{std::string_view(args)...}; size_t total_size = length(args...) + segments.size(); - std::string result; - result.reserve(total_size); - - result.append(segments[0]); - for(size_t i = 1; i < count; ++i) { - result.append(separator); - result.append(segments[i]); - } - - return result; -} - -template < typename... Args > -std::pmr::string make_topic(std::pmr::memory_resource &mr, const Args &... args) { - using namespace std::string_literals; - constexpr size_t count = sizeof...(Args); - if constexpr(count == 0) { - return ""; - } - - auto separator = R"(/)"s; - - std::array< std::string_view, count > segments{std::string_view(args)...}; - size_t total_size = length(args...) + segments.size(); - std::pmr::string result{&mr}; result.reserve(total_size); - + result.append(segments[0]); for(size_t i = 1; i < count; ++i) { result.append(separator); result.append(segments[i]); } - + return result; } // Full topic: home////{N}/ -inline std::string buildTopic(std::string_view room, std::string_view type, std::string_view place, uint number, std::string_view action) { - using namespace std::string_view_literals; - return make_topic("home"sv, room, type, place, std::to_string(number), action); -} -inline std::pmr::string buildTopic(std::pmr::memory_resource &mr, std::string_view room, std::string_view type, std::string_view place, uint number, std::string_view action) { +inline std::pmr::string buildPublishTopic(std::pmr::memory_resource & mr, + std::string_view room, + std::string_view type, + std::string_view place, + uint number, + std::string_view action) { using namespace std::string_view_literals; return make_topic(mr, "home"sv, room, type, place, std::to_string(number), action); } +enum CommandType { state, command }; +// Full topic: home////{N}//[state|command] +inline std::pmr::string buildCommandTopic(std::pmr::memory_resource & mr, + std::string_view room, + std::string_view type, + std::string_view place, + uint number, + std::string_view action, + CommandType commandType) { + using namespace std::string_view_literals; + if(commandType == CommandType::state) + return make_topic(mr, "home"sv, room, type, place, std::to_string(number), action, "state"sv); + + return make_topic(mr, "home"sv, room, type, place, std::to_string(number), action, "command"sv); +} + namespace topic { namespace heating { - // Topic: - // home//heating/floor//command - // - // ➤ Purpose: - // Accepts commands to set target floor temperature or heating mode. - // Payload: JSON with fields like {"target": 23.0, "mode": "manual"} - // - // ➤ Direction: SUBSCRIBE - // ➤ Request/Response: YES (ideal for MQTT v5 request/response pattern) - // - // ➤ Example: - // mqttClient.publish("home/bathroom/heating/floor//command", payload, { - // response_topic: "client/myid/response", - // correlation_data: "uuid-123" - // }); - inline std::string subscribeToCommand(std::string_view room, int zone, std::string_view command) { + // home//heating/floor///[command|status] + inline std::pmr::string subscribeToControl(std::string_view room, + int zone, + std::string_view command, + std::pmr::memory_resource * mr = std::pmr::get_default_resource()) { using namespace std::string_view_literals; - return buildTopic(room, "heating"sv, "floor"sv, zone, command); + BOOST_ASSERT(mr); + return buildCommandTopic(*mr, room, "heating"sv, "floor"sv, zone, command, CommandType::command); } - // Topic: // home//heating/floor//state - // - // ➤ Purpose: - // Publishes the current heating state periodically (every 60s or on change). - // Includes fields like: - // { - // "current": 21.4, - // "target": 23.0, - // "heating": true, - // "mode": "manual", - // "timestamp": "2025-08-06T12:34:56Z" - // } - // - // ➤ Direction: PUBLISH - // ➤ Retained: YES - // ➤ Request/Response: NOT used — this is a one-way state feed. - inline std::string state(std::string_view room, int zone =1) { + inline std::pmr::string + publishState(std::string_view room, int zone = 1, std::pmr::memory_resource * mr = std::pmr::get_default_resource()) { using namespace std::string_view_literals; - return buildTopic( room, "heating"sv, "floor"sv, zone,"state"sv); - } - inline std::pmr::string state(std::pmr::memory_resource&mr, std::string_view room, int zone =1) { - using namespace std::string_view_literals; - return buildTopic(mr, room, "heating"sv, "floor"sv, zone, "state"sv); - } - - // Topic: - // home//heating/floor//config - // - // ➤ Purpose: - // Publishes discovery metadata for Home Assistant or external tooling. - // JSON payload compatible with Home Assistant MQTT discovery: - // { - // "temperature_state_topic": "...", - // "temperature_command_topic": "...", - // "unique_id": "...", - // ... - // } - // - // ➤ Direction: PUBLISH - // ➤ Retained: YES - // ➤ Request/Response: NO — usually published once at startup. - // - // ➤ Tip: - // Consider sending this automatically on boot or after config change. - inline std::string config(std::string_view room, int zone =1) { - using namespace std::string_view_literals; - return buildTopic(room, "heating"sv, "floor"sv, zone, "config"sv); + BOOST_ASSERT(mr); + return buildPublishTopic(*mr, room, "heating"sv, "floor"sv, zone, "state"sv); } } // namespace heating namespace temperature { - // Topic: // home//sensor/floor//temperature - // - // ➤ Purpose: - // Publishes the current floor temperature measured by a sensor. - // Payload example: - // { - // "value": 22.4, - // "unit": "°C", - // "timestamp": "2025-08-06T14:35:00Z" - // } - // - // ➤ Direction: PUBLISH - // ➤ Retained: Optional (depending on freshness expectations) - // ➤ Request/Response: NO — this is state-only (event or periodic) - // - // ➤ Tip: - // Can be consumed by heating services, dashboards, or Home Assistant - inline std::string floor(std::string_view room, int zone) { - BOOST_ASSERT(zone>0);// no broadcast + inline std::pmr::string + publishFloor(std::string_view room, int zone, std::pmr::memory_resource * mr = std::pmr::get_default_resource()) { + BOOST_ASSERT(zone > 0); // no broadcast + BOOST_ASSERT(mr); using namespace std::string_view_literals; - return buildTopic(room, "sensor"sv, "floor"sv, zone, "temperature"sv); - } - inline std::pmr::string floor(std::pmr::memory_resource&mr, std::string_view room, int zone) { - BOOST_ASSERT(zone>0);// no broadcast - using namespace std::string_view_literals; - return buildTopic(mr, room, "sensor"sv, "floor"sv, zone, "temperature"sv); + return buildPublishTopic(*mr, room, "sensor"sv, "floor"sv, zone, "temperature"sv); } - // Topic: // home//sensor/air//temperature - // - // ➤ Purpose: - // Publishes the current air temperature in the room. - // Payload format is the same as for floor temperature: - // { - // "value": 23.1, - // "unit": "°C", - // "timestamp": "2025-08-06T14:35:10Z" - // } - // - // ➤ Direction: PUBLISH - // ➤ Retained: Optional (usually not unless used for dashboards) - // ➤ Request/Response: NO — meant for passive state reporting - inline std::string air(std::string_view room, int zone) { - BOOST_ASSERT(zone>0);// no broadcast + inline std::pmr::string + publishAir(std::string_view room, int zone, std::pmr::memory_resource * mr = std::pmr::get_default_resource()) { + BOOST_ASSERT(zone > 0); // no broadcast + BOOST_ASSERT(mr); using namespace std::string_view_literals; - return buildTopic(room, "sensor"sv, "air"sv,zone, "temperature"sv); + return buildPublishTopic(*mr, room, "sensor"sv, "air"sv, zone, "temperature"sv); } - inline std::pmr::string air(std::pmr::memory_resource&mr, std::string_view room, int zone) { - BOOST_ASSERT(zone>0);// no broadcast - using namespace std::string_view_literals; - return buildTopic(mr, room, "sensor"sv, "air"sv, zone, "temperature"sv); - } - + } // namespace temperature } // namespace topic diff --git a/services/floorheat_svc/main.cpp b/services/floorheat_svc/main.cpp index b439926..e94a053 100644 --- a/services/floorheat_svc/main.cpp +++ b/services/floorheat_svc/main.cpp @@ -103,7 +103,7 @@ inline awaitable_expected< void > forward_floor_temperature_all_rooms(AsyncMqttC if(remap.contains(room)) room = remap.at(room); - std::string dst = topic::temperature::floor(room, zone); + auto dst = topic::temperature::publishFloor(room, zone); spdlog::info("Republishing temperature from: {} to: {}", data.topic, dst); ASYNC_CHECK(mqtt.publish(dst, data.request)); co_return _void{}; diff --git a/services/floorheat_svc/temperature_controller.cpp b/services/floorheat_svc/temperature_controller.cpp index 93f266e..5c7e5ff 100644 --- a/services/floorheat_svc/temperature_controller.cpp +++ b/services/floorheat_svc/temperature_controller.cpp @@ -22,6 +22,8 @@ #include #include +#include +#include #include #include #include @@ -31,7 +33,6 @@ #include #include -#include #include #include #include @@ -40,37 +41,12 @@ namespace ranczo { +template +std::optional from_string(std::optional< std::string_view > state, + std::pmr::memory_resource * mr = std::pmr::get_default_resource()); + enum class ThermostatState { Enabled, Disabled, Error }; -enum class Trend { Fall, Const, Rise }; - -std::pmr::string Trend_to_string(Trend state) { - switch(state) { - case Trend::Fall: - return "Fall"; - case Trend::Const: - return "Const"; - default: - return "Rise"; - } -} - -std::optional< ThermostatState > ThermostatState_from_string(std::optional< std::string > state) { - if(not state) { - return std::nullopt; - } - std::string s(state->begin(), state->end()); - std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast< char >(std::tolower(c)); }); - - if(s == "enabled") - return ThermostatState::Enabled; - if(s == "disabled") - return ThermostatState::Disabled; - if(s == "error") - return ThermostatState::Error; - return std::nullopt; -} - -std::string ThermostatState_to_string(ThermostatState state) { +std::string to_string(ThermostatState state) { switch(state) { case ThermostatState::Enabled: return "Enabled"; @@ -81,12 +57,58 @@ std::string ThermostatState_to_string(ThermostatState state) { } } -/** - * @brief readValue - * @param jv - * @param key - * @return - */ +template<> +std::optional< ThermostatState > from_string(std::optional< std::string_view > state, + std::pmr::memory_resource * mr) { + BOOST_ASSERT(mr); + + if(not state) { + return std::nullopt; + } + std::pmr::string s(state->begin(), state->end(), mr); + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast< char >(std::tolower(c)); }); + + if(s == "enabled") + return ThermostatState::Enabled; + if(s == "disabled") + return ThermostatState::Disabled; + if(s == "error") + return ThermostatState::Error; + return std::nullopt; +} + +enum class Trend { Fall, Const, Rise }; + +std::pmr::string to_string(Trend state) { + switch(state) { + case Trend::Fall: + return "Fall"; + case Trend::Const: + return "Const"; + default: + return "Rise"; + } +} +template < > +std::optional< Trend > from_string(std::optional< std::string_view > state, + std::pmr::memory_resource * mr) { + BOOST_ASSERT(mr); + + if(not state) { + return std::nullopt; + } + std::pmr::string s(state->begin(), state->end(), mr); + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast< char >(std::tolower(c)); }); + + if(s == "fall") + return Trend::Fall; + if(s == "const") + return Trend::Const; + if(s == "rise") + return Trend::Rise; + return std::nullopt; +} + template < typename T > inline expected< T > readValue(const boost::json::value & jv, std::string_view key) { if(auto * obj = jv.if_object()) { @@ -99,7 +121,7 @@ inline expected< T > readValue(const boost::json::value & jv, std::string_view k if(!pv->is_string()) { return unexpected{make_error_code(boost::system::errc::invalid_argument)}; } - auto v = ThermostatState_from_string(std::make_optional< std::string >(pv->as_string())); + auto v = from_string(std::make_optional< std::string >(pv->as_string())); if(not v) return unexpected{make_error_code(boost::system::errc::invalid_argument)}; return *v; @@ -975,8 +997,8 @@ struct RelayThermostat::Impl : private boost::noncopyable { auto toSec = [](auto t) { return seconds{t}.count(); }; - _state = ThermostatState_from_string( - co_await _settings.async_get_store_default("state", ThermostatState_to_string(ThermostatState::Disabled))) + _state = from_string( + co_await _settings.async_get_store_default("state", to_string(ThermostatState::Disabled))) .value_or(ThermostatState::Disabled); _targetTemperature = co_await _settings.async_get_store_default("target_temperature", 20.0); @@ -988,7 +1010,7 @@ struct RelayThermostat::Impl : private boost::noncopyable { // subscribe to a thermostat commands feed _log.info("Start: subscribe to mqtt"); - ASYNC_CHECK_LOG(subscribeToAllCommands(), "subscribe to command stream failed"); // todo pass logger?? + ASYNC_CHECK_LOG(subscribeToAllCommands(), "subscribe to command stream failed"); // detaching listening thread _log.info("Start: listen"); @@ -1027,24 +1049,28 @@ struct RelayThermostat::Impl : private boost::noncopyable { template < typename Command > awaitable_expected< void > subscribeCommand() { // broadcast: zone 0 - const auto broadcast_topic = topic::heating::subscribeToCommand(_room, 0, Command::topic_suffix); - + const auto broadcast_topic = topic::heating::subscribeToControl(_room, 0, Command::topic_suffix); // konkretna strefa: - const auto zone_topic = topic::heating::subscribeToCommand(_room, _zone, Command::topic_suffix); + const auto zone_topic = topic::heating::subscribeToControl(_room, _zone, Command::topic_suffix) ; auto cb = [this](const AsyncMqttClient::CallbackData & data, AsyncMqttClient::ResponseData & resp) -> awaitable_expected< void > { auto _result = Command::from_payload(data.request); if(!_result) - co_return unexpected{_result.error()}; + co_return unexpected{_result.error()}; // auto cmd = *_result; - - /// TODO command can "throw an unexpected, this should be handled here - /// TODO command can return a true/false status for ok/nok case - auto status = ASYNC_TRY(handle_command(cmd)); - if(resp) { - _log.trace("command {} request has a response topic, write response", Command::topic_suffix); - (*resp) = boost::json::object{{"status", status ? "ok" : "nok"}, {"details", "heater updated"}}; + auto expected = co_await handle_command(cmd); + if(expected) { + if(resp.has_value()) { + _log.trace("command {} request has a response topic, write response", Command::topic_suffix); + (*resp).get() = boost::json::object{{"status", *expected ? "ok" : "nok"}, {"details", "heater updated"}}; + } + } else { + _log.error("command {} has failed", Command::topic_suffix); + if(resp.has_value()) { + _log.trace("command {} request has a response topic, write response", Command::topic_suffix); + (*resp).get() = boost::json::object{{"status", "nok"}, {"details", expected.error().what()}}; + } } co_return _void{}; @@ -1070,7 +1096,7 @@ struct RelayThermostat::Impl : private boost::noncopyable { _log.info("Heater target temperature update {}", _targetTemperature); _targetTemperature = cmd.setpoint_c; co_await update_config("target_temperature", _targetTemperature); - co_return true; + co_return ASYNC_TRY(publish_status()); } awaitable_expected< bool > handle_command(const commands::StateChange & cmd) { @@ -1083,8 +1109,8 @@ struct RelayThermostat::Impl : private boost::noncopyable { } _state = cmd.state; - - co_return true; + co_await update_config("state", to_string(_state)); + co_return ASYNC_TRY(publish_status()); } awaitable_expected< bool > handle_command(const commands::HisteresisChange & cmd) { @@ -1092,28 +1118,28 @@ struct RelayThermostat::Impl : private boost::noncopyable { _hysteresis = cmd.histeresis; /// TODO check if histeresis has ok value co_await update_config("hysteresis", _hysteresis); - co_return true; + co_return ASYNC_TRY(publish_status()); } awaitable_expected< bool > handle_command(const commands::TickTimeChange & cmd) { _log.info("Heater tick time update {}ns", cmd.tickTime.count()); _tickTime = cmd.tickTime; co_await update_config("tick_time_s", std::chrono::duration_cast< std::chrono::seconds >(_tickTime).count()); - co_return true; + co_return ASYNC_TRY(publish_status()); } awaitable_expected< bool > handle_command(const commands::SlopeWindowChange & cmd) { _log.info("Heater slope window update {}ns", cmd.window.count()); _slopeWindow = cmd.window; co_await update_config("slope_window_s", std::chrono::duration_cast< std::chrono::seconds >(_slopeWindow).count()); - co_return true; + co_return ASYNC_TRY(publish_status()); } awaitable_expected< bool > handle_command(const commands::SlopeTemperatureDiffChange & cmd) { _log.info("Heater slope temperature update {}C", cmd.dT_c); _slopeDT_c = cmd.dT_c; co_await update_config("slope_delta_t", _slopeDT_c); - co_return true; + co_return ASYNC_TRY(publish_status()); } awaitable_expected< bool > handle_command(const commands::StatusRequest & cmd) { @@ -1169,7 +1195,7 @@ struct RelayThermostat::Impl : private boost::noncopyable { auto trend = _thermo.temperatureTrend(_slopeWindow, _slopeDT_c); if(trend) { - obj["current_trend"] = Trend_to_string(*trend); + obj["current_trend"] = to_string(*trend); } obj["measurements_size"] = _thermo.size(); obj["hysteresis"] = _hysteresis; @@ -1181,7 +1207,7 @@ struct RelayThermostat::Impl : private boost::noncopyable { obj["slope_window_s"] = duration_cast< seconds_d >(_slopeWindow).count(); obj["sensor_timeout_s"] = duration_cast< seconds_d >(_sensorTimeout).count(); - auto topic = topic::heating::state(mr, _room, _zone); + auto topic = topic::heating::publishState(_room, _zone, &mr); ASYNC_CHECK(_mqtt.publish(topic, obj)); co_return true; } diff --git a/services/floorheat_svc/thermometer.cpp b/services/floorheat_svc/thermometer.cpp index a186f5b..cd18152 100644 --- a/services/floorheat_svc/thermometer.cpp +++ b/services/floorheat_svc/thermometer.cpp @@ -18,7 +18,7 @@ awaitable_expected< void > MqttThermometer::on_update(async_cb handler) noexcept _log.trace("on_update procedure start"); handler_ = std::move(handler); const auto topic = - cfg_.type == "floor" ? topic::temperature::floor(cfg_.room, cfg_.zone) : topic::temperature::air(cfg_.room, cfg_.zone); + cfg_.type == "floor" ? topic::temperature::publishFloor(cfg_.room, cfg_.zone) : topic::temperature::publishAir(cfg_.room, cfg_.zone); _log.trace("on_update procedure subscribe on topic: {}", topic); _subscribtionToken = ASYNC_TRY(mqtt_.subscribe(topic, [this](const AsyncMqttClient::CallbackData & data, AsyncMqttClient::ResponseData &resp ) -> awaitable_expected< void > { diff --git a/services/temperature_svc/measurement_publisher.cpp b/services/temperature_svc/measurement_publisher.cpp index 0d1a134..bc95c32 100644 --- a/services/temperature_svc/measurement_publisher.cpp +++ b/services/temperature_svc/measurement_publisher.cpp @@ -49,7 +49,7 @@ MqttMeasurementPublisher::publish_measurement(std::string_view sensor_type, std: const auto &[room, type] = _mapping.find(sensor_id)->second; - auto topic = ranczo::topic::temperature::floor(mr, room, 1); + auto topic = ranczo::topic::temperature::publishFloor(room, 1, &mr); using namespace std::chrono; const auto now = date::floor< std::chrono::milliseconds >(system_clock::now());