Esp32-RBGridUI
Library for creating UIs for the RBController app
Loading...
Searching...
No Matches
gridui.cpp
Go to the documentation of this file.
1#include <esp_log.h>
2#include <esp_timer.h>
3#include <stdio.h>
4#include <fstream>
5
6#include "gridui.h"
7#include "rbdns.h"
8#include "rbprotocol.h"
9#include "rbwebserver.h"
10#include "rbwifi.h"
11
12namespace gridui {
13
15
16static void defaultOnPacketReceived(const std::string& cmd, rbjson::Object* pkt) {
17 // Let GridUI handle its packets
18 if (UI.handleRbPacket(cmd, pkt))
19 return;
20 // ignore the rest
21}
22
23#ifdef RBGRIDUI_USING_ESP_IDF
24
25extern const uint8_t index_html_start[] asm("_binary_index_html_gz_start");
26extern const uint8_t index_html_end[] asm("_binary_index_html_gz_end");
27
28extern const uint8_t combined_js_start[] asm("_binary_combined_js_gz_start");
29extern const uint8_t combined_js_end[] asm("_binary_combined_js_gz_end");
30
31not_found_response_t webserverNotFoundCallback(const char *request_path) {
32 not_found_response_t resp = {};
33 if(strcmp(request_path, "/") == 0 || strcmp(request_path, "/index.html") == 0) {
34 resp.data = (uint8_t*)index_html_start;
35 resp.size = index_html_end - index_html_start;
36 resp.is_gzipped = 1;
37 } else if(strcmp(request_path, "/combined.js") == 0) {
38 resp.data = (uint8_t*)combined_js_start;
39 resp.size = combined_js_end - combined_js_start;
40 resp.is_gzipped = 1;
41 }
42 return resp;
43}
44#else
45not_found_response_t webserverNotFoundCallback(const char *request_path) {
46 not_found_response_t resp = {};
47 return resp;
48}
49#endif
50
52 : m_protocol(nullptr)
53 , m_protocol_ours(false)
54 , m_update_timer(nullptr)
55 , m_web_server_task(nullptr)
56 , m_state_mustarrive_id(UINT32_MAX)
57 , m_states_modified(false) {
58}
59
62
63void _GridUi::begin(rb::Protocol* protocol, int cols, int rows, bool enableSplitting) {
64 std::lock_guard<std::mutex> l(m_states_mu);
65 if (m_protocol != nullptr) {
66 ESP_LOGE("GridUI", "begin() called more than once!");
67 return;
68 }
69
70 m_protocol = protocol;
71 m_protocol_ours = false;
72
73 m_layout.reset(new rbjson::Object);
74 m_layout->set("cols", cols);
75 m_layout->set("rows", rows);
76 m_layout->set("enableSplitting", new rbjson::Bool(enableSplitting));
77}
78
79rb::Protocol* _GridUi::begin(const char* owner, const char* deviceName) {
80 // Start serving the web page
81 m_web_server_task = rb_web_start(80);
82 if(m_web_server_task == NULL) {
83 ESP_LOGE("GridUI", "failed to call rb_web_start");
84 return nullptr;
85 }
86
87#ifdef RBGRIDUI_USING_ESP_IDF
88 rb_web_set_not_found_callback(webserverNotFoundCallback);
89#endif
90
91 auto protocol = new rb::Protocol(owner, deviceName, "Compiled at " __DATE__ " " __TIME__, defaultOnPacketReceived);
92 protocol->start();
93
95 m_protocol_ours = true;
96 return protocol;
97}
98
99rb::Protocol* _GridUi::beginConnect(const char* owner, const char* deviceName, const char* wifiSSID, const char* wifiPassword) {
100 rb::WiFi::connect(wifiSSID, wifiPassword);
101 return begin(owner, deviceName);
102}
103
104rb::Protocol* _GridUi::beginStartAp(const char* owner, const char* deviceName, const char* wifiSSID, const char* wifiPassword, bool withCaptivePortal) {
105 rb::WiFi::startAp(wifiSSID, wifiPassword);
106 if (withCaptivePortal) {
107 rb::DnsServer::get().start();
108 }
109 return begin(owner, deviceName);
110}
111
112uint16_t _GridUi::generateUuidLocked() const {
113 while (1) {
114 const uint32_t rnd = esp_random();
115 if (checkUuidFreeLocked(rnd & 0xFFFF))
116 return rnd & 0xFFFF;
117 if (checkUuidFreeLocked(rnd >> 16))
118 return rnd >> 16;
119 }
120}
121
123 auto *protocol = m_protocol.load();
124 if(!protocol) {
125 ESP_LOGE("GridUI", "end() called when not initialized!");
126 return;
127 }
128
129 if(m_protocol_ours) {
130 protocol->stop();
131 delete protocol;
132 }
133 m_protocol = nullptr;
134
135 if(m_update_timer) {
136 esp_timer_stop(m_update_timer);
137 esp_timer_delete(m_update_timer);
138 m_update_timer = nullptr;
139 }
140
141 if(m_web_server_task) {
142 rb_web_stop(m_web_server_task);
143 m_web_server_task = nullptr;
144 }
145
146 {
147 std::lock_guard<std::mutex> guard(m_states_mu);
148 m_states.clear();
149 m_states.shrink_to_fit();
150 m_widgets.clear();
151 m_widgets.shrink_to_fit();
152 }
153
154 m_layout.reset();
155 m_state_mustarrive_id = 0;
156 m_states_modified = false;
157 m_tab_changed = false;
158 m_tab = 0;
159}
160
162 std::lock_guard<std::mutex> l(m_states_mu);
163 if (!m_layout) {
164 ESP_LOGE("GridUI", "commit() called with no layout prepared!");
165 return;
166 }
167
168 {
169
170 char buf[256];
171
172 snprintf(buf, sizeof(buf), "%s/layout.json", rb_web_get_files_root());
173
174 std::ofstream stream;
175 stream.rdbuf()->pubsetbuf(buf, sizeof(buf));
176 stream.open(buf, std::ofstream::trunc);
177
178 if(stream.fail()) {
179 ESP_LOGE("GridUI", "failed to open %s", buf);
180 return;
181 }
182
183 m_states.shrink_to_fit();
184
185 m_layout->serialize(stream);
186 m_layout.reset();
187
188 stream.seekp(-1, std::ofstream::cur);
189
190 stream << ",\"widgets\": [";
191 for (size_t i = 0; i < m_widgets.size(); ++i) {
192 if (i != 0) {
193 stream << ",";
194 }
195
196 auto& w = m_widgets[i];
197 w->serialize(stream);
198 if(strcmp(w->m_type, "Arm") == 0) {
199 w->extra().remove("info");
200 }
201 w.reset();
202 }
203 m_widgets.clear();
204 m_widgets.shrink_to_fit();
205
206 stream << "]}";
207
208 if(stream.fail()) {
209 ESP_LOGE("GridUI", "failed to serialize layout");
210 return;
211 }
212
213 stream.close();
214 if(stream.fail()) {
215 ESP_LOGE("GridUI", "failed to write layout");
216 return;
217 }
218 }
219
220 esp_timer_create_args_t args = {
221 .callback = stateChangeTask,
222 .arg = this,
223 .dispatch_method = ESP_TIMER_TASK,
224 .name = "gridui_state",
225#ifdef ESP_IDF_VERSION
226 .skip_unhandled_events = false,
227#endif
228 };
229
230 esp_timer_create(&args, &m_update_timer);
231 esp_timer_start_periodic(m_update_timer, 50 * 1000);
232}
233
234bool _GridUi::handleRbPacket(const std::string& cmd, rbjson::Object* pkt) {
235 if (cmd == "_gev") {
236 m_states_mu.lock();
237 auto* state = stateByUuidLocked(pkt->getInt("id"));
238 if (state == nullptr) {
239 m_states_mu.unlock();
240 return true;
241 }
242
243 auto* st = pkt->getObject("st");
244 if (st != nullptr) {
245 state->update(st);
246 }
247 m_states_mu.unlock();
248
249 state->call(pkt->getString("ev"));
250 } else if (cmd == "_gall") {
251 bool changed = false;
252 std::lock_guard<std::mutex> l(m_states_mu);
253 for (auto& itr : m_states) {
254 if (itr->remarkAllChanges())
255 changed = true;
256 }
257 if (changed) {
258 m_states_modified = true;
259 }
260 m_tab_changed = true;
261 } else {
262 return false;
263 }
264 return true;
265}
266
267void _GridUi::changeTab(uint16_t index) {
268 std::lock_guard<std::mutex> k(m_tab_mu);
269 m_tab_changed = true;
270 m_tab = index;
271}
272
273void _GridUi::stateChangeTask(void* selfVoid) {
274 auto* self = (_GridUi*)selfVoid;
275
276 auto* prot = self->protocol();
277 if (prot == nullptr || !prot->is_possessed())
278 return;
279
280 if (!prot->is_mustarrive_complete(self->m_state_mustarrive_id))
281 return;
282
283 if (self->m_states_modified.exchange(false)) {
284 std::unique_ptr<rbjson::Object> pkt(new rbjson::Object);
285 {
286 std::lock_guard<std::mutex> guard(self->m_states_mu);
287 char buf[6];
288 std::unique_ptr<rbjson::Object> state(new rbjson::Object);
289
290 const size_t size = self->m_states.size();
291 for (size_t i = 0; i < size; ++i) {
292 auto& s = self->m_states[i];
293 if (s->popChanges(*state.get())) {
294 snprintf(buf, sizeof(buf), "%d", (int)s->uuid());
295 pkt->set(buf, state.release());
296 state.reset(new rbjson::Object);
297 }
298 }
299 }
300 self->m_state_mustarrive_id = prot->send_mustarrive("_gst", pkt.release());
301 }
302
303 if (self->m_tab_changed.exchange(false)) {
304 std::lock_guard<std::mutex> lock(self->m_tab_mu);
305 std::unique_ptr<rbjson::Object> pkt(new rbjson::Object);
306
307 pkt->set("tab", self->m_tab);
308
309 self->m_state_mustarrive_id = prot->send_mustarrive("_gtb", pkt.release());
310 }
311}
312};
rb::Protocol * protocol() const
Definition gridui.h:82
rb::Protocol * beginConnect(const char *owner, const char *deviceName, const char *wifiSSID, const char *wifiPassword="")
Definition gridui.cpp:99
void begin(rb::Protocol *protocol, int cols=12, int rows=18, bool enableSplitting=true)
Definition gridui.cpp:63
bool handleRbPacket(const std::string &command, rbjson::Object *pkt)
Definition gridui.cpp:234
void changeTab(uint16_t index)
Definition gridui.cpp:267
rb::Protocol * beginStartAp(const char *owner, const char *deviceName, const char *wifiSSID, const char *wifiPassword="", bool withCaptivePortal=true)
Definition gridui.cpp:104
Definition arm.h:8
_GridUi UI
Definition gridui.cpp:14
static void defaultOnPacketReceived(const std::string &cmd, rbjson::Object *pkt)
Definition gridui.cpp:16
not_found_response_t webserverNotFoundCallback(const char *request_path)
Definition gridui.cpp:45