lacteApp
C++17 service for Lacte hardware
Loading...
Searching...
No Matches
ConsoleModel.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstdint>
5#include <mutex>
6#include <optional>
7#include <sstream>
8#include <string>
9
10#include "bus/UiBusClient.hpp"
11
12namespace lacte::ui {
13
14// optional_text форматирует значение из BoardSnapshot в то же текстовое
15// представление, что видит консольный оператор (ConsoleModel::snapshot())
16// - вынесены сюда (а не остались file-local в ConsoleUi.cpp) специально
17// для того, чтобы ConsoleModel::DisplayState оставался пригоден для
18// прямой сборки в GTest (см. LacteApp/tests/ConsoleUiDisplayTest.cpp) без
19// копии этой логики форматирования.
20inline auto optional_text(const std::optional<std::uint64_t>& value)
21 -> std::string {
22 if (!value) {
23 return "-";
24 }
25 return std::to_string(*value) + "%";
26}
27
28inline auto optional_text(const std::optional<std::uint16_t>& value)
29 -> std::string {
30 if (!value) {
31 return "-";
32 }
33 std::ostringstream out;
34 out << "0x" << std::hex << *value;
35 return out.str();
36}
37
38// Консольная модель: политика двери/аэрации/тест-платы поверх шины —
39// подключение живёт в UiApp (см. ConsoleUi::exec()), здесь только вызовы
40// методов UiBusClient (UiBusClient.hpp) напрямую на m_bus_/m_runtime_.
41// Вынесена из ConsoleUi.cpp в собственный заголовок (была в анонимном
42// namespace, видна только этому единственному .cpp) специально для того,
43// чтобы её можно было собрать напрямую в GTest поверх того же
44// BusFixture, что уже использует LacteApp/tests/UiBusTest.cpp - без
45// терминала/клавиатуры ConsoleModel не знает вообще ничего о вводе-
46// выводе, вся эта логика (TerminalRawMode/read_key/run_console_ansi)
47// осталась в ConsoleUi.cpp.
48//
49// Действия сериализованы через m_mutex_ - методы вызываются из основного
50// цикла run_console_ansi() (ConsoleUi.cpp) либо напрямую из теста,
51// синхронизация нужна на случай, если этот класс когда-нибудь снова
52// будет опрашиваться из отдельного потока.
54 public:
55 struct DisplayState {
56 bool ui_enabled{false};
57 bool door_open{false};
58 bool last_health{false};
59 bool board_online{false};
60 bool has_snapshot{false};
61 int aeration{0};
62 std::string app_version{"-"};
63 std::string fw_version{"-"};
64 std::string milk_system_sn{"-"};
65 std::string expiry_date{"-"};
66 std::string remaining{"-"};
67 std::string errors{"-"};
68 std::string status;
69 // Совмещённое состояние системы (online lacte-платы + HMI-сигнал
70 // двери, см. lacte::SystemState/SystemStateResolver в SystemState.hpp
71 // и BoardSnapshot::system_state) - "door_open"/"normal"/
72 // "board_disconnected"/"board_not_installed", либо "-" у снапшота,
73 // построенного версией до появления этого поля.
74 std::string system_state{"-"};
75 };
76
77 // Стандартный таймаут туда-обратно для обычных вызовов шины этой модели
78 // (health/snapshot/param get-set) - достаточно короткий, чтобы консольный
79 // UI никогда не казался зависшим в ожидании недоступной платы.
80 static constexpr auto K_BUS_OP_TIMEOUT = std::chrono::milliseconds(300);
81 // Нескольким операциям (переключение платы online/offline, явное
82 // сохранение аэрации) даётся чуть больше запаса - это осознанные, редкие
83 // действия пользователя, а не непрерывный ритм
84 // poll_health()/refresh_snapshot().
85 static constexpr auto K_BUS_SLOW_OP_TIMEOUT = std::chrono::milliseconds(500);
86 static constexpr int K_MAX_AERATION_PERCENT = 100;
87
88 // Управляет client/runtime, которыми владеет и которые подключила база
89 // UiApp; консоль добавляет поверх только свою политику
90 // двери/аэрации/статуса.
92 : m_bus_(bus), m_runtime_(runtime) {}
93
94 void poll_health() {
95 std::scoped_lock lock(m_mutex_);
96 m_last_health_ = m_bus_.check_health(m_runtime_, K_BUS_OP_TIMEOUT);
97 if (!m_last_health_) {
98 m_runtime_.set_enabled(false);
99 }
100 }
101
102 // Периодический опрос: обновляет поля снапшота, но НЕ трогает аэрацию —
103 // иначе фоновый поток каждую секунду затирает локальную позицию слайдера.
104 // Аэрация синхронизируется с backend только явно: открытие двери,
105 // кнопка Refresh, переключение платы.
107 std::scoped_lock lock(m_mutex_);
108 if (const auto snapshot = m_bus_.get_snapshot(K_BUS_OP_TIMEOUT)) {
109 m_runtime_.apply_snapshot(*snapshot);
110 }
111 }
112
114 std::scoped_lock lock(m_mutex_);
115 if (const auto snapshot = m_bus_.get_snapshot(K_BUS_OP_TIMEOUT)) {
116 m_runtime_.apply_snapshot(*snapshot);
117 m_runtime_.set_aeration(snapshot->aeration);
118 }
119 }
120
121 void open_door() {
122 std::scoped_lock lock(m_mutex_);
123 m_door_open_ = true;
124 poll_health();
125 if (!m_last_health_) {
126 m_status_ = "door open ignored: lacte board is offline";
127 return;
128 }
129
130 if (const auto aeration =
131 m_bus_.get_param(bus::K_PARAM_AERATION, K_BUS_OP_TIMEOUT)) {
132 m_runtime_.set_aeration(*aeration);
133 }
134 m_runtime_.set_enabled(true);
135 m_status_ = "door opened: ui enabled";
136 }
137
138 void close_door() {
139 std::scoped_lock lock(m_mutex_);
140 m_door_open_ = false;
141 if (m_runtime_.enabled()) {
142 m_bus_.set_param(bus::K_PARAM_AERATION, m_runtime_.aeration(),
144 }
145 m_runtime_.set_enabled(false);
146 m_status_ = "door closed: ui disabled";
147 }
148
149 void toggle_door() { m_door_open_ ? close_door() : open_door(); }
150
152 std::scoped_lock lock(m_mutex_);
153 m_requested_board_online_ = !m_requested_board_online_;
154 if (m_bus_.set_test_board_online(m_requested_board_online_,
156 m_status_ = m_requested_board_online_ ? "test board connected"
157 : "test board disconnected";
158 } else {
159 m_status_ = "board toggle is available only with virtual backend";
160 }
161 poll_health();
163 }
164
165 void set_aeration(const int value) {
166 std::scoped_lock lock(m_mutex_);
167 m_runtime_.set_aeration(std::clamp(value, 0, K_MAX_AERATION_PERCENT));
168 m_status_ = "aeration changed locally";
169 }
170
171 void adjust_aeration(const int delta) { set_aeration(aeration() + delta); }
172
174 std::scoped_lock lock(m_mutex_);
175 m_bus_.set_param(bus::K_PARAM_AERATION, m_runtime_.aeration(),
177 m_status_ = "aeration sent to backend";
178 }
179
180 void refresh() {
181 std::scoped_lock lock(m_mutex_);
182 poll_health();
184 m_status_ = "snapshot refreshed";
185 }
186
187 void shutdown() {
188 std::scoped_lock lock(m_mutex_);
189 m_runtime_.set_enabled(false);
190 }
191
192 [[nodiscard]] auto aeration() -> int {
193 std::scoped_lock lock(m_mutex_);
194 return m_runtime_.aeration();
195 }
196
197 // Быстрый снимок всего отображаемого состояния под одним замком — рендер
198 // копирует его и дальше строит вид без удержания m_mutex_.
199 [[nodiscard]] auto snapshot() -> DisplayState {
200 std::scoped_lock lock(m_mutex_);
201 DisplayState state;
202 state.ui_enabled = m_runtime_.enabled();
203 state.door_open = m_door_open_;
204 state.last_health = m_last_health_;
205 state.aeration = m_runtime_.aeration();
206 state.status = m_status_;
207 if (const auto snapshot = m_runtime_.last_snapshot()) {
208 state.has_snapshot = true;
209 state.board_online = snapshot->board_online;
210 state.app_version = snapshot->app_version;
211 state.fw_version = snapshot->fw_version;
212 state.milk_system_sn = snapshot->milk_system_sn;
213 state.expiry_date = snapshot->expiry_date;
214 state.remaining = optional_text(snapshot->remaining_percent);
215 state.errors = optional_text(snapshot->error_flags);
216 if (!snapshot->system_state.empty()) {
217 state.system_state = snapshot->system_state;
218 }
219 }
220 return state;
221 }
222
223 private:
224 std::recursive_mutex m_mutex_;
225 bus::UiBusClient& m_bus_;
226 bus::UiRuntime& m_runtime_;
227 bool m_door_open_{false};
228 bool m_requested_board_online_{true};
229 bool m_last_health_{false};
230 std::string m_status_{"connected"};
231};
232
233} // namespace lacte::ui
UiBusClient: собственный клиент UI на шине lacte - создаётся и принадлежит самому UiApp (см.
UI-состояние, управляемое push-сообщениями от бэкенда (см.
ConsoleModel(bus::UiBusClient &bus, bus::UiRuntime &runtime)
void set_aeration(const int value)
static constexpr auto K_BUS_OP_TIMEOUT
static constexpr int K_MAX_AERATION_PERCENT
static constexpr auto K_BUS_SLOW_OP_TIMEOUT
auto snapshot() -> DisplayState
void adjust_aeration(const int delta)
constexpr auto K_PARAM_AERATION
Имя параметра бэкенда (и ключ заголовка снапшота) для уровня аэрации.
Definition Envelopes.hpp:43
auto optional_text(const std::optional< std::uint64_t > &value) -> std::string