lacteApp
C++17 service for Lacte hardware
Loading...
Searching...
No Matches
UiBusClient.hpp
Go to the documentation of this file.
1
39#pragma once
40
41#include <Bus.hpp>
42#include <SubscribeOwned.hpp>
43#include <algorithm>
44#include <chrono>
45#include <cstdint>
46#include <functional>
47#include <memory>
48#include <mutex>
49#include <optional>
50#include <string>
51#include <utility>
52
53#include "bus/Envelopes.hpp"
55
56namespace lacte::bus {
57// ---------------------------------------------------------------------------
58// UiRuntime: UI-состояние, которым `UiBusClient::SubscribeRuntime()`
59// управляет на основе push-команд и снапшотов от бэкенда; методы-запросы
60// `UiBusClient` отправляют блокирующие запросы UI к бэкенду.
61// ---------------------------------------------------------------------------
62
68class UiRuntime {
69 public:
70 // Верхняя граница валидного значения аэрации на проводе (0-100%) -
71 // отдельная от ConsoleModel::K_MAX_AERATION_PERCENT константа (та живёт
72 // выше по стеку, зависит от UiBusClient.hpp, а не наоборот), но то же
73 // самое значение по той же причине: у платы поле аэрации - uint8_t
74 // (0-255), значение вне 0-100 не может быть валидным процентом.
75 static constexpr int K_MAX_AERATION_PERCENT = 100;
76 using SetEnabledCallback = std::function<void(bool)>;
77 using SetAerationCallback = std::function<void(int)>;
78 using ApplySnapshotCallback = std::function<void(const BoardSnapshot&)>;
79
82 : m_set_enabled_(std::move(set_enabled)),
83 m_set_aeration_(std::move(set_aeration)),
84 m_apply_snapshot_(std::move(apply_snapshot)) {}
85
86 // Защищено m_mutex_: эти сеттеры (вызываемые из того потока, что
87 // публикует соответствующее событие - см.
88 // UiBusClient::SubscribeRuntime()) и геттеры ниже (вызываемые из того
89 // потока, что владеет UI, например его цикла рендера) по-настоящему
90 // гоняются друг с другом (race).
91 void set_enabled(const bool enabled) {
92 {
93 std::scoped_lock lock(m_mutex_);
94 m_enabled_ = enabled;
95 }
96 if (m_set_enabled_) {
97 m_set_enabled_(enabled);
98 }
99 }
100
101 // Единственная точка входа значения аэрации в UiRuntime - и от
102 // локального +/- пользователя (уже 0-100, см. ConsoleModel::
103 // set_aeration()), и от самой платы (см. ConsoleModel::
104 // refresh_snapshot_with_aeration(), напрямую снапшот -> сюда, без
105 // собственного клэмпа по пути) - клэмп именно здесь гарантирует, что
106 // некорректное сырое значение платы (>100, до 255 - uint8_t на проводе)
107 // никогда не попадёт на экран как есть.
108 void set_aeration(const int value) {
109 const int clamped = std::clamp(value, 0, K_MAX_AERATION_PERCENT);
110 {
111 std::scoped_lock lock(m_mutex_);
112 m_aeration_ = clamped;
113 }
114 if (m_set_aeration_) {
115 m_set_aeration_(clamped);
116 }
117 }
118
119 void apply_snapshot(const BoardSnapshot& snapshot) {
120 {
121 std::scoped_lock lock(m_mutex_);
122 m_last_snapshot_ = snapshot;
123 }
124 if (m_apply_snapshot_) {
125 m_apply_snapshot_(snapshot);
126 }
127 }
128
130 [[nodiscard]] auto enabled() const -> bool {
131 std::scoped_lock lock(m_mutex_);
132 return m_enabled_;
133 }
134
136 [[nodiscard]] auto aeration() const -> int {
137 std::scoped_lock lock(m_mutex_);
138 return m_aeration_;
139 }
140
143 [[nodiscard]] auto last_snapshot() const -> std::optional<BoardSnapshot> {
144 std::scoped_lock lock(m_mutex_);
145 return m_last_snapshot_;
146 }
147
148 private:
149 SetEnabledCallback m_set_enabled_;
150 SetAerationCallback m_set_aeration_;
151 ApplySnapshotCallback m_apply_snapshot_;
152 mutable std::mutex m_mutex_;
153 bool m_enabled_{false};
154 int m_aeration_{0};
155 std::optional<BoardSnapshot> m_last_snapshot_;
156};
157
163inline constexpr uint32_t K_UI_BUS_ADDRESS = 2;
164
165// ---------------------------------------------------------------------------
166// UiBusClient: см. doc-комментарий файла выше.
167// ---------------------------------------------------------------------------
168
169class UiBusClient : public insitech::bus::LocalClient {
170 public:
192 static auto create(insitech::bus::IHub& bus, const uint32_t address,
193 const std::string& name,
194 const std::string& binary = "lacteApp")
195 -> std::shared_ptr<UiBusClient> {
196 auto self = std::shared_ptr<UiBusClient>(new UiBusClient(
197 address, proto::bus::make_identity(name, binary, 1, "lacte"),
198 bus.make_client_bindings(address)));
199 bus.register_local_client(address, self);
200 // Объявлено один раз здесь (не при каждом вызове Publish() -
201 // Publish<Msg>() сам сознательно не принимает описание, см. его
202 // собственный doc-комментарий в Bus.hpp), чтобы эти топики появлялись
203 // с реальным описанием в Hub::build_report(), а не с пустым, к которому
204 // иначе откатился бы advertise_if_new().
205 self->advertise(UiCommand::K_TOPIC,
206 "Model-pushed command enabling/disabling the UI");
207 self->advertise(BoardSnapshotEvent::K_TOPIC,
208 "Model-pushed full board snapshot");
209 return self;
210 }
211
212 UiBusClient(const UiBusClient&) = delete;
213 auto operator=(const UiBusClient&) -> UiBusClient& = delete;
214
216 void set_ui_enabled(const bool enabled) { publish(UiCommand{enabled}); }
217
220 void enable_ui(ModelBusHandler& handler) { set_ui_enabled(handler.health()); }
221
223 void disable_ui() { set_ui_enabled(false); }
224
227 publish(BoardSnapshotEvent{handler.get_snapshot()});
228 }
229
244 void subscribe_runtime(const std::shared_ptr<UiRuntime>& runtime) {
245 insitech::bus_utils::subscribe_owned_typed<UiCommand>(
246 *this, runtime,
247 [](UiRuntime& ui_runtime, const UiCommand& command) -> void {
248 ui_runtime.set_enabled(command.enable);
249 },
250 "Model-pushed command enabling/disabling this UI");
251 insitech::bus_utils::subscribe_owned_typed<BoardSnapshotEvent>(
252 *this, runtime,
253 [](UiRuntime& ui_runtime, const BoardSnapshotEvent& event) -> void {
254 ui_runtime.apply_snapshot(event.snapshot);
255 ui_runtime.set_aeration(event.snapshot.aeration);
256 },
257 "Model-pushed full board snapshot");
258 }
259
266 auto check_health(UiRuntime& runtime, const std::chrono::milliseconds timeout)
267 -> bool {
268 if (const auto reply = request<HealthRequest, StatusReply>({}, timeout);
269 !reply || !reply->ok) {
270 runtime.set_enabled(false);
271 return false;
272 }
273 return true;
274 }
275
278 auto set_param(std::string param, const int value,
279 const std::chrono::milliseconds timeout) -> bool {
280 const auto reply = request<ParamSetRequest, StatusReply>(
281 {std::move(param), value}, timeout);
282 return reply && reply->ok;
283 }
284
286 auto get_param(std::string param, const std::chrono::milliseconds timeout)
287 -> std::optional<int> {
288 auto reply =
289 request<ParamGetRequest, ParamGetReply>({std::move(param)}, timeout);
290 if (!reply || !reply->ok) {
291 return std::nullopt;
292 }
293 return reply->value;
294 }
295
297 auto get_snapshot(const std::chrono::milliseconds timeout)
298 -> std::optional<BoardSnapshot> {
299 auto reply = request<SnapshotRequest, SnapshotReply>({}, timeout);
300 if (!reply || !reply->ok) {
301 return std::nullopt;
302 }
303 return reply->snapshot;
304 }
305
310 auto get_rfid_data(const std::chrono::milliseconds timeout)
311 -> std::optional<RfidData> {
312 auto reply = request<RfidDataRequest, RfidDataReply>({}, timeout);
313 if (!reply || !reply->ok) {
314 return std::nullopt;
315 }
316 return reply->data;
317 }
318
321 auto set_test_board_online(const bool online,
322 const std::chrono::milliseconds timeout) -> bool {
323 const auto reply =
324 request<TestBoardOnlineRequest, StatusReply>({online}, timeout);
325 return reply && reply->ok;
326 }
327
328 private:
329 // Приватный: только Create() может сконструировать экземпляр, так что
330 // нигде не может оказаться UiBusClient, который никогда не был
331 // зарегистрирован на своей шине (см. doc-комментарий Create()) - обычный
332 // публичный конструктор не смог бы это гарантировать.
333 UiBusClient(const uint32_t address, const proto::bus::NodeIdentity& identity,
334 Bindings bindings)
335 : LocalClient(address, identity, std::move(bindings)) {}
336};
337} // namespace lacte::bus
Типы wire-формата, которыми обмениваются по шине lacte: BoardSnapshot (доменное значение) и типы сооб...
ModelBusClient: собственный клиент Model на шине lacte - создаётся и принадлежит самому Model (см.
Серверный контракт: реализуйте эти методы, и ModelBusClient::ServeModel() будет их обслуживать.
virtual auto health() -> bool=0
Здоров ли бэкенд/диспенсер.
virtual auto get_snapshot() -> BoardSnapshot=0
Формирует текущий снапшот устройства.
void disable_ui()
Публикует команду выключения для UI.
UiBusClient(const UiBusClient &)=delete
auto operator=(const UiBusClient &) -> UiBusClient &=delete
void set_ui_enabled(const bool enabled)
Публикует команду включения/выключения для UI.
auto set_test_board_online(const bool online, const std::chrono::milliseconds timeout) -> bool
Запрашивает у бэкенда переключить онлайн-состояние виртуальной тестовой платы (только для тестировани...
auto check_health(UiRuntime &runtime, const std::chrono::milliseconds timeout) -> bool
Выполняет блокирующую проверку состояния (health check) бэкенда.
void publish_snapshot(ModelBusHandler &handler)
Публикует текущий снапшот устройства для UI.
void enable_ui(ModelBusHandler &handler)
Публикует команду включения для UI, если только бэкенд не нездоров (тогда выключает).
static auto create(insitech::bus::IHub &bus, const uint32_t address, const std::string &name, const std::string &binary="lacteApp") -> std::shared_ptr< UiBusClient >
Создаёт UiBusClient по адресу address на bus и регистрирует его там одним вызовом - см.
auto get_snapshot(const std::chrono::milliseconds timeout) -> std::optional< BoardSnapshot >
Запрашивает у бэкенда полный снапшот устройства.
auto set_param(std::string param, const int value, const std::chrono::milliseconds timeout) -> bool
Запрашивает у бэкенда установить именованный параметр в новое значение.
auto get_rfid_data(const std::chrono::milliseconds timeout) -> std::optional< RfidData >
Запрашивает у бэкенда сырые данные текущей подключённой RFID-карты (см.
void subscribe_runtime(const std::shared_ptr< UiRuntime > &runtime)
Подписывает runtime на два push-топика от бэкенда.
auto get_param(std::string param, const std::chrono::milliseconds timeout) -> std::optional< int >
Запрашивает текущее значение именованного параметра бэкенда.
UI-состояние, управляемое push-сообщениями от бэкенда (см.
void apply_snapshot(const BoardSnapshot &snapshot)
std::function< void(bool)> SetEnabledCallback
auto aeration() const -> int
Сообщает текущее удерживаемое значение aeration.
void set_enabled(const bool enabled)
std::function< void(int)> SetAerationCallback
static constexpr int K_MAX_AERATION_PERCENT
auto enabled() const -> bool
Сообщает текущее состояние enabled этого UI.
UiRuntime(SetEnabledCallback set_enabled, SetAerationCallback set_aeration, ApplySnapshotCallback apply_snapshot={})
auto last_snapshot() const -> std::optional< BoardSnapshot >
Возвращает последний снапшот, переданный в ApplySnapshot(), если он есть.
std::function< void(const BoardSnapshot &)> ApplySnapshotCallback
void set_aeration(const int value)
constexpr uint32_t K_UI_BUS_ADDRESS
Предпочтительный адрес клиента UI (НЕ собственный own_address_ общей шины, который LacteApp выбирает ...
Push backend -> UI: сериализованный снапшот платы.
static constexpr auto K_TOPIC
Моментальный снимок состояния диспенсера/платы, которым обмениваются бэкенд и UI (как запрос/ответ сн...
Definition Envelopes.hpp:73
int aeration
Текущий уровень аэрации (в процентах).
Definition Envelopes.hpp:76
Push backend -> UI: команда enable/disable.
static constexpr auto K_TOPIC