lacteApp
C++17 service for Lacte hardware
Loading...
Searching...
No Matches
Envelopes.hpp
Go to the documentation of this file.
1
31#pragma once
32
33#include <Envelope.hpp>
34#include <cstdint>
35#include <optional>
36#include <stdexcept>
37#include <string>
38#include <vector>
39
40namespace lacte::bus {
41
43inline constexpr auto K_PARAM_AERATION = "aeration";
44
45// ---------------------------------------------------------------------------
46// Вспомогательные функции кодирования на проводе, общие для нескольких
47// типов сообщений ниже.
48// ---------------------------------------------------------------------------
49
50inline auto bool_to_string(const bool value) -> std::string {
51 return value ? "true" : "false";
52}
53
54inline auto string_to_bool(const std::string& value) -> bool {
55 return value == "true" || value == "1" || value == "online" ||
56 value == "enabled";
57}
58
61inline auto string_to_int(const std::string& value) -> int {
62 try {
63 return std::stoi(value);
64 } catch (const std::exception&) {
65 throw std::invalid_argument("lacte bus integer field is invalid: " + value);
66 }
67}
68
75 false};
76 int aeration{0};
77 std::string app_version;
79 std::string fw_version;
81 std::string milk_system_sn;
83 std::string expiry_date;
85 std::optional<std::uint64_t>
87 std::optional<std::uint16_t>
96 std::string system_state;
97};
98
114struct RfidData {
119 bool present{false};
120 std::string rfid_number;
121 std::string lacte_id;
122 std::string lacte_sn;
123 std::string mcu_uid;
124 std::string machine_sn;
126 std::string product_volume;
128 std::string prod_date;
129 std::string shelf_life;
130 std::string usage_time;
132 std::string activation_time;
134 std::string milk_counter;
136 std::string time_counter;
138};
139
140namespace detail {
141
142inline void encode_board_snapshot(insitech::bus::Envelope& envelope,
143 const BoardSnapshot& snapshot) {
144 envelope.m_headers["online"] = bool_to_string(snapshot.board_online);
145 envelope.m_headers["aeration"] = std::to_string(snapshot.aeration);
146 envelope.m_headers["app_version"] = snapshot.app_version;
147 envelope.m_headers["fw_version"] = snapshot.fw_version;
148 envelope.m_headers["milk_system_sn"] = snapshot.milk_system_sn;
149 envelope.m_headers["expiry_date"] = snapshot.expiry_date;
150 if (snapshot.remaining_percent) {
151 envelope.m_headers["remaining_percent"] =
152 std::to_string(*snapshot.remaining_percent);
153 }
154 if (snapshot.error_flags) {
155 envelope.m_headers["error_flags"] = std::to_string(*snapshot.error_flags);
156 }
157 if (!snapshot.system_state.empty()) {
158 envelope.m_headers["system_state"] = snapshot.system_state;
159 }
160}
161
162inline auto decode_board_snapshot(const insitech::bus::Envelope& envelope)
163 -> BoardSnapshot {
164 BoardSnapshot snapshot{};
165 if (const auto found = envelope.m_headers.find("online");
166 found != envelope.m_headers.end()) {
167 snapshot.board_online = string_to_bool(found->second);
168 }
169 if (const auto found = envelope.m_headers.find("aeration");
170 found != envelope.m_headers.end()) {
171 snapshot.aeration = string_to_int(found->second);
172 }
173 if (const auto found = envelope.m_headers.find("app_version");
174 found != envelope.m_headers.end()) {
175 snapshot.app_version = found->second;
176 }
177 if (const auto found = envelope.m_headers.find("fw_version");
178 found != envelope.m_headers.end()) {
179 snapshot.fw_version = found->second;
180 }
181 if (const auto found = envelope.m_headers.find("milk_system_sn");
182 found != envelope.m_headers.end()) {
183 snapshot.milk_system_sn = found->second;
184 }
185 if (const auto found = envelope.m_headers.find("expiry_date");
186 found != envelope.m_headers.end()) {
187 snapshot.expiry_date = found->second;
188 }
189 if (const auto found = envelope.m_headers.find("remaining_percent");
190 found != envelope.m_headers.end()) {
191 snapshot.remaining_percent =
192 static_cast<std::uint64_t>(string_to_int(found->second));
193 }
194 if (const auto found = envelope.m_headers.find("error_flags");
195 found != envelope.m_headers.end()) {
196 snapshot.error_flags =
197 static_cast<std::uint16_t>(string_to_int(found->second));
198 }
199 if (const auto found = envelope.m_headers.find("system_state");
200 found != envelope.m_headers.end()) {
201 snapshot.system_state = found->second;
202 }
203 return snapshot;
204}
205
206inline void encode_rfid_data(insitech::bus::Envelope& envelope,
207 const RfidData& data) {
208 envelope.m_headers["present"] = bool_to_string(data.present);
209 if (!data.present) {
210 return; // все остальные поля бессмысленны/пусты - см. doc-комментарий
211 // самой RfidData
212 }
213 envelope.m_headers["rfid_number"] = data.rfid_number;
214 envelope.m_headers["lacte_id"] = data.lacte_id;
215 envelope.m_headers["lacte_sn"] = data.lacte_sn;
216 envelope.m_headers["mcu_uid"] = data.mcu_uid;
217 envelope.m_headers["machine_sn"] = data.machine_sn;
218 envelope.m_headers["product_volume"] = data.product_volume;
219 envelope.m_headers["prod_date"] = data.prod_date;
220 envelope.m_headers["shelf_life"] = data.shelf_life;
221 envelope.m_headers["usage_time"] = data.usage_time;
222 envelope.m_headers["activation_time"] = data.activation_time;
223 envelope.m_headers["milk_counter"] = data.milk_counter;
224 envelope.m_headers["time_counter"] = data.time_counter;
225}
226
227inline auto decode_rfid_data(const insitech::bus::Envelope& envelope)
228 -> RfidData {
229 RfidData data{};
230 if (const auto found = envelope.m_headers.find("present");
231 found != envelope.m_headers.end()) {
232 data.present = string_to_bool(found->second);
233 }
234 if (!data.present) {
235 return data;
236 }
237 auto field = [&](const char* key, std::string& out) -> void {
238 if (const auto found = envelope.m_headers.find(key);
239 found != envelope.m_headers.end()) {
240 out = found->second;
241 }
242 };
243 field("rfid_number", data.rfid_number);
244 field("lacte_id", data.lacte_id);
245 field("lacte_sn", data.lacte_sn);
246 field("mcu_uid", data.mcu_uid);
247 field("machine_sn", data.machine_sn);
248 field("product_volume", data.product_volume);
249 field("prod_date", data.prod_date);
250 field("shelf_life", data.shelf_life);
251 field("usage_time", data.usage_time);
252 field("activation_time", data.activation_time);
253 field("milk_counter", data.milk_counter);
254 field("time_counter", data.time_counter);
255 return data;
256}
257
258} // namespace detail
259
260// ---------------------------------------------------------------------------
261// Типы сообщений. Тип, который реально используется в Publish()/Subscribe()/
262// как запрос Serve<Req,...>/Request<Req,...>, несёт K_TOPIC; тип,
263// используемый только как ответ, - нет (см. doc-комментарий файла выше).
264// ---------------------------------------------------------------------------
265
268 static constexpr auto K_TOPIC = "health";
269 static constexpr auto
270 kTopic = // NOLINT(readability-identifier-naming): баг protolib —
271 // serve<Req,Reply>() читает Req::kTopic,
272 // request<Req,Reply>()/request_to() читает Req::K_TOPIC для
273 // того же типа Req; оба имени должны существовать
274 // одновременно.
275 K_TOPIC;
276 [[nodiscard]] static auto build() -> std::vector<uint8_t> { return {}; }
277 static auto parse(const std::vector<uint8_t>& /*unused*/) -> HealthRequest {
278 return {};
279 }
280};
281
285 bool ok{false};
286 std::string error;
287
288 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
289 insitech::bus::Envelope envelope;
290 envelope.m_headers["status"] = ok ? "ok" : "error";
291 if (!error.empty()) {
292 envelope.m_headers["error"] = error;
293 }
294 return insitech::bus::encode_envelope(envelope);
295 }
296 static auto parse(const std::vector<uint8_t>& bytes) -> StatusReply {
297 const auto [m_headers, m_payload] = insitech::bus::decode_envelope(bytes);
298 StatusReply reply;
299 if (const auto found = m_headers.find("status"); found != m_headers.end()) {
300 reply.ok = found->second == "ok";
301 }
302 if (const auto found = m_headers.find("error"); found != m_headers.end()) {
303 reply.error = found->second;
304 }
305 return reply;
306 }
307};
308
311 static constexpr auto K_TOPIC = "param.get";
312 static constexpr auto
313 kTopic = // NOLINT(readability-identifier-naming): баг protolib —
314 // serve<Req,Reply>() читает Req::kTopic,
315 // request<Req,Reply>()/request_to() читает Req::K_TOPIC для
316 // того же типа Req; оба имени должны существовать
317 // одновременно.
318 K_TOPIC;
319 std::string param;
320
321 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
322 insitech::bus::Envelope envelope;
323 envelope.m_headers["param"] = param;
324 return insitech::bus::encode_envelope(envelope);
325 }
326 static auto parse(const std::vector<uint8_t>& bytes) -> ParamGetRequest {
327 const auto [m_headers, m_payload] = insitech::bus::decode_envelope(bytes);
328 ParamGetRequest request;
329 if (const auto found = m_headers.find("param"); found != m_headers.end()) {
330 request.param = found->second;
331 }
332 return request;
333 }
334};
335
338 bool ok{false};
339 std::string error;
340 std::string param;
341 int value{0};
342
343 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
344 insitech::bus::Envelope envelope;
345 envelope.m_headers["status"] = ok ? "ok" : "error";
346 if (!error.empty()) {
347 envelope.m_headers["error"] = error;
348 }
349 if (ok) {
350 envelope.m_headers["param"] = param;
351 envelope.m_headers["value"] = std::to_string(value);
352 }
353 return insitech::bus::encode_envelope(envelope);
354 }
355 static auto parse(const std::vector<uint8_t>& bytes) -> ParamGetReply {
356 const auto [m_headers, m_payload] = insitech::bus::decode_envelope(bytes);
357 ParamGetReply reply;
358 if (const auto found = m_headers.find("status"); found != m_headers.end()) {
359 reply.ok = found->second == "ok";
360 }
361 if (const auto found = m_headers.find("error"); found != m_headers.end()) {
362 reply.error = found->second;
363 }
364 if (const auto found = m_headers.find("param"); found != m_headers.end()) {
365 reply.param = found->second;
366 }
367 if (const auto found = m_headers.find("value"); found != m_headers.end()) {
368 reply.value = string_to_int(found->second);
369 }
370 return reply;
371 }
372};
373
376 static constexpr auto K_TOPIC = "param.set";
377 static constexpr auto
378 kTopic = // NOLINT(readability-identifier-naming): баг protolib —
379 // serve<Req,Reply>() читает Req::kTopic,
380 // request<Req,Reply>()/request_to() читает Req::K_TOPIC для
381 // того же типа Req; оба имени должны существовать
382 // одновременно.
383 K_TOPIC;
384 std::string param;
385 int value{0};
386
387 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
388 insitech::bus::Envelope envelope;
389 envelope.m_headers["param"] = param;
390 envelope.m_headers["value"] = std::to_string(value);
391 return insitech::bus::encode_envelope(envelope);
392 }
393 static auto parse(const std::vector<uint8_t>& bytes) -> ParamSetRequest {
394 const auto envelope = insitech::bus::decode_envelope(bytes);
395 ParamSetRequest request;
396 if (const auto found = envelope.m_headers.find("param");
397 found != envelope.m_headers.end()) {
398 request.param = found->second;
399 }
400 if (const auto found = envelope.m_headers.find("value");
401 found != envelope.m_headers.end()) {
402 request.value = string_to_int(found->second);
403 }
404 return request;
405 }
406};
407
411 static constexpr auto K_TOPIC = "snapshot.get";
412 static constexpr auto
413 kTopic = // NOLINT(readability-identifier-naming): баг protolib —
414 // serve<Req,Reply>() читает Req::kTopic,
415 // request<Req,Reply>()/request_to() читает Req::K_TOPIC для
416 // того же типа Req; оба имени должны существовать
417 // одновременно.
418 K_TOPIC;
419 [[nodiscard]] static auto build() -> std::vector<uint8_t> { return {}; }
420 static auto parse(const std::vector<uint8_t>& /*unused*/) -> SnapshotRequest {
421 return {};
422 }
423};
424
427 bool ok{false};
428 std::string error;
430
431 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
432 insitech::bus::Envelope envelope;
433 envelope.m_headers["status"] = ok ? "ok" : "error";
434 if (!error.empty()) {
435 envelope.m_headers["error"] = error;
436 }
438 return insitech::bus::encode_envelope(envelope);
439 }
440 static auto parse(const std::vector<uint8_t>& bytes) -> SnapshotReply {
441 const auto envelope = insitech::bus::decode_envelope(bytes);
442 SnapshotReply reply;
443 if (const auto found = envelope.m_headers.find("status");
444 found != envelope.m_headers.end()) {
445 reply.ok = found->second == "ok";
446 }
447 if (const auto found = envelope.m_headers.find("error");
448 found != envelope.m_headers.end()) {
449 reply.error = found->second;
450 }
451 reply.snapshot = detail::decode_board_snapshot(envelope);
452 return reply;
453 }
454};
455
461 static constexpr auto K_TOPIC = "rfid.get";
462 static constexpr auto
463 kTopic = // NOLINT(readability-identifier-naming): баг protolib —
464 // serve<Req,Reply>() читает Req::kTopic,
465 // request<Req,Reply>()/request_to() читает Req::K_TOPIC для
466 // того же типа Req; оба имени должны существовать
467 // одновременно.
468 K_TOPIC;
469 [[nodiscard]] static auto build() -> std::vector<uint8_t> { return {}; }
470 static auto parse(const std::vector<uint8_t>& /*unused*/) -> RfidDataRequest {
471 return {};
472 }
473};
474
477 bool ok{false};
478 std::string error;
480
481 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
482 insitech::bus::Envelope envelope;
483 envelope.m_headers["status"] = ok ? "ok" : "error";
484 if (!error.empty()) {
485 envelope.m_headers["error"] = error;
486 }
488 return insitech::bus::encode_envelope(envelope);
489 }
490 static auto parse(const std::vector<uint8_t>& bytes) -> RfidDataReply {
491 const auto envelope = insitech::bus::decode_envelope(bytes);
492 RfidDataReply reply;
493 if (const auto found = envelope.m_headers.find("status");
494 found != envelope.m_headers.end()) {
495 reply.ok = found->second == "ok";
496 }
497 if (const auto found = envelope.m_headers.find("error");
498 found != envelope.m_headers.end()) {
499 reply.error = found->second;
500 }
501 reply.data = detail::decode_rfid_data(envelope);
502 return reply;
503 }
504};
505
509 static constexpr auto K_TOPIC = "test.board.online.set";
510 static constexpr auto
511 kTopic = // NOLINT(readability-identifier-naming): баг protolib —
512 // serve<Req,Reply>() читает Req::kTopic,
513 // request<Req,Reply>()/request_to() читает Req::K_TOPIC для
514 // того же типа Req; оба имени должны существовать
515 // одновременно.
516 K_TOPIC;
517 bool online{false};
518
519 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
520 insitech::bus::Envelope envelope;
521 envelope.m_headers["online"] = bool_to_string(online);
522 return insitech::bus::encode_envelope(envelope);
523 }
524 static auto parse(const std::vector<uint8_t>& bytes)
526 const auto [m_headers, m_payload] = insitech::bus::decode_envelope(bytes);
528 if (const auto found = m_headers.find("online"); found != m_headers.end()) {
529 request.online = string_to_bool(found->second);
530 }
531 return request;
532 }
533};
534
536struct UiCommand {
537 static constexpr auto K_TOPIC = "command";
538 bool enable{false};
539
540 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
541 const std::string command = enable ? "enable" : "disable";
542 return {command.begin(), command.end()};
543 }
544 static auto parse(const std::vector<uint8_t>& bytes) -> UiCommand {
545 const std::string command(bytes.begin(), bytes.end());
546 return {command == "enable"};
547 }
548};
549
552 static constexpr auto K_TOPIC = "snapshot";
554
555 [[nodiscard]] auto build() const -> std::vector<uint8_t> {
556 insitech::bus::Envelope envelope;
558 return insitech::bus::encode_envelope(envelope);
559 }
560 static auto parse(const std::vector<uint8_t>& bytes) -> BoardSnapshotEvent {
561 return {
562 detail::decode_board_snapshot(insitech::bus::decode_envelope(bytes))};
563 }
564};
565
566} // namespace lacte::bus
void encode_rfid_data(insitech::bus::Envelope &envelope, const RfidData &data)
void encode_board_snapshot(insitech::bus::Envelope &envelope, const BoardSnapshot &snapshot)
auto decode_rfid_data(const insitech::bus::Envelope &envelope) -> RfidData
auto decode_board_snapshot(const insitech::bus::Envelope &envelope) -> BoardSnapshot
auto string_to_int(const std::string &value) -> int
Definition Envelopes.hpp:61
constexpr auto K_PARAM_AERATION
Имя параметра бэкенда (и ключ заголовка снапшота) для уровня аэрации.
Definition Envelopes.hpp:43
auto string_to_bool(const std::string &value) -> bool
Definition Envelopes.hpp:54
auto bool_to_string(const bool value) -> std::string
Definition Envelopes.hpp:50
Push backend -> UI: сериализованный снапшот платы.
auto build() const -> std::vector< uint8_t >
static constexpr auto K_TOPIC
static auto parse(const std::vector< uint8_t > &bytes) -> BoardSnapshotEvent
Моментальный снимок состояния диспенсера/платы, которым обмениваются бэкенд и UI (как запрос/ответ сн...
Definition Envelopes.hpp:73
bool board_online
Доступна/онлайн ли диспенсерная плата в данный момент.
Definition Envelopes.hpp:74
std::optional< std::uint64_t > remaining_percent
Оставшийся процент молока, если известен.
Definition Envelopes.hpp:86
std::string expiry_date
Срок годности подключённой молочной системы, как сообщает бэкенд.
Definition Envelopes.hpp:83
std::optional< std::uint16_t > error_flags
Битовая маска активных флагов ошибок, если известна.
Definition Envelopes.hpp:88
std::string fw_version
Строка версии низкоуровневой прошивки платы, если известна.
Definition Envelopes.hpp:79
std::string app_version
Строка версии прикладной прошивки, выполняющейся на плате, если известна.
Definition Envelopes.hpp:77
std::string system_state
Совмещённое состояние системы (online lacte-платы + HMI-сигнал двери, см.
Definition Envelopes.hpp:96
int aeration
Текущий уровень аэрации (в процентах).
Definition Envelopes.hpp:76
std::string milk_system_sn
Серийный номер подключённой молочной системы/картриджа, если известен.
Definition Envelopes.hpp:81
Запрос UI -> backend: жив ли бэкенд и здоров ли он? Без payload.
static constexpr auto kTopic
static auto parse(const std::vector< uint8_t > &) -> HealthRequest
static auto build() -> std::vector< uint8_t >
static constexpr auto K_TOPIC
Ответ на ParamGetRequest: значение запрошенного параметра.
static auto parse(const std::vector< uint8_t > &bytes) -> ParamGetReply
auto build() const -> std::vector< uint8_t >
Запрос UI -> backend: прочитать именованный параметр бэкенда.
static auto parse(const std::vector< uint8_t > &bytes) -> ParamGetRequest
auto build() const -> std::vector< uint8_t >
static constexpr auto K_TOPIC
static constexpr auto kTopic
Запрос UI -> backend: записать именованный параметр бэкенда.
auto build() const -> std::vector< uint8_t >
static auto parse(const std::vector< uint8_t > &bytes) -> ParamSetRequest
static constexpr auto kTopic
static constexpr auto K_TOPIC
СЫРЫЕ поля, считанные с текущей подключённой RFID-карты/молочной системы - ДЕРЖАТСЯ ОТДЕЛЬНО от Board...
std::string lacte_id
LacteId - идентификатор продукта/рецепта.
std::string shelf_life
Срок годности (сырое значение счётчика).
std::string rfid_number
Собственный сырой RFID-номер карты.
std::string product_volume
Общий объём продукта, которым была заполнена карта.
std::string lacte_sn
LacteSn - серийный номер продукта.
std::string prod_date
Дата производства (сырое значение счётчика).
std::string activation_time
Когда эта карта была впервые активирована (сырое значение счётчика).
std::string milk_counter
Количество напитков, выданных с этой карты.
std::string time_counter
Накопительный счётчик времени использования.
std::string machine_sn
Серийный номер диспенсера, на котором эта карта была активирована.
std::string mcu_uid
MCU UID собственного контроллера карты.
bool present
Отражают ли поля ниже реально считанную сейчас карту - зеркалит собственную защиту "ошибка rfid" в Mo...
std::string usage_time
Окно времени использования (сырое значение счётчика).
Ответ на RfidDataRequest: сырые данные RFID-карты.
static auto parse(const std::vector< uint8_t > &bytes) -> RfidDataReply
auto build() const -> std::vector< uint8_t >
Запрос UI -> backend: получить сырые данные текущей подключённой RFID-карты по требованию (почему это...
static auto build() -> std::vector< uint8_t >
static constexpr auto kTopic
static constexpr auto K_TOPIC
static auto parse(const std::vector< uint8_t > &) -> RfidDataRequest
Ответ на SnapshotRequest: полный снапшот состояния устройства.
static auto parse(const std::vector< uint8_t > &bytes) -> SnapshotReply
auto build() const -> std::vector< uint8_t >
Запрос UI -> backend: получить полный снапшот устройства по требованию.
static auto parse(const std::vector< uint8_t > &) -> SnapshotRequest
static auto build() -> std::vector< uint8_t >
static constexpr auto K_TOPIC
static constexpr auto kTopic
Универсальный ответ ok/error, переиспользуемый каждым запросом ниже, которому больше нечего сообщить ...
static auto parse(const std::vector< uint8_t > &bytes) -> StatusReply
auto build() const -> std::vector< uint8_t >
Запрос UI -> backend: переключить виртуальную тестовую плату (только для консольного тестирования).
static auto parse(const std::vector< uint8_t > &bytes) -> TestBoardOnlineRequest
static constexpr auto K_TOPIC
auto build() const -> std::vector< uint8_t >
Push backend -> UI: команда enable/disable.
static auto parse(const std::vector< uint8_t > &bytes) -> UiCommand
static constexpr auto K_TOPIC
auto build() const -> std::vector< uint8_t >