fixes in thermometer service

This commit is contained in:
Bartosz Wieczorek 2025-11-27 20:20:58 +01:00
parent b4bbdb3ed3
commit a1bfc4c218
5 changed files with 154 additions and 50 deletions

View File

@ -1,4 +1,7 @@
#include "relay.hpp"
#include "config.hpp"
#include "ranczo-io/utils/mqtt_topic_builder.hpp"
#include <chrono>
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<Relay::State> 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;
}

View File

@ -8,6 +8,7 @@
#include <boost/regex/config.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <chrono>
#include <config.hpp>
#include <ranczo-io/utils/logger.hpp>
@ -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

View File

@ -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

View File

@ -1,5 +1,7 @@
#include "measurement_publisher.hpp"
#include "fmt/core.h"
#include <memory_resource>
#include <ranczo-io/utils/json_helpers.hpp>
#include <ranczo-io/utils/memory_resource.hpp>
#include <ranczo-io/utils/mqtt_client.hpp>
@ -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);

View File

@ -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