Uniot Core
0.8.1
Loading...
Searching...
No Matches
LispDevice.h
Go to the documentation of this file.
1/*
2 * This is a part of the Uniot project.
3 * Copyright (C) 2016-2023 Uniot <contact@uniot.io>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21#include <CBORStorage.h>
22#include <Date.h>
23#include <EventListener.h>
24#include <LispEvents.h>
25#include <MQTTDevice.h>
26#include <unLisp.h>
27
28#include <functional>
29
30namespace uniot {
31struct LispEvent {
32 struct Sender {
33 String type;
34 String id;
36 String eventID;
37 int32_t value;
38 uint64_t timestamp;
39};
40
41using LispEventInterceptor = std::function<bool(const LispEvent &event)>;
42
53class LispDevice : public MQTTDevice, public CBORStorage, public CoreEventListener {
54 public:
73
80 virtual void syncSubscriptions() override {
81 mTopicScript = MQTTDevice::subscribeDevice("script");
82 mTopicEvents = MQTTDevice::subscribeGroup("all", "event/+");
83 }
84
91 return unLisp::getInstance();
92 }
93
95 mEventInterceptor = interceptor;
96 }
97
98 void publishLispEvent(const String &eventID, int32_t value) {
99 CBORObject event;
100 event.put("eventID", eventID.c_str());
101 event.put("value", static_cast<int64_t>(value));
102 _populateAndPublishEvent(event.build());
103 }
104
112 if (CBORStorage::restore()) {
113 auto code = object().getString("code");
114 mPersist = object().getInt("persist");
115 mChecksum = object().getInt("checksum");
116
117 if (mPersist && code.length() > 0) {
118 getLisp().runCode(code);
119 }
120 }
121 }
122
132 bool store() {
133 object()
134 .put("persist", mPersist)
135 .put("checksum", (int)mChecksum)
136 .put("code", mPersist ? getLisp().getLastCode().c_str() : "")
137 .forceDirty(); // Avoid optimization. The pointer to the data may be the same. Read the warnings.
138
139 return CBORStorage::store();
140 }
141
151 virtual void onEventReceived(unsigned int topic, int msg) override {
155 if (!empty) {
156 mFailedWithError = true;
157
158 CBORObject packet;
159 packet.put("type", "error");
160 packet.put("timestamp", static_cast<int64_t>(Date::now()));
161 packet.put("msg", data.c_str());
162 publishDevice("debug/err", packet.build(), true);
163 UNIOT_LOG_ERROR("lisp error: %s", data.c_str());
164 }
165 });
166 return;
167 }
170 if (!empty) {
171 CBORObject packet;
172 packet.put("type", "log");
173 packet.put("timestamp", static_cast<int64_t>(Date::now()));
174 packet.put("msg", data.c_str());
175 publishDevice("debug/log", packet.build());
176 UNIOT_LOG_INFO("lisp log: %s", data.c_str());
177 }
178 });
179 return;
180 }
183 // NOTE: it is currently only used for debugging purposes
184 // UNIOT_LOG_TRACE_IF(!empty, "lisp: %s", data.toString().c_str());
185 });
186 return;
187 }
188 return;
189 }
192 // This is necessary to retrieve events marked as `retained` during the execution of a new script
193 this->unsubscribe(mTopicEvents);
194 this->subscribe(mTopicEvents);
195 return;
196 }
197 return;
198 }
201 CoreEventListener::receiveDataFromChannel(events::lisp::Channel::OUT_EVENT, [this](unsigned int id, bool empty, Bytes data) {
202 if (!empty) {
203 _populateAndPublishEvent(data);
204 }
205 });
206 }
207 return;
208 }
209 }
210
219 virtual void handle(const String &topic, const Bytes &payload) override {
220 if (MQTTDevice::isTopicMatch(mTopicScript, topic)) {
221 MQTTDevice::publishEmptyDevice("debug/err"); // clear previous errors
222 handleScript(payload);
223 return;
224 }
225 if (MQTTDevice::isTopicMatch(mTopicEvents, topic)) {
226 handleEvent(payload);
227 return;
228 }
229 }
230
240 void handleScript(const Bytes &payload) {
241 static bool firstPacketReceived = false;
242 CBORObject packet(payload);
243 auto script = Bytes(packet.getString("code"));
244 auto newPersist = packet.getBool("persist");
245 auto newChecksum = script.terminate().checksum();
246
247 auto ignoreScript = false;
248
249 if (!firstPacketReceived) {
250 auto isEqual = mChecksum == newChecksum;
251 ignoreScript = isEqual && mPersist && !mFailedWithError;
252 firstPacketReceived = true;
253 }
254
255 if (!ignoreScript) {
256 mChecksum = newChecksum;
257 mPersist = newPersist;
258 mFailedWithError = false;
259
260 // TODO: check signature here later
261 getLisp().runCode(script);
263 } else {
264 UNIOT_LOG_INFO("script ignored: %s", script.c_str());
265 }
266
267 // Free memory to avoid fragmentation
268 object().clean();
269 // NOTE: you may need getLasCode() somewhere else, but it has already cleaned here
270 getLisp().cleanLastCode(); // Is it safe?
271 }
272
280 void handleEvent(const Bytes &payload) {
281 if (payload.size() > 0) {
282 CBORObject event(payload);
283 auto eventID = event.getString("eventID");
284 auto valueStr = event.getValueAsString("value");
285
286 if (eventID.isEmpty()) {
287 UNIOT_LOG_WARN("received event with empty eventID, ignoring");
288 return;
289 }
290
291 if (valueStr.isEmpty()) {
292 UNIOT_LOG_WARN("received event '%s' with empty value, ignoring", eventID.c_str());
293 return;
294 }
295
296 auto value = valueStr.toInt();
297 auto isNumber = value || valueStr == "0";
298
299 if (!isNumber) {
300 UNIOT_LOG_WARN("received event '%s' with non-numeric value '%s', ignoring", eventID.c_str(), valueStr.c_str());
301 return;
302 }
303
304 auto sender = event.getMap("sender");
305 LispEvent lispEvent;
306 lispEvent.eventID = eventID;
307 lispEvent.value = value;
308 lispEvent.timestamp = event.getInt("timestamp");
309 lispEvent.sender.type = sender.getString("type");
310 lispEvent.sender.id = sender.getString("id");
311
312 if (mEventInterceptor && !mEventInterceptor(lispEvent)) {
313 // The event was not accepted by the interceptor
314 return;
315 }
316
319 }
320 }
321
322 private:
323 void _populateAndPublishEvent(const Bytes &eventData) {
324 CBORObject event(eventData);
325 event.put("timestamp", static_cast<int64_t>(Date::now()))
326 .putMap("sender")
327 .put("type", "device")
328 .put("id", MQTTDevice::getDeviceId().c_str());
329 auto eventDataBuilt = event.build();
330 auto eventID = event.getString("eventID");
331 MQTTDevice::publishGroup("all", "event/" + eventID, eventDataBuilt, true);
332 // NOTE: Do we need to have a separate primitive to publish not retained events?
333 }
334
335 LispEventInterceptor mEventInterceptor;
336 uint32_t mChecksum;
337 bool mPersist;
338 bool mFailedWithError;
339
340 String mTopicScript;
341 String mTopicEvents;
342};
343
344} // namespace uniot
Lisp interpreter event definitions for the Uniot event system.
Definition Bytes.h:38
const char * c_str() const
Gets the byte array as a C string.
Definition Bytes.h:266
size_t size() const
Gets the size of the byte array.
Definition Bytes.h:303
Definition CBORObject.h:40
long getInt(int key) const
Get an integer value at a specific integer key.
Definition CBORObject.h:399
CBORObject & put(int key, int value)
Put an integer value at a specific integer key.
Definition CBORObject.h:170
void forceDirty()
Force the object to be marked as dirty (modified)
Definition CBORObject.h:526
String getString(int key) const
Get a string value at a specific integer key.
Definition CBORObject.h:419
void clean()
Reset the object to an empty state.
Definition CBORObject.h:535
Bytes build() const
Build the CBOR data into binary format.
Definition CBORObject.h:499
bool getBool(int key) const
Get a boolean value at a specific integer key.
Definition CBORObject.h:379
virtual bool restore() override
Restore the CBOR object from the filesystem.
Definition CBORStorage.h:92
virtual bool store() override
Store the CBOR object to the filesystem.
Definition CBORStorage.h:74
CBORStorage(const String &path)
Constructs a new CBORStorage object.
Definition CBORStorage.h:46
CBORObject & object()
Get access to the underlying CBORObject.
Definition CBORStorage.h:60
static time_t now()
Returns the current Unix timestamp.
Definition Date.h:74
void emitEvent(unsigned int topic, int msg)
void receiveDataFromChannel(T_topic channel, DataChannelCallback callback)
Receives data from a specific channel on all connected EventBus instances.
Definition EventEntity.h:117
bool sendDataToChannel(T_topic channel, T_data data)
Sends data to a specific channel on all connected EventBus instances.
Definition EventEntity.h:98
EventListener * listenToEvent(unsigned int topic)
virtual void syncSubscriptions() override
Sets up MQTT topic subscriptions for the device.
Definition LispDevice.h:80
virtual void handle(const String &topic, const Bytes &payload) override
Processes MQTT messages received on subscribed topics.
Definition LispDevice.h:219
LispDevice()
Constructs a LispDevice instance.
Definition LispDevice.h:61
void runStoredCode()
Loads and executes previously stored code from persistent storage.
Definition LispDevice.h:111
void handleScript(const Bytes &payload)
Processes script payloads received via MQTT.
Definition LispDevice.h:240
void setEventInterceptor(LispEventInterceptor interceptor)
Definition LispDevice.h:94
virtual void onEventReceived(unsigned int topic, int msg) override
Processes events received from the unLisp interpreter.
Definition LispDevice.h:151
unLisp & getLisp()
Provides access to the unLisp interpreter instance.
Definition LispDevice.h:90
bool store()
Stores the current script state to persistent storage.
Definition LispDevice.h:132
void handleEvent(const Bytes &payload)
Processes event payloads received via MQTT.
Definition LispDevice.h:280
void publishLispEvent(const String &eventID, int32_t value)
Definition LispDevice.h:98
const String & subscribe(const String &topic)
Subscribes to a specific MQTT topic.
Definition MQTTDevice.cpp:74
MQTTDevice()
Constructs a new MQTTDevice instance.
Definition MQTTDevice.h:44
bool isTopicMatch(const String &storedTopic, const String &incomingTopic) const
Determines if a stored topic matches an incoming topic string using MQTT wildcards.
Definition MQTTDevice.cpp:137
void publishGroup(const String &groupId, const String &subTopic, const Bytes &payload, bool retained=false, bool sign=false)
Publishes a message to a group-specific subtopic.
Definition MQTTDevice.cpp:114
const String & subscribeGroup(const String &groupId, const String &subTopic)
Subscribes to a group-specific subtopic.
Definition MQTTDevice.cpp:92
const String & getDeviceId() const
Gets the device identifier.
Definition MQTTDevice.cpp:37
bool unsubscribe(const String &topic)
Unsubscribes from a specific topic.
Definition MQTTDevice.cpp:64
void publishEmptyDevice(const String &subTopic)
Publishes an empty message to a device-specific subtopic with retained flag set.
Definition MQTTDevice.cpp:120
void publishDevice(const String &subTopic, const Bytes &payload, bool retained=false, bool sign=false)
Publishes a message to a device-specific subtopic.
Definition MQTTDevice.cpp:108
const String & subscribeDevice(const String &subTopic)
Subscribes to a device-specific subtopic.
Definition MQTTDevice.cpp:83
static unLisp & getInstance()
Definition Singleton.h:73
Definition unLisp.h:219
void runCode(const Bytes &data)
Run Lisp code in the interpreter.
Definition unLisp.h:269
void cleanLastCode()
Clear the stored last executed code.
Definition unLisp.h:337
EventListener< unsigned int, int, Bytes > CoreEventListener
Type alias for the common EventListener configuration used in the core system.
Definition EventListener.h:100
#define UNIOT_LOG_INFO(...)
Log an INFO level message Used for general information about system operation. Only compiled if UNIOT...
Definition Logger.h:268
#define UNIOT_LOG_WARN(...)
Log an WARN level message Used for warnings about potentially problematic situations....
Definition Logger.h:247
#define UNIOT_LOG_ERROR(...)
Log an ERROR level message Used for critical errors that may prevent normal operation....
Definition Logger.h:226
@ OUT_LISP_REQUEST
Topic for Lisp requests to the application services.
Definition LispEvents.h:89
@ OUT_LISP_MSG
Topic for Lisp output messages and notifications.
Definition LispEvents.h:88
@ IN_LISP_EVENT
Topic for incoming events from application to Lisp.
Definition LispEvents.h:91
@ OUT_LISP_EVENT
Topic for outgoing events from Lisp to application.
Definition LispEvents.h:90
@ IN_NEW_EVENT
New incoming event received from application for Lisp processing.
Definition LispEvents.h:107
@ OUT_MSG_ADDED
Standard output message was added to the output buffer.
Definition LispEvents.h:102
@ OUT_MSG_LOG
Log message was generated by the Lisp interpreter.
Definition LispEvents.h:103
@ OUT_NEW_EVENT
New outgoing event generated by Lisp for the application.
Definition LispEvents.h:106
@ OUT_REFRESH_EVENTS
Request to refresh the event queue and process pending events.
Definition LispEvents.h:105
@ OUT_MSG_ERROR
Error message was generated due to Lisp execution failure.
Definition LispEvents.h:104
@ OUT_LISP_LOG
Channel for Lisp log messages and debug information.
Definition LispEvents.h:74
@ IN_EVENT
Channel for incoming events from the application to Lisp.
Definition LispEvents.h:77
@ OUT_LISP
Channel for standard Lisp output (stdout equivalent)
Definition LispEvents.h:73
@ OUT_LISP_ERR
Channel for Lisp error messages and exceptions.
Definition LispEvents.h:75
@ OUT_EVENT
Channel for outgoing events from Lisp to the application.
Definition LispEvents.h:76
Contains all classes and functions related to the Uniot Core.
std::function< bool(const LispEvent &event)> LispEventInterceptor
Definition LispDevice.h:41
Definition LispDevice.h:32
String type
Definition LispDevice.h:33
String id
Definition LispDevice.h:34
Definition LispDevice.h:31
String eventID
Definition LispDevice.h:36
uint64_t timestamp
Definition LispDevice.h:38
struct uniot::LispEvent::Sender sender
int32_t value
Definition LispDevice.h:37