Skip to content

Commit 966b975

Browse files
committed
add header library for ESP32.
1 parent 219eab6 commit 966b975

File tree

2 files changed

+300
-7
lines changed

2 files changed

+300
-7
lines changed

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,25 @@ platformio.ini
1111

1212
```ini
1313
lib_deps =
14+
...
1415
https://github.com/binzume/esp32quickjs.git
1516
```
1617

1718
main.cpp
1819

1920
```c++
2021
#include <Arduino.h>
21-
#include "quickjs.h"
22+
#include "esp/QuickJS.h"
2223

23-
JSRuntime *rt;
24-
JSContext *ctx;
24+
static const char *jscode = R"CODE(
25+
console.log('Hello, JavaScript!');
26+
)CODE";
27+
28+
M5QuickJS qjs;
2529

2630
void setup() {
27-
rt = JS_NewRuntime();
28-
ctx = JS_NewContext(rt);
29-
// Do anything.
31+
Serial.begin(115200);
32+
qjs.begin();
33+
qjs.exec(jscode);
3034
}
31-
3235
```

esp32/QuickJS.h

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
#pragma once
2+
#include <Arduino.h>
3+
4+
#include <algorithm>
5+
#include <vector>
6+
7+
#include "../quickjs.h"
8+
9+
void qjs_dump_exception(JSContext *ctx, JSValue v) {
10+
if (!JS_IsUndefined(v)) {
11+
const char *str = JS_ToCString(ctx, v);
12+
if (str) {
13+
Serial.println(str);
14+
JS_FreeCString(ctx, str);
15+
} else {
16+
Serial.println("[Exception]");
17+
}
18+
}
19+
JSValue e = JS_GetException(ctx);
20+
const char *str = JS_ToCString(ctx, e);
21+
if (str) {
22+
Serial.println(str);
23+
JS_FreeCString(ctx, str);
24+
}
25+
if (JS_IsError(ctx, e)) {
26+
JSValue s = JS_GetPropertyStr(ctx, e, "stack");
27+
if (!JS_IsUndefined(s)) {
28+
const char *str = JS_ToCString(ctx, s);
29+
if (str) {
30+
Serial.println(str);
31+
JS_FreeCString(ctx, str);
32+
}
33+
}
34+
JS_FreeValue(ctx, s);
35+
}
36+
JS_FreeValue(ctx, e);
37+
}
38+
39+
class JSTimer {
40+
// 20 bytes / entry.
41+
struct TimerEntry {
42+
uint32_t id;
43+
int32_t timeout;
44+
int32_t interval;
45+
JSValue func;
46+
};
47+
std::vector<TimerEntry> timers;
48+
uint32_t id_counter = 0;
49+
50+
public:
51+
uint32_t RegisterTimer(JSValue f, int32_t time, int32_t interval = -1) {
52+
uint32_t id = ++id_counter;
53+
timers.push_back(TimerEntry{id, time, interval, f});
54+
return id;
55+
}
56+
void RemoveTimer(uint32_t id) {
57+
timers.erase(std::remove_if(timers.begin(), timers.end(),
58+
[id](TimerEntry &t) { return t.id == id; }),
59+
timers.end());
60+
}
61+
int32_t GetNextTimeout(int32_t now) {
62+
if (timers.empty()) {
63+
return -1;
64+
}
65+
std::sort(timers.begin(), timers.end(),
66+
[now](TimerEntry &a, TimerEntry &b) -> bool {
67+
return (a.timeout - now) >
68+
(b.timeout - now); // 2^32 wraparound
69+
});
70+
int next = timers.back().timeout - now;
71+
return max(next, 0);
72+
}
73+
bool ConsumeTimer(JSContext *ctx, int32_t now) {
74+
std::vector<TimerEntry> t;
75+
int32_t eps = 2;
76+
while (!timers.empty() && timers.back().timeout - now <= eps) {
77+
t.push_back(timers.back());
78+
timers.pop_back();
79+
}
80+
for (auto &ent : t) {
81+
JSValue r =
82+
JS_Call(ctx, ent.func, ent.func, 0, nullptr); // may update timers.
83+
if (JS_IsException(r)) {
84+
qjs_dump_exception(ctx, r);
85+
}
86+
JS_FreeValue(ctx, r);
87+
88+
if (ent.interval >= 0) {
89+
ent.timeout = now + ent.interval;
90+
timers.push_back(ent);
91+
} else {
92+
JS_FreeValue(ctx, ent.func);
93+
}
94+
}
95+
return !t.empty();
96+
}
97+
};
98+
99+
class ESP32QuickJS {
100+
public:
101+
JSRuntime *rt;
102+
JSContext *ctx;
103+
JSTimer timer;
104+
JSValue loop_func = JS_UNDEFINED;
105+
106+
void begin() {
107+
JSRuntime *rt = JS_NewRuntime();
108+
begin(rt, JS_NewContext(rt));
109+
}
110+
111+
void begin(JSRuntime *rt, JSContext *ctx, int memoryLimit = 0) {
112+
this->rt = rt;
113+
this->ctx = ctx;
114+
if (memoryLimit == 0) {
115+
memoryLimit = ESP.getFreeHeap() >> 1;
116+
}
117+
JS_SetMemoryLimit(rt, memoryLimit);
118+
JS_SetGCThreshold(rt, memoryLimit >> 3);
119+
JSValue global = JS_GetGlobalObject(ctx);
120+
setup(ctx, global);
121+
JS_FreeValue(ctx, global);
122+
}
123+
124+
void loop(bool callLoopFn = true) {
125+
// async
126+
JSContext *c;
127+
int ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &c);
128+
if (ret < 0) {
129+
qjs_dump_exception(ctx, JS_UNDEFINED);
130+
}
131+
132+
// timer
133+
uint32_t now = millis();
134+
if (timer.GetNextTimeout(now) >= 0) {
135+
timer.ConsumeTimer(ctx, now);
136+
}
137+
138+
// loop()
139+
if (callLoopFn && JS_IsFunction(ctx, loop_func)) {
140+
JSValue ret = JS_Call(ctx, loop_func, loop_func, 0, nullptr);
141+
if (JS_IsException(ret)) {
142+
qjs_dump_exception(ctx, ret);
143+
}
144+
JS_FreeValue(ctx, ret);
145+
}
146+
}
147+
148+
void runGC() { JS_RunGC(rt); }
149+
150+
bool exec(const char *code) {
151+
JSValue result = eval(code);
152+
bool ret = JS_IsException(result);
153+
JS_FreeValue(ctx, result);
154+
return ret;
155+
}
156+
157+
JSValue eval(const char *code) {
158+
JSValue ret =
159+
JS_Eval(ctx, code, strlen(code), "<eval>", JS_EVAL_FLAG_STRICT);
160+
if (JS_IsException(ret)) {
161+
qjs_dump_exception(ctx, ret);
162+
}
163+
return ret;
164+
}
165+
166+
void registerLoopFunc(const char *fname) {
167+
JSValue global = JS_GetGlobalObject(ctx);
168+
setLoopFunc(JS_GetPropertyStr(ctx, global, fname));
169+
JS_FreeValue(ctx, global);
170+
}
171+
172+
void dispose() {
173+
JS_FreeContext(ctx);
174+
JS_FreeRuntime(rt);
175+
}
176+
177+
protected:
178+
void setLoopFunc(JSValue f) {
179+
JS_FreeValue(ctx, loop_func);
180+
loop_func = f;
181+
}
182+
183+
virtual void setup(JSContext *ctx, JSValue global) {
184+
this->ctx = ctx;
185+
JS_SetContextOpaque(ctx, this);
186+
187+
// setup console.log()
188+
JSValue console = JS_NewObject(ctx);
189+
JS_SetPropertyStr(ctx, global, "console", console);
190+
JS_SetPropertyStr(ctx, console, "log",
191+
JS_NewCFunction(ctx, console_log, "log", 1));
192+
193+
// timer
194+
JS_SetPropertyStr(ctx, global, "setTimeout",
195+
JS_NewCFunction(ctx, set_timeout, "setTimeout", 2));
196+
JS_SetPropertyStr(ctx, global, "clearTimeout",
197+
JS_NewCFunction(ctx, clear_timeout, "clearTimeout", 1));
198+
JS_SetPropertyStr(ctx, global, "setInterval",
199+
JS_NewCFunction(ctx, set_interval, "setInterval", 2));
200+
JS_SetPropertyStr(ctx, global, "clearInterval",
201+
JS_NewCFunction(ctx, clear_timeout, "clearInterval", 1));
202+
203+
// gpio
204+
JSValue gpio = JS_NewObject(ctx);
205+
JS_SetPropertyStr(ctx, global, "gpio", gpio);
206+
JS_SetPropertyStr(ctx, gpio, "pinMode",
207+
JS_NewCFunction(ctx, gpio_mode, "pinMode", 2));
208+
JS_SetPropertyStr(
209+
ctx, gpio, "digitalRead",
210+
JS_NewCFunction(ctx, gpio_digital_read, "digitalRead", 1));
211+
JS_SetPropertyStr(
212+
ctx, gpio, "digitalWrite",
213+
JS_NewCFunction(ctx, gpio_digital_write, "digitalWrite", 2));
214+
215+
JS_SetPropertyStr(ctx, global, "registerLoop",
216+
JS_NewCFunction(ctx, register_loop, "registerLoop", 1));
217+
218+
}
219+
220+
static JSValue set_timeout(JSContext *ctx, JSValueConst jsThis, int argc,
221+
JSValueConst *argv) {
222+
ESP32QuickJS *qjs = (ESP32QuickJS *)JS_GetContextOpaque(ctx);
223+
uint32_t t;
224+
JS_ToUint32(ctx, &t, argv[1]);
225+
uint32_t id =
226+
qjs->timer.RegisterTimer(JS_DupValue(ctx, argv[0]), millis() + t);
227+
return JS_NewUint32(ctx, id);
228+
}
229+
230+
static JSValue clear_timeout(JSContext *ctx, JSValueConst jsThis, int argc,
231+
JSValueConst *argv) {
232+
ESP32QuickJS *qjs = (ESP32QuickJS *)JS_GetContextOpaque(ctx);
233+
uint32_t tid;
234+
JS_ToUint32(ctx, &tid, argv[0]);
235+
qjs->timer.RemoveTimer(tid);
236+
return JS_UNDEFINED;
237+
}
238+
239+
static JSValue set_interval(JSContext *ctx, JSValueConst jsThis, int argc,
240+
JSValueConst *argv) {
241+
ESP32QuickJS *qjs = (ESP32QuickJS *)JS_GetContextOpaque(ctx);
242+
uint32_t t;
243+
JS_ToUint32(ctx, &t, argv[1]);
244+
uint32_t id =
245+
qjs->timer.RegisterTimer(JS_DupValue(ctx, argv[0]), millis() + t, t);
246+
return JS_NewUint32(ctx, id);
247+
}
248+
249+
static JSValue gpio_mode(JSContext *ctx, JSValueConst jsThis, int argc,
250+
JSValueConst *argv) {
251+
uint32_t pin, mode;
252+
JS_ToUint32(ctx, &pin, argv[0]);
253+
JS_ToUint32(ctx, &mode, argv[1]);
254+
pinMode(pin, mode);
255+
return JS_UNDEFINED;
256+
}
257+
258+
static JSValue gpio_digital_read(JSContext *ctx, JSValueConst jsThis,
259+
int argc, JSValueConst *argv) {
260+
uint32_t pin;
261+
JS_ToUint32(ctx, &pin, argv[0]);
262+
return JS_NewUint32(ctx, digitalRead(pin));
263+
}
264+
265+
static JSValue gpio_digital_write(JSContext *ctx, JSValueConst jsThis,
266+
int argc, JSValueConst *argv) {
267+
uint32_t pin, value;
268+
JS_ToUint32(ctx, &pin, argv[0]);
269+
JS_ToUint32(ctx, &value, argv[1]);
270+
digitalWrite(pin, value);
271+
return JS_UNDEFINED;
272+
}
273+
274+
static JSValue console_log(JSContext *ctx, JSValueConst jsThis, int argc,
275+
JSValueConst *argv) {
276+
const char *str = JS_ToCString(ctx, argv[0]);
277+
if (str) {
278+
Serial.println(str);
279+
JS_FreeCString(ctx, str);
280+
}
281+
return JS_UNDEFINED;
282+
}
283+
284+
static JSValue register_loop(JSContext *ctx, JSValueConst jsThis, int argc,
285+
JSValueConst *argv) {
286+
ESP32QuickJS *qjs = (ESP32QuickJS *)JS_GetContextOpaque(ctx);
287+
qjs->setLoopFunc(JS_DupValue(ctx, argv[0]));
288+
return JS_UNDEFINED;
289+
}
290+
};

0 commit comments

Comments
 (0)