Uniot Core
0.8.1
Loading...
Searching...
No Matches
unLisp.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
26
36
37#pragma once
38
39#include <Bytes.h>
40#include <CBORObject.h>
41#include <Common.h>
42#include <EventListener.h>
43#include <LimitedQueue.h>
44#include <LispEvents.h>
45#include <LispHelper.h>
46#include <Logger.h>
47#include <PrimitiveExpeditor.h>
48#include <Singleton.h>
49#include <TaskScheduler.h>
50#include <libminilisp.h>
51
52#ifndef UNIOT_LISP_HEAP
53#if defined(ESP32)
54#define UNIOT_LISP_HEAP 20480 // 20 KB for ESP32
55#elif defined(ESP8266)
56#define UNIOT_LISP_HEAP 8192 // 8 KB for ESP8266
57#else
58#define UNIOT_LISP_HEAP 4096 // 4 KB for other platforms
59#endif
60#endif
61
62namespace uniot {
63using namespace lisp;
64
66 public:
68 int32_t value;
69 int8_t errorCode;
70
72 IncomingEvent(int32_t v, int8_t err = 0) : value(v), errorCode(err) {}
73 };
74
75 void pushEvent(const Bytes &eventData) {
76 CBORObject eventObj(eventData);
77 auto eventID = eventObj.getString("eventID");
78 auto valueStr = eventObj.getValueAsString("value");
79
80 if (eventID.isEmpty() || valueStr.isEmpty()) {
81 return; // Invalid event
82 }
83
84 auto value = valueStr.toInt();
85 auto isNumber = value || valueStr == "0";
86 if (!isNumber) {
87 return; // Invalid event
88 }
89
90 bool isNewEvent = !mIncomingEvents.exist(eventID);
91 if (isNewEvent) {
92 auto eventQueue = std::make_shared<EventQueue>();
93 mIncomingEvents.put(eventID, eventQueue);
94 UNIOT_LOG_DEBUG("created new event queue for '%s'", eventID.c_str());
95 }
96
97 auto eventQueue = mIncomingEvents.get(eventID);
98 eventQueue->lastAccessed = millis();
99
100 size_t queueSizeBefore = eventQueue->queue.size();
101 eventQueue->queue.pushLimited(IncomingEvent(value));
102 size_t queueSizeAfter = eventQueue->queue.size();
103
104 if (queueSizeAfter > queueSizeBefore) {
105 UNIOT_LOG_TRACE("pushed event '%s' with value '%d', queue size: %d", eventID.c_str(), value, queueSizeAfter);
106 } else {
107 UNIOT_LOG_WARN("event queue for '%s' is full (limit: %d), oldest event was dropped", eventID.c_str(), EVENTS_LIMIT);
108 }
109 }
110
111 bool isEventAvailable(const String &eventID) {
112 if (!mIncomingEvents.exist(eventID)) {
113 return false;
114 }
115
116 auto eventQueue = mIncomingEvents.get(eventID);
117 eventQueue->isUsedInScript = true;
118 eventQueue->lastAccessed = millis();
119
120 return eventQueue->queue.size() > 0;
121 }
122
123 IncomingEvent popEvent(const String &eventID) {
124 const IncomingEvent emptyEvent(0, -1);
125
126 if (!mIncomingEvents.exist(eventID)) {
127 UNIOT_LOG_WARN("attempted to pop non-existent event '%s'", eventID.c_str());
128 return emptyEvent;
129 }
130
131 auto eventQueue = mIncomingEvents.get(eventID);
132 eventQueue->isUsedInScript = true;
133 eventQueue->lastAccessed = millis();
134
135 if (eventQueue->queue.size() == 0) {
136 UNIOT_LOG_WARN("attempted to pop from empty event queue '%s'", eventID.c_str());
137 return emptyEvent;
138 }
139
140 return eventQueue->queue.popLimited(emptyEvent);
141 }
142
144 unsigned long currentTime = millis();
145 ClearQueue<String> keysToRemove;
146 size_t totalEvents = 0;
147 size_t unusedEvents = 0;
148 size_t expiredEvents = 0;
149 size_t totalQueuedItems = 0;
150
151 mIncomingEvents.forEach([&](const Pair<String, SharedPointer<EventQueue>> &entry) {
152 totalEvents++;
153 auto eventQueue = entry.second;
154 totalQueuedItems += eventQueue->queue.size();
155 unsigned long timeSinceLastAccess = currentTime - eventQueue->lastAccessed;
156
157 if (!eventQueue->isUsedInScript) {
158 unusedEvents++;
159 if (timeSinceLastAccess > EVENT_TTL_MS) {
160 expiredEvents++;
161 keysToRemove.push(entry.first);
162 UNIOT_LOG_DEBUG("marking event '%s' for removal (unused, last accessed %lu ms ago)",
163 entry.first.c_str(), timeSinceLastAccess);
164 }
165 }
166 });
167
168 // Remove expired events
169 keysToRemove.forEach([this](const String &key) {
170 mIncomingEvents.remove(key);
171 });
172
173 if (expiredEvents > 0) {
174 UNIOT_LOG_INFO("cleaned up %d expired events (total: %d, unused: %d, queued items: %d)",
175 expiredEvents, totalEvents, unusedEvents, totalQueuedItems);
176 } else if (totalEvents > 0) {
177 UNIOT_LOG_TRACE("no events to cleanup (total: %d, unused: %d, queued items: %d)", totalEvents, unusedEvents, totalQueuedItems);
178 }
179 }
180
181 void clean() {
182 mIncomingEvents.clean();
183 UNIOT_LOG_DEBUG("cleared all incoming events");
184 }
185
186 private:
187 struct EventQueue {
189 unsigned long lastAccessed;
190 bool isUsedInScript;
191
192 EventQueue() : lastAccessed(millis()), isUsedInScript(false) {
193 queue.limit(EVENTS_LIMIT);
194 }
195 };
196
197 Map<String, SharedPointer<EventQueue>> mIncomingEvents;
198 static constexpr size_t EVENTS_LIMIT = 2;
199 static constexpr unsigned long EVENT_TTL_MS = 30000; // 30 seconds TTL
200};
201
219class unLisp : public CoreEventListener, public Singleton<unLisp> {
220 friend class Singleton<unLisp>;
221
222 public:
228 return mTaskLispEval;
229 }
230
232 return mTaskEventCleanup;
233 }
234
240 bool isCreated() {
241 return lisp_is_created();
242 }
243
250 return mTaskLispEval->isAttached();
251 }
252
257 size_t memoryUsed() {
258 return lisp_mem_used();
259 }
260
269 void runCode(const Bytes &data) {
270 if (!data.size())
271 return;
272
273 mLastCode = data;
274
275 mTaskLispEval->detach();
276 _destroyMachine();
277 _createMachine();
278
279 auto code = mLastCode.terminate().c_str();
280 UNIOT_LOG_DEBUG("eval: %s", code);
281
282 _refreshIncomingEvents();
283 lisp_eval(mLispRoot, mLispEnv, code);
284 if (!mTaskLispEval->isAttached()) {
285 _destroyMachine();
286 }
287 }
288
295 unLisp *pushPrimitive(Primitive *primitive) {
297 mUserPrimitives.push(MakePair(description.name, primitive));
298 UNIOT_LOG_TRACE("primitive added: %s", description.name.c_str());
299 UNIOT_LOG_TRACE("args count: %d", description.argsCount);
300 UNIOT_LOG_TRACE("return type: %d", description.returnType);
301 for (auto i = 0; i < description.argsCount; i++) {
302 UNIOT_LOG_TRACE("arg %d: %d", i, description.argsTypes[i]);
303 }
304 return this;
305 }
306
316 mUserPrimitives.forEach([&](Pair<const String &, Primitive *> holder) {
317 auto description = PrimitiveExpeditor::extractDescription(holder.second);
318 obj.putArray(holder.first.c_str())
319 .append(description.returnType)
320 .appendArray()
321 .append(description.argsCount, reinterpret_cast<const uint8_t *>(description.argsTypes));
322 });
323 }
324
330 UNIOT_LOG_WARN_IF(!mLastCode.size(), "there is no last saved code");
331 return mLastCode;
332 }
333
338 mLastCode.clean();
339 }
340
350 virtual void onEventReceived(unsigned int topic, int msg) override {
354 _pushIncomingEvent(data);
355 });
356 return;
357 }
358 return;
359 }
360 }
361
362 private:
369 unLisp() {
371
372 auto fnPrintOut = [](const char *msg, int size) {
373 if (size > 0) {
374 auto &instance = unLisp::getInstance();
375 instance.sendDataToChannel(events::lisp::Channel::OUT_LISP, Bytes((uint8_t *)msg, size).terminate());
377 }
378 yield();
379 };
380
381 auto fnPrintLog = [](const char *msg, int size) {
382 if (size > 0) {
383 auto &instance = unLisp::getInstance();
384 instance.sendDataToChannel(events::lisp::Channel::OUT_LISP_LOG, Bytes((uint8_t *)msg, size).terminate());
386 }
387 yield();
388 };
389
390 auto fnPrintErr = [](const char *msg, int size) {
391 auto &instance = unLisp::getInstance();
392 instance.sendDataToChannel(events::lisp::Channel::OUT_LISP_ERR, Bytes((uint8_t *)msg, size).terminate());
394
395 instance.mTaskLispEval->detach();
396 instance._destroyMachine();
397 };
398
399 lisp_set_cycle_yield(yield);
400 lisp_set_printers(fnPrintOut, fnPrintLog, fnPrintErr);
401
402 _constructLispEnv();
403
404 mTaskLispEval = TaskScheduler::make([this](SchedulerTask &self, short t) {
405 auto root = mLispRoot;
406 auto env = mLispEnv;
407
408 DEFINE2(t_obj, t_pass);
409 *t_pass = get_variable(root, env, "#t_pass");
410 (*t_pass)->cdr->value = t;
411 *t_obj = get_variable(root, env, "#t_obj")->cdr;
412 safe_eval(root, env, t_obj);
413
414 if (!t) {
415 _destroyMachine();
416 }
417
418 // UNIOT_LOG_DEBUG("lisp machine running, mem used: %d", lisp_mem_used());
419 });
420
421 mTaskEventCleanup = TaskScheduler::make([this](SchedulerTask &self, short t) {
422 mEventManager.cleanupUnusedEvents();
423 yield();
424 });
425 }
426
432 void _constructLispEnv() {
433 mLispEnvConstructor[0] = NULL;
434 mLispEnvConstructor[1] = NULL;
435 mLispEnvConstructor[2] = ROOT_END;
436 mLispRoot = mLispEnvConstructor;
437 mLispEnv = (Obj **)(mLispEnvConstructor + 1);
438 }
439
446 void _createMachine() {
447 lisp_create(UNIOT_LISP_HEAP);
448
449 *mLispEnv = make_env(mLispRoot, &Nil, &Nil);
450 define_constants(mLispRoot, mLispEnv);
451 define_primitives(mLispRoot, mLispEnv);
452
453 // Register special variables and built-in primitives
454 add_constant(mLispRoot, mLispEnv, "#t_obj", &Nil);
455 add_constant_int(mLispRoot, mLispEnv, "#t_pass", 0);
456 add_primitive(mLispRoot, mLispEnv, "task", mPrimitiveTask);
457 add_primitive(mLispRoot, mLispEnv, "is_event", mPrimitiveIsEventAvailable);
458 add_primitive(mLispRoot, mLispEnv, "pop_event", mPrimitivePopEvent);
459 add_primitive(mLispRoot, mLispEnv, "push_event", mPrimitivePushEvent);
460
461 // Register all user-defined primitives
462 mUserPrimitives.forEach([this](Pair<const String &, Primitive *> holder) {
463 add_primitive(mLispRoot, mLispEnv, holder.first.c_str(), holder.second);
464 });
465
466 UNIOT_LOG_DEBUG("lisp machine created, mem used: %d", lisp_mem_used());
467 }
468
472 void _destroyMachine() {
473 lisp_destroy();
474 }
475
479 void _refreshIncomingEvents() {
480 mEventManager.clean();
482 }
483
489 void _pushIncomingEvent(const Bytes &eventData) {
490 // mEventManager.cleanupUnusedEvents();
491 mEventManager.pushEvent(eventData);
492 }
493
501 bool _isIncomingEventAvailable(const String &eventID) {
502 return mEventManager.isEventAvailable(eventID);
503 }
504
512 IncomingEventManager::IncomingEvent _popIncomingEvent(const String &eventID) {
513 return mEventManager.popEvent(eventID);
514 }
515
524 bool _pushOutgoingEvent(String eventID, int value) {
525 CBORObject event;
526 event.put("eventID", eventID.c_str());
527 event.put("value", value);
528
529 auto sent = this->sendDataToChannel(events::lisp::Channel::OUT_EVENT, event.build());
531 return sent;
532 }
533
545 inline Object _primTask(Root root, VarObject env, VarObject list) {
547 .init(root, env, list);
548 expeditor.assertDescribedArgs();
549
550 auto times = expeditor.getArgInt(0);
551 auto ms = expeditor.getArgInt(1);
552 auto obj = expeditor.getArg(2);
553
554 DEFINE1(t_obj);
555 *t_obj = get_variable(root, env, "#t_obj");
556 (*t_obj)->cdr = obj;
557
558 mTaskLispEval->attach(ms, times);
559
560 return expeditor.makeBool(true);
561 }
562
573 inline Object _primIsEventAvailable(Root root, VarObject env, VarObject list) {
574 auto expeditor = PrimitiveExpeditor::describe("is_event", Lisp::Bool, 1, Lisp::Symbol)
575 .init(root, env, list);
576 expeditor.assertDescribedArgs();
577
578 auto eventId = expeditor.getArgSymbol(0);
579 auto available = _isIncomingEventAvailable(eventId);
580
581 return expeditor.makeBool(available);
582 }
583
596 inline Object _primPopEvent(Root root, VarObject env, VarObject list) {
597 auto expeditor = PrimitiveExpeditor::describe("pop_event", Lisp::Bool, 1, Lisp::Symbol)
598 .init(root, env, list);
599 expeditor.assertDescribedArgs();
600
601 auto eventId = expeditor.getArgSymbol(0);
602 auto event = _popIncomingEvent(eventId);
603
604 UNIOT_LOG_WARN_IF(event.errorCode, "error popping event '%s': %d", event.errorCode);
605
606 return expeditor.makeInt(event.value);
607 }
608
620 inline Object _primPushEvent(Root root, VarObject env, VarObject list) {
621 auto expeditor = PrimitiveExpeditor::describe("push_event", Lisp::Bool, 2, Lisp::Symbol, Lisp::BoolInt)
622 .init(root, env, list);
623 expeditor.assertDescribedArgs();
624
625 auto eventId = expeditor.getArgSymbol(0);
626 auto value = expeditor.getArgInt(1);
627
628 auto sent = _pushOutgoingEvent(eventId, value);
629
630 return expeditor.makeBool(sent);
631 }
632
633 // Private member variables
634 Bytes mLastCode;
635 TaskScheduler::TaskPtr mTaskLispEval;
636 TaskScheduler::TaskPtr mTaskEventCleanup;
637 ClearQueue<Pair<String, Primitive *>> mUserPrimitives;
638
642 const Primitive *mPrimitiveTask = [](Root root, VarObject env, VarObject list) { return getInstance()._primTask(root, env, list); };
643 const Primitive *mPrimitiveIsEventAvailable = [](Root root, VarObject env, VarObject list) { return getInstance()._primIsEventAvailable(root, env, list); };
644 const Primitive *mPrimitivePopEvent = [](Root root, VarObject env, VarObject list) { return getInstance()._primPopEvent(root, env, list); };
645 const Primitive *mPrimitivePushEvent = [](Root root, VarObject env, VarObject list) { return getInstance()._primPushEvent(root, env, list); };
646
647 void *mLispEnvConstructor[3];
648 Root mLispRoot;
649 VarObject mLispEnv;
650 IncomingEventManager mEventManager;
651};
652
653} // namespace uniot
Lisp interpreter event definitions for the Uniot event system.
Definition Bytes.h:38
size_t size() const
Gets the size of the byte array.
Definition Bytes.h:303
Definition ClearQueue.h:38
void forEach(VoidCallback callback) const
Executes a callback function on each element in the queue.
Definition ClearQueue.h:325
void push(const T &value)
Adds an element to the end of the queue.
Definition ClearQueue.h:207
Definition LimitedQueue.h:36
size_t limit() const
Get the current size limit of the queue.
Definition LimitedQueue.h:51
Array & append(int value)
Append an integer to the array.
Definition CBORObject.h:593
Array appendArray()
Append a new array as an element.
Definition CBORObject.h:635
Definition CBORObject.h:40
Array putArray(int key)
Create or get an array at a specific integer key.
Definition CBORObject.h:121
String getString(int key) const
Get a string value at a specific integer key.
Definition CBORObject.h:419
String getValueAsString(int key) const
Get a value as a string at a specific integer key.
Definition CBORObject.h:440
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)
Definition unLisp.h:65
IncomingEvent popEvent(const String &eventID)
Definition unLisp.h:123
void clean()
Definition unLisp.h:181
void cleanupUnusedEvents()
Definition unLisp.h:143
bool isEventAvailable(const String &eventID)
Definition unLisp.h:111
void pushEvent(const Bytes &eventData)
Definition unLisp.h:75
@ BoolInt
Definition LispHelper.h:98
@ Symbol
Definition LispHelper.h:99
@ Cell
Definition LispHelper.h:100
@ Bool
Definition LispHelper.h:97
@ Int
Definition LispHelper.h:96
static PrimitiveDescription extractDescription(Primitive *primitive)
Extracts description metadata from a primitive function.
Definition PrimitiveExpeditor.h:116
static PrimitiveExpeditorInitializer describe(const String &name, Lisp::Type returnType, int argsCount,...)
Describes a primitive function by setting up its metadata.
Definition PrimitiveExpeditor.h:84
void assertDescribedArgs()
Asserts arguments against the primitive's description.
Definition PrimitiveExpeditor.h:297
Singleton(const Singleton &)=delete
static unLisp & getInstance()
Definition Singleton.h:73
Definition unLisp.h:219
TaskScheduler::TaskPtr getCleanupTask()
Definition unLisp.h:231
void runCode(const Bytes &data)
Run Lisp code in the interpreter.
Definition unLisp.h:269
virtual void onEventReceived(unsigned int topic, int msg) override
Event handler for received events.
Definition unLisp.h:350
void serializePrimitives(CBORObject &obj)
Serialize all registered primitive functions to CBOR format.
Definition unLisp.h:315
TaskScheduler::TaskPtr getTask()
Get the task associated with Lisp evaluation.
Definition unLisp.h:227
const Bytes & getLastCode()
Get the last executed Lisp code.
Definition unLisp.h:329
void cleanLastCode()
Clear the stored last executed code.
Definition unLisp.h:337
size_t memoryUsed()
Get the amount of memory used by the Lisp machine.
Definition unLisp.h:257
bool taskIsRunning()
Check if the Lisp evaluation task is currently running.
Definition unLisp.h:249
unLisp * pushPrimitive(Primitive *primitive)
Add a new primitive function to the Lisp environment.
Definition unLisp.h:295
bool isCreated()
Check if the Lisp machine has been created.
Definition unLisp.h:240
auto MakePair(Args &&...args) -> decltype(std::make_pair(std::forward< Args >(args)...))
Creates a pair instance, alias for std::make_pair.
Definition Common.h:184
std::shared_ptr< T > SharedPointer
Type alias for std::shared_ptr with cleaner syntax.
Definition Common.h:160
std::pair< T_First, T_Second > Pair
Type alias for std::pair with cleaner syntax.
Definition Common.h:169
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_TRACE(...)
Log an TRACE level message Used for general information about system operation. Only compiled if UNIO...
Definition Logger.h:314
#define UNIOT_LOG_WARN(...)
Log an WARN level message Used for warnings about potentially problematic situations....
Definition Logger.h:247
#define UNIOT_LOG_WARN_IF(log_cond, log...)
Conditionally log an WARN level message Used for warnings about potentially problematic situations....
Definition Logger.h:255
#define UNIOT_LOG_DEBUG(...)
Log an DEBUG level message Used for general information about system operation. Only compiled if UNIO...
Definition Logger.h:293
SharedPointer< SchedulerTask > TaskPtr
Shared pointer type for scheduler tasks.
Definition TaskScheduler.h:171
static TaskPtr make(SchedulerTask::SchedulerTaskCallback callback)
Static factory method to create a task with a callback.
Definition TaskScheduler.h:193
struct Obj * Object
A pointer to a Lisp object structure.
Definition LispHelper.h:61
void * Root
A generic pointer representing the root of a Lisp environment.
Definition LispHelper.h:76
struct Obj ** VarObject
A pointer to a pointer to a Lisp object structure.
Definition LispHelper.h:69
@ 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 descriptions and implementations of primitive functions for hardware interaction.
Definition LispPrimitives.cpp:21
Contains all classes and functions related to the Uniot Core.
int32_t value
Definition unLisp.h:68
IncomingEvent(int32_t v, int8_t err=0)
Definition unLisp.h:72
int8_t errorCode
Definition unLisp.h:69
#define UNIOT_LISP_HEAP
Definition unLisp.h:54