lacteApp
C++17 service for Lacte hardware
Loading...
Searching...
No Matches
ui Directory Reference
Directory dependency graph for ui:

Directories

 
console
 
framebuffer

Files

 
UiApp.hpp
 
UiAppFactory.hpp

Detailed Description

ui_bus

The lacte bus protocol between the Lacte UI process/thread and the Lacte backend (the milk dispenser controller) lives in LacteApp/bus/ModelBusClient.hpp (the backend/Model side) and LacteApp/bus/UiBusClient.hpp (the UI side) - message types in LacteApp/bus/Envelopes.hpp. Both are built directly on top of protolib's protocols/bus/Bus.hpp (insitech::bus::LocalClient - knows nothing about lacte). ModelBusClient/UiBusClient are where the actual protocol lives: serving the backend's request topics, listening for backend pushes, and issuing the UI's blocking requests are all methods on them — there's no separate "wiring" layer of free functions next to a pass-through wrapper.

The protocol covers:

  • enabling/disabling the UI,
  • getting/setting backend parameters (e.g. aeration),
  • fetching a device snapshot (board online state, firmware/app versions, milk system serial number, expiry date, remaining percent, error flags),
  • toggling a virtual test board, used for console/desktop testing when there is no real dispenser board attached.

Architecture

Two distinct client types, not one shared class trying to serve both roles: ModelBusClient (built and owned by Model itself, see Model::ConnectBus()) and UiBusClient (built and owned by UiApp itself, see UiApp::ConnectBus()). Both IS-A insitech::bus::LocalClient (see protolib's protocols/bus/Bus.hpp) and know nothing about bus mechanics themselves (framing, dispatch, addressing) - just the wire format for each of their own methods. Every client Connect()s onto the SAME one shared insitech::bus::Bus<RX,TX> node LacteApp owns (see AppContext::bus) at its own distinct address - LacteApp only ever builds that ONE shared bus node itself; every participant (Model, the UI, the DB) builds and configures its OWN client onto it, implementing its own subscriptions/publications, rather than being handed an already-built one:

protolib::bus (insitech::bus::Bus)
UI process/thread Backend process/thread
+---------------------+ requests (5 topics) +------------------------+
| ConsoleUi/ | --------------------> | Model |
| FramebufferUi | <-------------------- | (bus.ServeModel(this) |
| (bus.SubscribeRun- | replies | Model is the |
| time() + Check/ | | ModelBusHandler) |
| Get/Set) | | |
| | <-------------------- | |
| | pushed commands + | |
| | snapshots (2 topics) | |
+---------------------+ +------------------------+
  • bus.ServeModel(handler) — call it once on the backend's ModelBusClient, with a ModelBusHandler& implementation (Model implements it directly, see Model::ConnectBus()). It advertises and serves the five request/reply topics. To push state to the UI whenever it changes, the backend also calls notifier.EnableUi(handler)/ notifier.DisableUi()/notifier.PublishSnapshot(handler) on a SECOND client it holds — a UiBusClient, not a ModelBusClient (that's UI protocol knowledge, so it lives on the UI's own client class regardless of which side calls it — see UiBusClient.hpp's file doc comment).
  • bus.SubscribeRuntime(runtime) — call it once on the UI's UiBusClient, with a shared_ptr<UiRuntime> (the UI's cached enabled/aeration/snapshot state, updated thread-safely - only a weak_ptr is captured internally, so runtime's and the client's teardown order relative to each other doesn't matter). It subscribes to the two topics the backend pushes on. The UI issues blocking requests via bus.CheckHealth()/bus.SetParam()/bus.GetParam()/bus.GetSnapshot()/ bus.SetTestBoardOnline() — there's no separate client facade class: UiApp (see LacteApp/ui/UiApp.hpp) builds and owns the UiBusClient/UiRuntime pair via its constructor/ConnectBus()/Bus()/ Runtime(), and concrete UIs (ConsoleUi, FramebufferUi) call these methods directly on Bus()/Runtime().

Both sides communicate only through the message types in Envelopes.hpp and never share any C++ state directly — everything crosses the insitech::bus::Bus (an in-process EchoInterface-backed pair in tests, MulticastUdpInterface in the real app — see LacteApp/App.cpp).

Bus topics

Request/reply topics (UI -> backend)

Topic (kTopic) Request type Reply type
lacte.backend.health HealthRequest (no payload) StatusReply
lacte.backend.param.get ParamGetRequest{param} ParamGetReply{ok, error, param, value}
lacte.backend.param.set ParamSetRequest{param, value} StatusReply
lacte.backend.snapshot.get SnapshotRequest (no payload) SnapshotReply{ok, error, snapshot}
lacte.backend.test.board.online.set TestBoardOnlineRequest{online} StatusReply (error is "not-available" or "rejected" on failure)

Each request type owns its own wire encoding (Build()/Parse(), see Envelopes.hpp) — callers never touch bytes or topic strings. Reply-only types (StatusReply, ParamGetReply, SnapshotReply) have no kTopic of their own: the topic they travel on is the paired request's, via ModelBusClient::Serve<Req, Reply>()/UiBusClient::Request<Req, Reply>() (both inherited directly from insitech::bus::LocalClient).

BoardSnapshot's fields: board_online (bool), aeration (int), app_version, fw_version, milk_system_sn, expiry_date (strings), and, only when present, remaining_percent and error_flags (ints; absence means the corresponding std::optional field is std::nullopt).

