From a1bfc4c2182495fb9e817bef71dab281e399bfe8 Mon Sep 17 00:00:00 2001 From: Bartosz Wieczorek Date: Thu, 27 Nov 2025 20:20:58 +0100 Subject: [PATCH] fixes in thermometer service --- services/floorheat_svc/relay.cpp | 31 ++++--- services/floorheat_svc/relay.hpp | 33 ++++--- services/temperature_svc/main.cpp | 44 ++++++--- .../temperature_svc/measurement_publisher.cpp | 92 +++++++++++++++++-- .../temperature_svc/measurement_publisher.hpp | 4 +- 5 files changed, 154 insertions(+), 50 deletions(-) diff --git a/services/floorheat_svc/relay.cpp b/services/floorheat_svc/relay.cpp index f10aa8b..fb9d63a 100644 --- a/services/floorheat_svc/relay.cpp +++ b/services/floorheat_svc/relay.cpp @@ -1,4 +1,7 @@ #include "relay.hpp" +#include "config.hpp" +#include "ranczo-io/utils/mqtt_topic_builder.hpp" +#include namespace ranczo { ModbusRelay::ModbusRelay(boost::asio::any_io_executor ex, std::shared_ptr< ModbusDevice > dev, int channel, uint16_t base_coil_addr) : _executor{ex}, @@ -11,23 +14,25 @@ ModbusRelay::ModbusRelay(boost::asio::any_io_executor ex, std::shared_ptr< Modbu _channel{channel}, _coil_addr{static_cast< std::uint16_t >(base_coil_addr + channel)} { BOOST_ASSERT(_dev); +} - boost::asio::co_spawn( - _executor, - [=, this]() -> awaitable_expected< void > { - auto state = co_await _dev->async_read_holding_register(channel); - if(state.has_value()) { - _log.info("status at create: {}", state.value()); - _state = state.value() == 1 ? State::On : State::Off; - } else { - _log.info("failed to read status {}", state.error().value()); - } - co_return _void{}; - }, - boost::asio::detached); +std::chrono::nanoseconds ModbusRelay::timeSinceLastRead() const +{ + BOOST_ASSERT(_lastRead.has_value()); + return std::chrono::system_clock::now() - _lastRead.value(); } awaitable_expected ModbusRelay::state() const noexcept { + BOOST_ASSERT(_dev); + _log.debug("STATUS (coil={})", _coil_addr); + + if(not _lastRead || timeSinceLastRead() > std::chrono::seconds{1}) { + auto reg_value = ASYNC_TRY(_dev->async_read_holding_register(_coil_addr)); + _state = reg_value == 1 ? State::On : State::Off; + _lastRead = std::chrono::system_clock::now(); + } + + BOOST_ASSERT(_lastRead.has_value()); co_return _state; } diff --git a/services/floorheat_svc/relay.hpp b/services/floorheat_svc/relay.hpp index d414c8a..54ab5f5 100644 --- a/services/floorheat_svc/relay.hpp +++ b/services/floorheat_svc/relay.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -26,23 +27,25 @@ class Relay { }; class ModbusRelay : public Relay { - boost::asio::any_io_executor _executor; - ModuleLogger _log; - std::shared_ptr< ranczo::ModbusDevice > _dev; - int _channel; - Relay::State _state; - std::uint16_t _coil_addr; // adres cewki (0-based) + /// cached + mutable Relay::State _state; + mutable std::optional< std::chrono::system_clock::time_point > _lastRead; - public: - // Prost y mapping: coil_addr = base_addr + channel - ModbusRelay(boost::asio::any_io_executor ex, - std::shared_ptr< ranczo::ModbusDevice > dev, - int channel, - std::uint16_t base_coil_addr = 0); + std::chrono::nanoseconds timeSinceLastRead() const; - awaitable_expected< Relay::State > state() const noexcept override; - awaitable_expected< void > on() noexcept override; - awaitable_expected< void > off() noexcept override; + boost::asio::any_io_executor _executor; + ModuleLogger _log; + std::shared_ptr< ranczo::ModbusDevice > _dev; + int _channel; + std::uint16_t _coil_addr; // adres cewki (0-based) + + public: + // Prost y mapping: coil_addr = base_addr + channel + ModbusRelay(boost::asio::any_io_executor ex, std::shared_ptr< ranczo::ModbusDevice > dev, int channel, std::uint16_t base_coil_addr = 0); + + awaitable_expected< Relay::State > state() const noexcept override; + awaitable_expected< void > on() noexcept override; + awaitable_expected< void > off() noexcept override; }; } // namespace ranczo diff --git a/services/temperature_svc/main.cpp b/services/temperature_svc/main.cpp index ee13b20..dfef3a0 100644 --- a/services/temperature_svc/main.cpp +++ b/services/temperature_svc/main.cpp @@ -31,26 +31,42 @@ using namespace std::string_view_literals; namespace fs = std::filesystem; std::vector< std::tuple< std::string_view, std::string_view, Type > > mapping = { + {"28-012113ed0fef", "beadroom_zone1", Type::floor}, // home/beadroom/sensor/floor/1/temperature + {"28-0121142e6c88", "beadroom_zone2", Type::floor}, // home/beadroom/sensor/floor/2/temperature {"28-0119513810ff", "maciek_room", Type::floor}, - {"28-0119517084ff", "office", Type::floor}, + {"28-0119516f39ff", "bathroom_guest", Type::floor}, + {"28-012113f4d852", "bathroom_private", Type::floor}, + {"28-0119517084ff", "office", Type::floor}, // home/office/sensor/floor/1/temperature {"28-0119514282ff", "aska_room", Type::floor}, {"28-01195141aeff", "playroom", Type::floor}, - {"28-0119516f39ff", "bathroom_guest", Type::floor}, - {"28-011951477dff", "living_room", Type::air}, + {"28-011951477dff", "living_room", Type::air}, // home/beadroom/sensor/air/1/temperature {"28-0b2398fcaed3", "living_room_zone1", Type::floor}, {"28-0b23990cdefb", "living_room_zone2", Type::floor}, - {"28-0121142e6c88", "beadroom_zone2", Type::floor}, - {"28-012113f4d852", "bathroom_private", Type::floor}, {"28-012113f7d95d", "wardrobe", Type::floor}, + {"28-0118790a57ff", "corridor", Type::floor}, +}; + +std::vector< std::tuple< std::string_view, std::string_view, Type > > mappingOld = { {"28-012113ed0fef", "beadroom_zone1", Type::floor}, + {"28-0121142e6c88", "beadroom_zone2", Type::floor}, + {"28-0119513810ff", "maciek_room", Type::floor}, + {"28-0119516f39ff", "bathroom_1", Type::floor}, // bathroom_guast + {"28-012113f4d852", "bathroom_2", Type::floor}, // bathroom_private + {"28-0119517084ff", "office", Type::floor}, + {"28-0119514282ff", "askaRoom", Type::floor}, + {"28-01195141aeff", "playroom", Type::floor}, + {"28-011951477dff", "livingroom", Type::air}, // living_room air ?? gdzie jest ten termometr??????? + {"28-0b2398fcaed3", "livingroom_zone1", Type::floor}, + {"28-0b23990cdefb", "livingroom_zone2", Type::floor}, + {"28-012113f7d95d", "wardrobe", Type::floor}, {"28-0118790a57ff", "corridor", Type::floor}, }; int main() { using namespace ranczo; boost::asio::io_context io_context; - - memory_resource::HeapPoolResource _pool{1024*1024*1024}; // 1M + + memory_resource::HeapPoolResource _pool{1024 * 1024 * 1024}; // 1M std::pmr::set_default_resource(&_pool); // Register signal handler @@ -81,11 +97,11 @@ int main() { // create a modbus client to use with all internal objects AsyncMqttClient mqttClient{io_executor}; - - MqttMeasurementPublisher pub{mqttClient, std::span{ mapping}, &_pool}; - - const fs::path w1_root = "/home/bartoszek/mnt/w1remote/devices"; - + + MqttMeasurementPublisher pub{mqttClient, std::span{mapping}, std::span{mappingOld}, &_pool}; + + // const fs::path w1_root = "/home/bartoszek/mnt/w1remote/devices"; + const fs::path w1_root = "/sys/devices"; std::pmr::vector< fs::path > bus_paths{&_pool}; spdlog::info("Iterating paths"); @@ -100,7 +116,7 @@ int main() { spdlog::error("Nie znaleziono żadnych magistral 1-Wire"); return 1; } - + std::pmr::vector< ranczo::OneWireBus > buses{&_pool}; buses.reserve(bus_paths.size()); @@ -124,7 +140,7 @@ int main() { work_guard.reset(); io_context.stop(); // ok na tym samym wątku - spdlog::info("Graceful shutdown posted"); + spdlog::info("Graceful shutdown posted"); }); stop_promise.set_value(); // pobudzi main diff --git a/services/temperature_svc/measurement_publisher.cpp b/services/temperature_svc/measurement_publisher.cpp index bc95c32..af1a27d 100644 --- a/services/temperature_svc/measurement_publisher.cpp +++ b/services/temperature_svc/measurement_publisher.cpp @@ -1,5 +1,7 @@ #include "measurement_publisher.hpp" +#include "fmt/core.h" +#include #include #include #include @@ -20,8 +22,64 @@ // } namespace ranczo { +static std::optional< int > extract_zone_number(std::string_view sv) { + // znajdź początek ciągu cyfr idącego od końca + size_t end = sv.size(); + if(end == 0) + return std::nullopt; + + size_t start = end; + while(start > 0 && std::isdigit(static_cast< unsigned char >(sv[start - 1]))) { + --start; + } + + if(start == end) { + // brak cyfr + return std::nullopt; + } + + int value = 0; + for(size_t i = start; i < end; ++i) { + value = value * 10 + (sv[i] - '0'); + } + + return value; +} + +std::pmr::string remove_zone_suffix(std::string_view sv, std::pmr::memory_resource * mr) { + // spodziewamy się końcówki: _zoneN + constexpr std::string_view suffix = "_zone"; + + if(sv.size() <= suffix.size()) + return std::pmr::string(sv, mr); + + // sprawdź czy kończy się na "_zone" + if(!sv.ends_with(suffix) && (sv.size() < suffix.size() + 1)) + return std::pmr::string(sv, mr); + + // sprawdź czy rzeczywiście występuje _zone + size_t pos = sv.rfind(suffix); + if(pos == std::string_view::npos) + return std::pmr::string(sv, mr); + + // teraz po _zone muszą być cyfry + size_t num_start = pos + suffix.size(); + if(num_start >= sv.size()) + return std::pmr::string(sv, mr); // nie ma N + + // sprawdź czy końcówka to wszystkie cyfry + for(size_t i = num_start; i < sv.size(); ++i) { + if(!std::isdigit(static_cast< unsigned char >(sv[i]))) + return std::pmr::string(sv, mr); + } + + // OK → usuń "_zoneN" + return std::pmr::string(sv.substr(0, pos), mr); +} + MqttMeasurementPublisher::MqttMeasurementPublisher(AsyncMqttClient & client, std::span< std::tuple< std::string_view, std::string_view, Type > > ds18b20mapping, + std::span< std::tuple< std::string_view, std::string_view, Type > > ds18b20mappingOld, std::pmr::memory_resource * mr, std::source_location sl) : _client(client), _mr(4096, mr, sl) { @@ -30,10 +88,22 @@ MqttMeasurementPublisher::MqttMeasurementPublisher(AsyncMqttClient & client, const auto & room_name_view = std::get< 1 >(entry); // nazwa pomieszczenia const auto & sensor_type_enum = std::get< 2 >(entry); // Type + std::pmr::string sensor_id{sensor_id_view, &_mr}; + std::pmr::string room_name{remove_zone_suffix(room_name_view, &_mr), &_mr}; + + _mapping.emplace( + std::move(sensor_id), std::make_tuple(std::move(room_name), extract_zone_number(room_name_view).value_or(1), sensor_type_enum)); + } + + for(const auto & entry : ds18b20mappingOld) { + const auto & sensor_id_view = std::get< 0 >(entry); // id DS-a + const auto & room_name_view = std::get< 1 >(entry); // nazwa pomieszczenia + const auto & sensor_type_enum = std::get< 2 >(entry); // Type + std::pmr::string sensor_id{sensor_id_view, &_mr}; std::pmr::string room_name{room_name_view, &_mr}; - _mapping.emplace(std::move(sensor_id), std::make_pair(std::move(room_name), sensor_type_enum)); + _mapping_old.emplace(std::move(sensor_id), std::make_pair(std::move(room_name), sensor_type_enum)); } } @@ -41,15 +111,16 @@ awaitable_expected< void > MqttMeasurementPublisher::publish_measurement(std::string_view sensor_type, std::string_view sensor_id, double measurement) noexcept { using namespace memory_resource::literals; memory_resource::MonotonicHeapResource mr{1_kB, &_mr}; - - if(not _mapping.contains(sensor_id)){ + + if(not _mapping.contains(sensor_id)) { spdlog::warn("Nie znaleziono {}", sensor_id); co_return _void{}; } - - const auto &[room, type] = _mapping.find(sensor_id)->second; - - auto topic = ranczo::topic::temperature::publishFloor(room, 1, &mr); + + const auto & [room, zone, type] = _mapping.find(sensor_id)->second; + + auto topic = type == Type::floor ? ranczo::topic::temperature::publishFloor(room, zone, &mr) : + ranczo::topic::temperature::publishAir(room, zone, &mr); using namespace std::chrono; const auto now = date::floor< std::chrono::milliseconds >(system_clock::now()); @@ -66,6 +137,13 @@ MqttMeasurementPublisher::publish_measurement(std::string_view sensor_type, std: boost::json::value payload{std::move(obj)}; + if(_mapping_old.contains(sensor_id)) { + const auto & [room, type] = _mapping_old.find(sensor_id)->second; + // home/bathroom_2/floor/temperature + auto oldTopic = fmt::format("home/{}/{}/temperature", room, type == Type::floor ? "floor" : "air"); + co_await _client.publish(oldTopic, payload); + } + // 4. Log (topic + JSON) spdlog::debug("Publishing MQTT measurement topic='{}'", topic); diff --git a/services/temperature_svc/measurement_publisher.hpp b/services/temperature_svc/measurement_publisher.hpp index 43fcc2d..1dccfc7 100644 --- a/services/temperature_svc/measurement_publisher.hpp +++ b/services/temperature_svc/measurement_publisher.hpp @@ -23,6 +23,7 @@ class MqttMeasurementPublisher { MqttMeasurementPublisher(AsyncMqttClient & client, std::span< std::tuple< std::string_view, std::string_view, Type > > ds18b20mapping, + std::span< std::tuple< std::string_view, std::string_view, Type > > ds18b20mappingOld, std::pmr::memory_resource * mr = std::pmr::get_default_resource(), std::source_location sl = std::source_location::current() ); @@ -33,6 +34,7 @@ class MqttMeasurementPublisher { AsyncMqttClient & _client; memory_resource::HeapPoolResource _mr; - std::pmr::map< std::pmr::string, std::pair< std::pmr::string, Type >, std::less<> > _mapping{&_mr}; + std::pmr::map< std::pmr::string, std::tuple< std::pmr::string, int, Type >, std::less<> > _mapping{&_mr}; + std::pmr::map< std::pmr::string, std::pair< std::pmr::string, Type >, std::less<> > _mapping_old{&_mr}; }; } // namespace ranczo