This commit is contained in:
Bartosz Wieczorek 2025-08-05 08:10:55 +02:00
parent 10c36bc30c
commit 9d138b0045
18 changed files with 60 additions and 327 deletions

View File

@ -10,9 +10,50 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
set(BOOST_ROOT /usr/local) set(BOOST_ROOT /usr/local)
find_package(Boost REQUIRED COMPONENTS system json) find_package(Boost REQUIRED COMPONENTS system json)
include(FetchContent) include(FetchContent)
# Create a Boost-style interface target for a header-only library
function(create_boost_header_only_target target_name)
string(TOLOWER ${target_name} target_key)
set(interface_target ${target_key}_interface)
set(header_subdir ${target_key})
if(NOT TARGET Boost::${target_name})
message(STATUS "Creating Boost::${target_name} as header-only interface target")
add_library(${interface_target} INTERFACE)
# Add include dir if not explicitly set
if(DEFINED Boost_INCLUDE_DIRS)
target_include_directories(${interface_target} INTERFACE ${Boost_INCLUDE_DIRS})
else()
message(WARNING "Boost_INCLUDE_DIRS not defined when creating Boost::${target_name}")
endif()
# Optional preprocessor tweaks per library
if(${target_name} STREQUAL "asio")
target_compile_definitions(${interface_target} INTERFACE BOOST_ASIO_NO_DEPRECATED)
endif()
# Create alias for consistency with imported targets
add_library(Boost::${target_name} ALIAS ${interface_target})
else()
message(STATUS "Boost::${target_name} already exists")
endif()
endfunction()
create_boost_header_only_target(asio)
create_boost_header_only_target(assert)
create_boost_header_only_target(core)
create_boost_header_only_target(endian)
create_boost_header_only_target(fusion)
create_boost_header_only_target(optional)
create_boost_header_only_target(random)
create_boost_header_only_target(range)
create_boost_header_only_target(smart_ptr)
create_boost_header_only_target(spirit)
create_boost_header_only_target(type_traits)
# spdlog # spdlog
FetchContent_Declare( FetchContent_Declare(
spdlog spdlog
@ -45,4 +86,4 @@ set(MQTT_BUILD_EXAMPLES OFF)
set(async-mqtt5_INCLUDES_WITH_SYSTEM OFF) set(async-mqtt5_INCLUDES_WITH_SYSTEM OFF)
FetchContent_MakeAvailable(asyncmqtt5) FetchContent_MakeAvailable(asyncmqtt5)
add_subdirectory(floorheat_hub) add_subdirectory(services)

View File

@ -1,88 +0,0 @@
syntax = "proto3";
import "ACPowerInternal.proto";
package acpowercontrol;
message Error{
///todo change to enum
string details = 1;
}
message ResetWatchdodRequest {
uint32 channel = 1;
}
message ResetWatchdodResponse {
uint32 channel = 1;
optional Error error = 2;
}
// Main control message that can include one of the above actions
message SetChannelRequest {
uint32 channel = 1;
oneof power {
acpowercontrol.internal.ACPowerSwitchBinary switch = 2;
acpowercontrol.internal.ACPowerToggle toggle = 3;
acpowercontrol.internal.ACPowerModulation modulation = 4;
acpowercontrol.internal.ACPowerSwipe swipe = 5;
}
}
message SetChannelResponse {
uint32 channel = 1;
optional Error error = 2;
}
message GetChannelRequest {
uint32 channel = 1;
}
message GetChannelResponse{
uint32 channel = 1;
oneof resp{
uint32 current_power = 2;
Error error = 3;
}
}
message SetChannelConfigRequest {
uint32 channel = 1;
acpowercontrol.internal.ChannelConfig config = 2;
}
message SetChannelConfigResponse {
uint32 channel = 1;
optional Error error = 2;
}
message GetChannelConfigRequest {
uint32 channel = 1;
}
message GetChannelConfigResponse {
uint32 channel = 1;
oneof resp {
acpowercontrol.internal.ChannelConfig config = 2;
Error error = 3;
}
}
message ACPower{
oneof data{
ResetWatchdodRequest reset_wtchdod_request= 1;
ResetWatchdodResponse reset_wtchdod_response= 2;
SetChannelRequest set_channel_request = 3;
SetChannelResponse set_channel_response = 4;
GetChannelRequest get_channel_request = 5;
GetChannelResponse get_channel_response = 6;
SetChannelConfigRequest set_channel_config_request = 7;
SetChannelConfigResponse set_channel_config_response= 8;
GetChannelConfigRequest get_channel_config_request = 9;
GetChannelConfigResponse get_channel_config_response = 10;
}
}

View File

@ -1,49 +0,0 @@
syntax = "proto3";
package acpowercontrol.internal;
// channel configuration
message ChannelConfig{
uint32 faze_nr = 1;
uint32 default_watchdog_s = 2;
}
// Message to represent basic on/off control
message ACPowerSwitchBinary {
bool on = 1;
}
// Message to represent power toggle
message ACPowerToggle {
}
// Message to represent AC phase control (e.g., 30% of sine wave)
message ACPowerModulation {
// Phase percentage in i/10 of percent (value 123 rtanslates to 12.3%, typically 100.0)
uint32 phase_percentage = 1;
// number of sinusoids taken to control (2 is a full sinus cycle, default 1)
optional uint32 phaseGroup = 3; // if not present, assumed 1
}
// Message to represent a soft start
message ACPowerSwipe {
enum Function{
Linear = 0;
Quadratic = 1; // Power increases exponentially over time, starting slow and speeding up.
Logarithmic = 2; // Power increases logarithmically, starting fast and slowing down as it approaches the end.
EaseInOut = 3; // This function starts and ends smoothly with a rapid change in the middle.
}
// Start percentage in i/10 of percent (value 123 rtanslates to 12.3%, typically 100.0)
optional uint32 start_percentage = 1;
// End percentage in i/10 of percent (value 123 rtanslates to 12.3%, typically 100.0)
uint32 end_percentage = 3;
// Duration in milliseconds
uint32 duration_ms = 5;
// function used
Function function=6;
}

View File

@ -1,40 +0,0 @@
syntax = "proto3";
message FixedPoint {
int32 integer_part = 1;
uint32 fractional_part = 2;
}
message x {
uint32 ch = 1;
bool state = 2;
}
message FloatingPoint{
float val = 2;
}
message SingleFazeData {
float Voltage = 1;
float Current = 2;
float ActivePower = 3;
float ApparentPower = 4;
float PowerFactor = 5;
float ReActivePower = 6;
}
message EnergymeterSensorData {
int32 sensor_id = 1;
SingleFazeData L1 = 2;
SingleFazeData L2 = 3;
SingleFazeData L3 = 4;
float TotalActivePower = 5;
float TotalPowerFactor = 6;
float TotalApparentePower = 7;
float TotalReActivePower = 8;
float GridFrequency = 9;
}
message can_frame {
uint32 left = 1;
bytes data = 2;
}

View File

@ -1,98 +0,0 @@
syntax = "proto3";
// internal
// channel configuration
message ChannelConfig{
uint32 faze = 1;
uint32 default_watchdog_s = 2;
}
// Message to represent basic on/off control
message ACPowerBinary {
bool on = 1;
}
// Message to represent power toggle
message ACPowerToggle {}
// Message to represent AC phase control (e.g., 30% of sine wave)
message ACPowerProcentage {
// Phase percentage from 0 to 100
uint32 phase_percentage = 1;
uint32 phase_percentageFraction = 2;
// number of sinusoids taken to control (2 is a full sinus cycle, default 1)
optional uint32 phaseGroup = 3; // if not present, assumed 1
}
// Message to represent a soft start
message ACPowerSwipe {
enum Function{
Linear = 0;
Quadratic = 1; // Power increases exponentially over time, starting slow and speeding up.
Logarithmic = 2; // Power increases logarithmically, starting fast and slowing down as it approaches the end.
EaseInOut = 3; // This function starts and ends smoothly with a rapid change in the middle.
}
// Start percentage (typically 0)
optional uint32 start_percentage = 1;
// Start percentage fraction (as 1/100 part of percentage) (typically 0)
uint32 start_percentage_fraction = 2;
// End percentage (typically 100)
uint32 end_percentage = 3;
// End percentage fraction (as 1/100 part of percentage) (typically 0)
uint32 end_percentage_fraction = 4;
// Duration in milliseconds
uint32 duration_ms = 5;
// function used
Function function=6;
}
message ACPowerControl{
oneof control_type {
ACPowerBinary binary = 5;
ACPowerProcentage procentage = 6;
ACPowerSwipe swipe = 7;
ACPowerToggle toggle = 8; // revert previous action
}
}
message ACPowerResetWatchdod{
repeated uint32 channel = 1;
}
// Main control message that can include one of the above actions
message SetACPowerChannelControl {
uint32 channel = 1;
ACPowerControl power = 2;
// seconds after which output will be turned off (in case of lack of control messages) 0 -> uses default
uint32 watchdog_s = 3;
}
message GetAcPowerChannelControl {
uint32 channel = 1;
}
message SetACPowerChannelConfig {
uint32 channel = 1;
ChannelConfig config = 2;
}
message GetACPowerChannelConfig {
uint32 channel = 1;
}
enum HeatingMessagesId{
ACPowerResetWatchdodID = 0;
SetACPowerChannelControlID = 1;
GetAcPowerChannelControlID = 2;
SetACPowerChannelConfigID = 3;
GetACPowerChannelConfigID = 4;
}

View File

@ -1,30 +0,0 @@
syntax = "proto3";
// single part of message
message Frame{
// frames left, 0 for End Of Stream
uint32 parts = 1; // parts left
// encapsulated message
bytes data = 2;
// type of message being encapsulated,only one of parts need to contain this information
uint32 type = 3;
}
message PrintfArgument {
oneof arg_type {
int32 int_val = 1;
float float_val = 2;
double double_val = 3;
string string_val = 4;
bool bool_val = 5;
bytes bytes_val = 6;
}
}
message PrintfMessage {
string format_string = 1;
uint32 severity = 2;
repeated PrintfArgument arguments = 3;
}

1
services/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
add_subdirectory(floorheat_hub)

View File

@ -1,10 +1,3 @@
# protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS
# ../proto/message.proto
# ../proto/heating.proto
# ../proto/ACPower.proto
# ../proto/ACPowerInternal.proto
# )
add_executable(ranczo-io_floorheating add_executable(ranczo-io_floorheating
main.cpp main.cpp
heater.cpp heater.cpp
@ -22,7 +15,7 @@ target_include_directories(ranczo-io_floorheating
target_link_libraries(ranczo-io_floorheating target_link_libraries(ranczo-io_floorheating
PUBLIC PUBLIC
# ${Protobuf_LIBRARIES} # ${Protobuf_LIBRARIES}
Async::MQTT5 Boost::mqtt5
Boost::system Boost::system
Boost::json Boost::json
spdlog::spdlog spdlog::spdlog

View File

@ -2,6 +2,7 @@
#include <boost/asio/associated_executor.hpp> #include <boost/asio/associated_executor.hpp>
#include <boost/asio/co_spawn.hpp> #include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp> #include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/this_coro.hpp> #include <boost/asio/this_coro.hpp>
#include <memory> #include <memory>
#include <memory_resource> #include <memory_resource>
@ -131,7 +132,7 @@ boost::asio::awaitable<void> spawn(ranczo::AsyncMqttClient & c1){
int main() { int main() {
spdlog::set_level(spdlog::level::trace); spdlog::set_level(spdlog::level::trace);
std::vector< std::unique_ptr< ranczo::IHeater > > _heaters; std::vector< std::unique_ptr< ranczo::IHeater > > _heaters;
boost::asio::io_service io_service; boost::asio::io_context io_service;
/// Strand powoduje ¿e zadania do niego przypisane zostaj± wykonane sekwencyjnie, /// 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 /// get_executor pobrany z io_service nie daje takiej mo¿liwo¶ci i wtedy mo¿na wykonywaæ zadania równloegle
@ -153,7 +154,9 @@ int main() {
// xes.emplace_back(std::allocate_shared< X >(alloc, io_executor, 2, "home/utilityRoom/floor/temperature")); // xes.emplace_back(std::allocate_shared< X >(alloc, io_executor, 2, "home/utilityRoom/floor/temperature"));
// xes.emplace_back(std::allocate_shared< X >(alloc, io_executor, 3, "home/wardrobe/floor/temperature")); // xes.emplace_back(std::allocate_shared< X >(alloc, io_executor, 3, "home/wardrobe/floor/temperature"));
boost::asio::io_service::work work(io_service); boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_guard =
boost::asio::make_work_guard(io_service);
io_service.run(); io_service.run();
return 0; return 0;

View File

@ -6,12 +6,12 @@
#include <memory> #include <memory>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <async_mqtt5.hpp> #include <boost/mqtt5.hpp>
#include <unordered_map> #include <unordered_map>
namespace ranczo { namespace ranczo {
constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable); constexpr auto use_nothrow_awaitable = boost::asio::as_tuple(boost::asio::use_awaitable);
using client_type = async_mqtt5::mqtt_client< boost::asio::ip::tcp::socket >; using client_type = boost::mqtt5::mqtt_client< boost::asio::ip::tcp::socket >;
struct AsyncMqttClient::AsyncMqttClientImpl { struct AsyncMqttClient::AsyncMqttClientImpl {
const boost::asio::any_io_executor & _executor; const boost::asio::any_io_executor & _executor;
@ -34,17 +34,17 @@ struct AsyncMqttClient::AsyncMqttClientImpl {
boost::asio::awaitable< bool > subscribe(std::string_view topic) { boost::asio::awaitable< bool > subscribe(std::string_view topic) {
// Configure the request to subscribe to a Topic. // Configure the request to subscribe to a Topic.
async_mqtt5::subscribe_topic sub_topic = async_mqtt5::subscribe_topic{topic.data(), boost::mqtt5::subscribe_topic sub_topic = boost::mqtt5::subscribe_topic{topic.data(),
async_mqtt5::subscribe_options{ boost::mqtt5::subscribe_options{
async_mqtt5::qos_e::exactly_once, // All messages will arrive at QoS 2. boost::mqtt5::qos_e::exactly_once, // All messages will arrive at QoS 2.
async_mqtt5::no_local_e::no, // Forward message from Clients with same ID. boost::mqtt5::no_local_e::no, // Forward message from Clients with same ID.
async_mqtt5::retain_as_published_e::retain, // Keep the original RETAIN flag. boost::mqtt5::retain_as_published_e::retain, // Keep the original RETAIN flag.
async_mqtt5::retain_handling_e::send // Send retained messages when the subscription is established. boost::mqtt5::retain_handling_e::send // Send retained messages when the subscription is established.
}}; }};
// Subscribe to a single Topic. // Subscribe to a single Topic.
auto && [ec, sub_codes, sub_props] = auto && [ec, sub_codes, sub_props] =
co_await _mqtt_client.async_subscribe(sub_topic, async_mqtt5::subscribe_props{}, use_nothrow_awaitable); co_await _mqtt_client.async_subscribe(sub_topic, boost::mqtt5::subscribe_props{}, use_nothrow_awaitable);
// Note: you can subscribe to multiple Topics in one mqtt_client::async_subscribe call. // Note: you can subscribe to multiple Topics in one mqtt_client::async_subscribe call.
// An error can occur as a result of: // An error can occur as a result of:
@ -65,7 +65,7 @@ struct AsyncMqttClient::AsyncMqttClientImpl {
// Receive an Appplication Message from the subscribed Topic(s). // Receive an Appplication Message from the subscribed Topic(s).
auto && [ec, topic, payload, publish_props] = co_await _mqtt_client.async_receive(use_nothrow_awaitable); auto && [ec, topic, payload, publish_props] = co_await _mqtt_client.async_receive(use_nothrow_awaitable);
if(ec == async_mqtt5::client::error::session_expired) { if(ec == boost::mqtt5::client::error::session_expired) {
/// TODO connect first, then subscribe to all topics /// TODO connect first, then subscribe to all topics
// The Client has reconnected, and the prior session has expired. // The Client has reconnected, and the prior session has expired.
// As a result, any previous subscriptions have been lost and must be reinstated. // As a result, any previous subscriptions have been lost and must be reinstated.