Backend-pushed topics (backend -> UI)

Topic (kTopic) Message type Meaning
lacte.ui.command UiCommand{enable} Backend telling the UI to enable or disable itself (notifier.EnableUi()/notifier.DisableUi())
lacte.ui.snapshot BoardSnapshotEvent{snapshot} Backend pushing a fresh device snapshot (notifier.PublishSnapshot())

bus.SubscribeRuntime() subscribes to both: on UiCommand it calls UiRuntime::SetEnabled(); on BoardSnapshotEvent it calls UiRuntime::ApplySnapshot() followed by UiRuntime::SetAeration().

Usage

Backend side (as in LacteApp/tests/UiBusTest.cpp)

// The ONE shared bus - an EchoInterface in tests, multicast in prod (see
// AppContext::bus). LacteApp is the only place that declares the
// concrete scratch-buffer Bus<RX,TX> type; every client (ModelBusClient,
// UiBusClient) only ever needs an IBus& to build through.
insitech::bus::Bus<rx_buffer, tx_buffer> shared_bus(100, proto::bus::make_identity("lacteApp", "lacteApp", 1, "lacte"));
shared_bus.set_interfaces(iface, iface);
auto bus = lacte::bus::ModelBusClient::Create(shared_bus, lacte::bus::kModelBusAddress, "lacte-model");
// A second client, purely to push UiCommand/BoardSnapshotEvent - a
// UiBusClient, not a ModelBusClient, since that's UI protocol knowledge
// (see UiBusClient.hpp's file doc comment). Needs its own distinct address.
auto notifier = lacte::bus::UiBusClient::Create(shared_bus, 4, "lacte-model-ui-notifier");
callbacks.health = [] { return true; };
callbacks.get_param = [&](std::string param) -> std::optional<int> {
if (param == lacte::bus::kParamAeration) return aeration;
return std::nullopt;
};
callbacks.set_param = [&](std::string param, int value) {
if (param != lacte::bus::kParamAeration) return false;
aeration = value;
return true;
};
callbacks.get_snapshot = [] {
snapshot.board_online = true;
snapshot.aeration = 88;
snapshot.app_version = "1.2.3";
return snapshot;
};
bus->ServeModel(callbacks);
// Push state to the UI whenever it changes:
notifier->EnableUi(callbacks); // or notifier->DisableUi()
notifier->PublishSnapshot(callbacks);
ModelBusClient: собственный клиент Model на шине lacte - создаётся и принадлежит самому Model (см.
UiBusClient: собственный клиент UI на шине lacte - создаётся и принадлежит самому UiApp (см.
ModelBusHandler, реализованный через пользовательские колбэки.
auto health() -> bool override
Здоров ли бэкенд/диспенсер.
auto set_param(const std::string &name, const int value) -> bool override
Записывает именованный параметр; возвращает, удалась ли запись.
auto get_param(const std::string &name) -> std::optional< int > override
Читает именованный параметр; nullopt, если параметр неизвестен.
auto get_snapshot() -> BoardSnapshot override
Формирует текущий снапшот устройства.
Моментальный снимок состояния диспенсера/платы, которым обмениваются бэкенд и UI (как запрос/ответ сн...
Definition Envelopes.hpp:73
bool board_online
Доступна/онлайн ли диспенсерная плата в данный момент.
Definition Envelopes.hpp:74
std::string app_version
Строка версии прикладной прошивки, выполняющейся на плате, если известна.
Definition Envelopes.hpp:77
int aeration
Текущий уровень аэрации (в процентах).
Definition Envelopes.hpp:76

UI side (as in LacteApp/ui/console/ConsoleUi.cpp)

// Same shared_bus as the backend side - a different client/address, since
// Model publishes UiCommand/BoardSnapshotEvent that this client subscribes
// to (see LacteApp.hpp's m_shared_bus_ doc comment for why these must stay
// separate clients, not the same one).
auto bus = lacte::bus::UiBusClient::Create(shared_bus, lacte::bus::kUiBusAddress, "lacte-ui");
// runtime adapts pushed enable/disable + aeration changes onto the actual
// GUI widgets/console state. shared_ptr, not a plain value: SubscribeRuntime()
// only captures a weak_ptr, so it's fine to outlive - or be outlived by -
// the bus client itself.
auto runtime = std::make_shared<lacte::bus::UiRuntime>(
[&](bool enabled) { /* start/stop the GUI, enable/disable input */ },
[&](int value) { win->set_level(value); });
bus->SubscribeRuntime(runtime);
// Periodic health/snapshot polling loop:
if (!bus->CheckHealth(*runtime, 300ms)) {
// health check disables `runtime` automatically on failure
}
if (auto snapshot = bus->GetSnapshot(300ms)) {
runtime->ApplySnapshot(*snapshot);
}
// Reading/writing a parameter:
if (auto aeration = bus->GetParam(lacte::bus::kParamAeration, 300ms)) {
runtime->SetAeration(*aeration);
}
bus->SetParam(lacte::bus::kParamAeration, runtime->Aeration(), 500ms);
// Console-only virtual board toggling:
bus->SetTestBoardOnline(true, 500ms);

In the real app, UiApp's constructor builds this UI's own UiBusClient (see LacteApp/ui/UiApp.hpp); UiApp::ConnectBus() builds the runtime and calls Bus().SubscribeRuntime(); concrete UIs call these methods directly on Bus()/Runtime().