add ds18b20 sensor, some changes in documentation

This commit is contained in:
Bartosz Wieczorek 2025-08-06 07:36:26 +02:00
parent b8cc238b5e
commit 396a3b3339
14 changed files with 249 additions and 81 deletions

View File

@ -0,0 +1,19 @@
add_library(ranczo-io_utils
mqtt_client.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ranczo-io/utils/mqtt_client.hpp
timer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ranczo-io/utils/timer.hpp
)
add_library(ranczo-io::utils ALIAS ranczo-io_utils)
target_include_directories(ranczo-io_utils
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(ranczo-io_utils
PUBLIC
Boost::mqtt5
Boost::system
Boost::json
spdlog::spdlog
)

View File

@ -1,4 +1,4 @@
#include "mqtt_client.hpp"
#include <ranczo-io/utils/mqtt_client.hpp>
#include <boost/random/uniform_smallint.hpp>
#include <boost/system/system_error.hpp>

View File

@ -29,9 +29,6 @@ class AsyncMqttClient {
/* value assosiated to request */
const boost::json::value & value;
/* memory_resource of client */
// TBD
};
struct AsyncMqttClientImpl;

View File

@ -1,6 +1,6 @@
#pragma once
#include <boost/asio/awaitable.hpp>
#include <config.hpp>
#include <chrono>
#include <functional>
@ -24,7 +24,7 @@ class SingleShootTimer {
SingleShootTimer(executor & executor, std::chrono::milliseconds timeout, std::function< void() > cb);
~SingleShootTimer();
boost::asio::awaitable< void > start() const;
awaitable_expected< void > start() const;
};
class PeriodicTimer {
@ -39,7 +39,7 @@ class PeriodicTimer {
PeriodicTimer(executor & executor, std::chrono::milliseconds period, std::function< void() > cb);
~PeriodicTimer();
boost::asio::awaitable< void > start() const;
awaitable_expected< void > start() const;
};
} // namespace ranczo

View File

@ -1,4 +1,8 @@
#include "timer.hpp"
#include <boost/system/detail/error_code.hpp>
#include <boost/system/system_error.hpp>
#include <expected>
#include <ranczo-io/utils/timer.hpp>
#include "config.hpp"
#include "spdlog/spdlog.h"
#include <boost/asio/any_io_executor.hpp>
@ -39,10 +43,11 @@ struct SingleShootTimer::Impl {
}
}
boost::asio::awaitable< void > start() {
awaitable_expected< void > start() {
spdlog::trace("co_spawn timer");
// Start the coroutine by invoking the timerCoroutine() with boost::asio::co_spawn
co_return boost::asio::co_spawn(_timer.get_executor(), timerCoroutine(), boost::asio::detached);
boost::asio::co_spawn(_timer.get_executor(), timerCoroutine(), boost::asio::detached);
co_return expected< void, boost::system::error_code >{};
}
};
@ -50,9 +55,10 @@ SingleShootTimer::SingleShootTimer(executor & executor, std::chrono::millisecond
: _impl{std::make_unique< Impl >(executor, timeout, std::move(cb))} {}
SingleShootTimer::~SingleShootTimer() = default;
boost::asio::awaitable< void > SingleShootTimer::start() const {
awaitable_expected<void> SingleShootTimer::start() const {
assert(_impl);
return _impl->start();
ASYNC_CHECK(_impl->start());
co_return expected< void, boost::system::error_code >{};
}
struct PeriodicTimer::Impl {
@ -85,19 +91,21 @@ struct PeriodicTimer::Impl {
}
}
boost::asio::awaitable< void > start() {
awaitable_expected< void > start() {
spdlog::trace("co_spawn timer");
// Start the coroutine by invoking the timerCoroutine() with boost::asio::co_spawn
co_return boost::asio::co_spawn(_timer.get_executor(), timerCoroutine(), boost::asio::detached);
boost::asio::co_spawn(_timer.get_executor(), timerCoroutine(), boost::asio::detached);
co_return expected< void, boost::system::error_code >{};
}
};
PeriodicTimer::PeriodicTimer(executor & executor, std::chrono::milliseconds period, std::function< void() > cb)
: _impl{std::make_unique< Impl >(executor, period, std::move(cb))} {}
boost::asio::awaitable< void > PeriodicTimer::start() const {
awaitable_expected<void> PeriodicTimer::start() const {
BOOST_ASSERT(_impl);
return _impl->start();
ASYNC_CHECK(_impl->start());
co_return expected< void, boost::system::error_code >{};
}
PeriodicTimer::~PeriodicTimer() = default;

View File

@ -1,21 +1,10 @@
add_executable(ranczo-io_floorheating
main.cpp
heater.cpp
heater._controller.cpp
relay.cpp
mqtt_client.cpp mqtt_client.hpp
timer.cpp timer.hpp
${PROTO_SRCS}
)
target_include_directories(ranczo-io_floorheating
PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(ranczo-io_floorheating
PUBLIC
Boost::mqtt5
Boost::system
Boost::json
spdlog::spdlog
ranczo-io::utils
)

View File

@ -1,6 +1,8 @@
# `floorheating_svc` Floor Heating Control Service
`floorheating_svc` is a service responsible for controlling **electric floor heating zones** in a smart home. It uses **MQTT v5** for communication and is implemented using **Boost.Asio** with asynchronous logic.
`floorheating_svc` is a service responsible for controlling **electric floor heating zones** in a smart home.
It uses **MQTT v5** for communication and is implemented using **Boost.Asio** with asynchronous logic.
This service is responsible for all home floor heating so any kill_switch should be a sole responsible of this service
The service:
- Maintains and applies target **floor** temperature per room
@ -26,9 +28,9 @@ The service:
| Topic | Purpose |
|-------|---------|
| `home/heating/<room>/floor/temperature/command` | Set target floor temperature |
| `home/sensor/<room>/floor/temperature/state` | Floor temperature sensor input |
| `home/sensor/<room>/air/temperature/state` | (Optional) air temperature sensor |
| `home/<room>/floor/heating/command` | Set target floor temperature |
| `home/<room>/floor/sensor/temperature/state` | Floor temperature sensor input |
| `home/<room>/air/sensor/temperature/state` | (Optional) air temperature sensor |
| `home/control/kill_switch` | Global emergency shutdown |
> All subscriptions use `no_local = yes` to avoid receiving own messages.
@ -39,9 +41,9 @@ The service:
| Topic | Purpose | Retained |
|-------|---------|----------|
| `home/heating/<room>/floor/temperature/state` | Periodic state report (current, target, heating status) | ✅ Yes |
| `home/heating/<room>/floor/temperature/result` | Response to `/command` (uses MQTT v5 correlation-data) | ❌ No |
| `home/error/floorheating/<room>` | Critical error events (e.g., temperature spike) | ❌ No |
| `home/<room>/floor/heating/state` | Periodic state report (current, target, heating status) | ✅ Yes |
| `home/<room>/floor/heating/temperature/result` | Response to `/command` (uses MQTT v5 correlation-data) | ❌ No |
| `home/error/heating/floor/<room>` | Critical error events (e.g., temperature spike) | ❌ No |
| `home/control/kill_switch` | Global kill message (optional, emitted by this service) | ❌ No |
| `home/heating/<room>/floor/config` | (Optional) auto-discovery data | ✅ Yes |
@ -52,7 +54,7 @@ The service:
Every **60 seconds**, the service publishes a message to:
```
home/heating/<room>/floor/temperature/state
home/<room>/floor/heating/state
```
**Example payload:**
@ -73,7 +75,7 @@ home/heating/<room>/floor/temperature/state
### Topic:
```
home/heating/<room>/floor/temperature/command
home/<room>/floor/temperature/command
```
**Example payload:**
@ -107,7 +109,7 @@ Published to the specified `response-topic`, with matching `correlation-data` (M
### Actions:
1. Publish error:
```
home/error/floorheating/<room>
home/<room>/floor/heating/error
```
```json
@ -119,7 +121,7 @@ home/error/floorheating/<room>
}
```
2. Trigger global shutdown:
2. Trigger global shutdown: Not yet implemented
```
home/control/kill_switch
```
@ -145,7 +147,7 @@ home/control/kill_switch
### Floor temperature (mandatory)
Topic:
```
home/sensor/<room>/floor/temperature/state
home/<room>/floor/sensor/temperature/state
```
Payload:
@ -159,7 +161,7 @@ Payload:
### Air temperature (optional)
Topic:
```
home/sensor/<room>/air/temperature/state
home/<room>/air/sensor/temperature/state
```
Payload:
@ -174,17 +176,6 @@ Payload:
---
## 🚫 Unsupported Features
| Feature | Status |
|--------|--------|
| Profile switching (`/profile/command`) | ❌ Not supported |
| Scheduling / automation | ❌ Not implemented internally |
| Direct relay access | ❌ Delegated to `io_output_svc` |
| Scene control | ❌ Should be handled externally |
---
## 📌 Operational Notes
- One shared MQTT client instance is used across all zone objects.
@ -199,7 +190,7 @@ Payload:
```bash
mosquitto_pub \
-t home/heating/bathroom/floor/temperature/command \
-t home/bathroom/floor/heating/temperature/command \
-m '{"target": 22.5, "mode": "manual"}' \
-V mqttv5 \
-D response-topic home/heating/bathroom/floor/temperature/result \

View File

@ -1,10 +1,9 @@
#include "heater.hpp"
#include "heater_controller.hpp"
#include "boost/mqtt5/impl/read_message_op.hpp"
#include "config.hpp"
#include "mqtt_client.hpp"
#include <ranczo-io/utils/mqtt_client.hpp>
#include "spdlog/spdlog.h"
#include "timer.hpp"
#include <ranczo-io/utils/timer.hpp>
#include <algorithm>
#include <chrono>
@ -111,17 +110,17 @@ struct ResistiveFloorHeater::Impl : private boost::noncopyable {
if(update == true) {
/// TODO if temp to low enable heater
/// TODO if temp to high disable heater
spdlog::info("heaterControlLoopTick got update");
spdlog::debug("heaterControlLoopTick got update");
switch(getTempTrendFromLastChange()) {
case Const:
spdlog::info("No temp change");
spdlog::debug("No temp change");
break;
case Fall:
spdlog::info("Temp FALL");
spdlog::debug("Temp FALL");
break;
case Rise:
spdlog::info("Temp RISE");
spdlog::debug("Temp RISE");
break;
default:
break;
@ -196,7 +195,7 @@ struct ResistiveFloorHeater::Impl : private boost::noncopyable {
ASYNC_CHECK_MSG(subscribeToTargetTemperatureUpdate(), "subscribe to temp update failed");
ASYNC_CHECK_MSG(subscribeToTargetProfileUpdate(), "subscribe to profile update failed");
co_await _tickTimer.start();
ASYNC_CHECK_MSG(_tickTimer.start(), "failed to start timer");
ASYNC_CHECK_MSG(_mqtt_client.listen(), "failed to listen");
co_return heater_expected_void{};

View File

@ -16,7 +16,8 @@ class IHeater {
template < typename T >
using awaitable = boost::asio::awaitable< T >;
virtual ~IHeater() = default;
virtual ~IHeater() = default;
virtual awaitable_expected< void > start() noexcept = 0;
};

View File

@ -1,4 +1,5 @@
#include "timer.hpp"
#include <ranczo-io/utils/mqtt_client.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
@ -8,9 +9,9 @@
#include <spdlog/spdlog.h>
#include "heater.hpp"
#include "mqtt_client.hpp"
#include "timer.hpp"
#include "heater_controller.hpp"
#include <ranczo-io/utils/mqtt_client.hpp>
#include <ranczo-io/utils/timer.hpp>
#include <vector>
@ -31,15 +32,12 @@ using namespace std::string_view_literals;
int main() {
spdlog::set_level(spdlog::level::trace);
std::vector< std::shared_ptr< ranczo::IHeater > > _heaters;
boost::asio::io_context io_service;
/// Strand powoduje że zadania do niego przypisane zostają wykonane sekwencyjnie,
/// get_executor pobrany z io_service nie daje takiej możliwości i wtedy można wykonywać zadania równloegle
boost::asio::any_io_executor io_executor = boost::asio::make_strand(io_service);
boost::asio::io_context io_context;
boost::asio::any_io_executor io_executor = io_context.get_executor();
// PARTER
_heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "corridor"sv));
_heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "utilityRoom"sv));
@ -59,14 +57,14 @@ int main() {
_heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "office"sv));
_heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "bathroom_1"sv));
_heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "corridor_up"sv));
for(auto &heater:_heaters){
co_spawn(io_executor, heater->start(), boost::asio::detached);
}
boost::asio::executor_work_guard< boost::asio::io_context::executor_type > work_guard = boost::asio::make_work_guard(io_service);
io_service.run();
for(auto & heater : _heaters) {
co_spawn(io_context, heater->start(), boost::asio::detached);
}
boost::asio::executor_work_guard< boost::asio::io_context::executor_type > work_guard = boost::asio::make_work_guard(io_context);
io_context.run();
return 0;
}

View File

@ -0,0 +1,12 @@
add_executable(ranczo-io_temperature
main.cpp
ds18b20_sensor.cpp ds18b20_sensor.hpp
)
target_link_libraries(ranczo-io_temperature
PUBLIC
uring
)
target_link_libraries(ranczo-io_temperature
PUBLIC
ranczo-io::utils
)

View File

@ -0,0 +1,58 @@
#include "ds18b20_sensor.hpp"
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/read.hpp>
#include <fstream>
#include <string>
#define BOOST_ASIO_HAS_IO_URING 1
#define BOOST_ASIO_HAS_FILE 1
#include <boost/asio/stream_file.hpp>
namespace ranczo {
boost::asio::awaitable< void > ranczo::DS18B20Sensor::run() {
while (true) {
// auto temp = read_temperature();
// if (temp.has_value()) {
// std::cout << "Temperature [" << device_id_ << "]: " << *temp << " C\n";
// } else {
// std::cerr << "Failed to read temperature for device: " << device_id_ << "\n";
// }
// timer_.expires_after(interval_);
// co_await timer_.async_wait(boost::asio::use_awaitable);
}
}
ranczo::awaitable_expected< float > ranczo::DS18B20Sensor::read_temperature() {
// const std::string path = "/sys/bus/w1/devices/" + device_id_ + "/w1_slave";
const std::string path = "/home/bartoszek/mnt/w1remote/devices/" + device_id_ + "/w1_slave";
using boost::asio::stream_file;
stream_file file(this->executor_, path, boost::asio::stream_file::flags::read_only);
// Create a buffer to hold the file data
size_t bufferSize = 1024;
std::vector<char> buffer(bufferSize);
boost::asio::async_read()
// Read the file asynchronously
async_read(file, boost::asio::buffer(buffer), boost::asio::transfer_all());
// std::string line1, line2;
// std::getline(file, line1);
// std::getline(file, line2);
// if(line1.find("YES") == std::string::npos) {
// return std::nullopt;
// }
// std::size_t pos = line2.find("t=");
// if(pos == std::string::npos) {
// return std::nullopt;
// }
// int temp_raw = std::stoi(line2.substr(pos + 2));
// return static_cast< float >(temp_raw) / 1000.0f;
}
} // namespace ranczo

View File

@ -0,0 +1,23 @@
#pragma once
#include <config.hpp>
#include <ranczo-io/utils/timer.hpp>
namespace ranczo {
class DS18B20Sensor {
public:
DS18B20Sensor(boost::asio::any_io_executor exec, std::string device_id, std::chrono::seconds interval = std::chrono::seconds(2))
: executor_(exec), timer_(exec, interval, [&]() { return this->run(); }), device_id_(std::move(device_id)) {}
boost::asio::awaitable< void > run();
private:
awaitable_expected< float > read_temperature();
boost::asio::any_io_executor executor_;
PeriodicTimer timer_;
std::string device_id_;
};
} // namespace ranczo

View File

@ -0,0 +1,73 @@
#include <ranczo-io/utils/mqtt_client.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/this_coro.hpp>
#include <spdlog/spdlog.h>
#include <ranczo-io/utils/mqtt_client.hpp>
#include <ranczo-io/utils/timer.hpp>
namespace ranczo {
/// TODO
/// * Przypisanie przełącznika do maty grzewczej
/// * Zapis danych w DB
/// * Zapis ustawień
/// * Nasłuchiwanie na MQTT
} // namespace ranczo
// Modified completion token that will prevent co_await from throwing exceptions.
using namespace std::chrono_literals;
using namespace std::string_view_literals;
namespace ranczo{
class DS18B20_termometer{
};
}
int main() {
spdlog::set_level(spdlog::level::trace);
// std::vector< std::shared_ptr< ranczo::IHeater > > _heaters;
boost::asio::io_context io_context;
boost::asio::any_io_executor io_executor = io_context.get_executor();
// PARTER
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "corridor"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "utilityRoom"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "wardrobe"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "bathroom_1"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "bathroom_2"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "beadroom_zone1"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "beadroom_zone2"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "livingroom_zone1"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "livingroom_zone2"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "livingroom_zone3"sv));
// PIĘTRO
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "askaRoom"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "maciejRoom"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "playroom"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "office"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "bathroom_1"sv));
// _heaters.emplace_back(std::make_shared< ranczo::ResistiveFloorHeater >(io_executor, "corridor_up"sv));
// for(auto & heater : _heaters) {
// co_spawn(io_context, heater->start(), boost::asio::detached);
// }
boost::asio::executor_work_guard< boost::asio::io_context::executor_type > work_guard = boost::asio::make_work_guard(io_context);
io_context.run();
return 0;
}