• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FarmBot / Farmbot-Web-App / 912f10c5-9ef3-466a-97da-3bc080087e89

29 Apr 2026 08:41PM UTC coverage: 99.403% (-0.01%) from 99.417%
912f10c5-9ef3-466a-97da-3bc080087e89

push

circleci

gabrielburnworth
clean up test url assignment

70436 of 70859 relevant lines covered (99.4%)

54.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.81
/frontend/demo/lua_runner/run.ts
1
import { lua, lauxlib, lualib, to_luastring } from "fengari-web";
65✔
2
import {
226✔
3
  getDeviceAccountSettings,
4
  selectAllCurves,
5
  selectAllGenericPointers,
6
  selectAllPlantPointers,
7
  selectAllPoints, selectAllTools, selectAllToolSlotPointers,
8
  selectAllWeedPointers,
9
} from "../../resources/selectors";
10
import {
31✔
11
  ParameterApplication, PercentageProgress, RpcRequest, TaggedPoint, uuid, Xyz,
12
} from "farmbot";
13
import { store } from "../../redux/store";
42✔
14
import { sortGroupBy } from "../../point_groups/point_group_sort";
66✔
15
import { LUA_HELPERS } from "./lua";
36✔
16
import {
107✔
17
  clean, createRecursiveNotImplemented, csToLua, filterPoint, jsToLua, luaToJs,
18
} from "./util";
19
import { Action, XyzNumber } from "./interfaces";
20
import {
21
  DeviceAccountSettings, Point, PointGroupSortType,
22
} from "farmbot/dist/resources/api_resources";
23
import {
133✔
24
  getFirmwareSettings, getGardenSize, getSafeZ, getSoilHeight,
25
  getGroupPoints, getJob,
26
  getDeviceStatus,
27
} from "./stubs";
28
import { error } from "../../toast/toast";
42✔
29
import { collectDemoSequenceActions } from "./index";
53✔
30
import { last } from "lodash";
30✔
31
import { XYZ } from "../../devices/constants";
46✔
32

33
export const runLua =
20✔
34
  (depth: number, luaCode: string, variables: ParameterApplication[]): Action[] => {
34✔
35
    const actions: Action[] = [];
21✔
36
    const L = lauxlib.luaL_newstate(); // stack: []
36✔
37

38
    lua.lua_newtable(L); // stack: [env]
22✔
39
    const envIndex = lua.lua_gettop(L);
37✔
40

41
    lauxlib.luaL_requiref(L, to_luastring("_G"), lualib.luaopen_base, 1);
71✔
42
    const gIndex = lua.lua_gettop(L);
35✔
43

44
    lua.lua_getfield(L, gIndex, to_luastring("type"));
52✔
45
    lua.lua_setfield(L, envIndex, to_luastring("type"));
54✔
46

47
    lua.lua_getfield(L, gIndex, to_luastring("tostring"));
56✔
48
    lua.lua_setfield(L, envIndex, to_luastring("tostring"));
58✔
49

50
    lua.lua_getfield(L, gIndex, to_luastring("tonumber"));
56✔
51
    lua.lua_setfield(L, envIndex, to_luastring("tonumber"));
58✔
52

53
    lua.lua_getfield(L, gIndex, to_luastring("pairs"));
53✔
54
    lua.lua_setfield(L, envIndex, to_luastring("pairs"));
55✔
55

56
    lua.lua_getfield(L, gIndex, to_luastring("ipairs"));
54✔
57
    lua.lua_setfield(L, envIndex, to_luastring("ipairs"));
56✔
58

59
    lua.lua_pop(L, 1); // stack: [env]
20✔
60

61
    lauxlib.luaL_requiref(L, to_luastring("math"), lualib.luaopen_math, 1);
73✔
62
    lua.lua_setfield(L, envIndex, to_luastring("math"));
54✔
63

64
    lauxlib.luaL_requiref(L, to_luastring("table"), lualib.luaopen_table, 1);
75✔
65
    lua.lua_setfield(L, envIndex, to_luastring("table"));
55✔
66

67
    lauxlib.luaL_requiref(L, to_luastring("string"), lualib.luaopen_string, 1);
77✔
68
    lua.lua_setfield(L, envIndex, to_luastring("string"));
56✔
69

70
    lua.lua_pushjsfunction(L, () => {
36✔
71
      let output = "";
20✔
72
      const n = lua.lua_gettop(L);
32✔
73
      for (let i = 1; i <= n; i++) {
34✔
74
        if (i > 1) { output += "\t"; }
46✔
75
        if (lua.lua_isstring(L, i)) {
36✔
76
          output += luaToJs(L, i);
30✔
77
        } else {
15✔
78
          output += JSON.stringify(luaToJs(L, i));
50✔
79
        }
80
      }
4✔
81
      actions.push({ type: "print", args: [output] });
52✔
82
      return 0;
8✔
83
    });
4✔
84
    lua.lua_setfield(L, envIndex, to_luastring("print"));
55✔
85

86
    lua.lua_pushjsfunction(L, () => {
36✔
87
      const input = luaToJs(L, 1);
32✔
88
      const output = JSON.stringify(input);
41✔
89
      jsToLua(L, output);
23✔
90
      return 1;
8✔
91
    });
4✔
92
    lua.lua_pushjsfunction(L, () => {
36✔
93
      const input = luaToJs(L, 1) as string;
32✔
94
      try {
11✔
95
        const output = JSON.parse(input);
39✔
96
        jsToLua(L, output);
24✔
97
      } catch {
13✔
98
        jsToLua(L, undefined);
30✔
99
      }
100
      return 1;
8✔
101
    });
4✔
102
    lua.lua_newtable(L);
22✔
103
    lua.lua_pushvalue(L, -3);
27✔
104
    lua.lua_setfield(L, -2, to_luastring("encode"));
50✔
105
    lua.lua_pushvalue(L, -2);
27✔
106
    lua.lua_setfield(L, -2, to_luastring("decode"));
50✔
107
    lua.lua_setfield(L, envIndex, to_luastring("json"));
54✔
108
    lua.lua_pop(L, 2);
20✔
109

110
    lua.lua_pushjsfunction(L, () => {
36✔
111
      const variableName = luaToJs(L, 1) as string;
39✔
112
      const n = variables
20✔
113
        .filter(variable => variable.args.label === variableName)
57✔
114
        .map(variable => variable.args.data_value)[0];
49✔
115
      switch (n?.kind) {
28✔
116
        case "numeric":
18✔
117
          jsToLua(L, n.args.number);
34✔
118
          break;
10✔
119
        case "text":
15✔
120
          jsToLua(L, n.args.string);
34✔
121
          break;
10✔
122
        case "coordinate":
21✔
123
          jsToLua(L, n.args);
27✔
124
          break;
10✔
125
        case "point":
16✔
126
          const point = selectAllPoints(store.getState().resources.index)
64✔
127
            .find(p => p.body.id === n.args.pointer_id)?.body;
57✔
128
          jsToLua(L, clean(point));
33✔
129
          break;
10✔
130
        case "tool":
15✔
131
          const slot = selectAllToolSlotPointers(store.getState().resources.index)
73✔
132
            .find(ts => ts.body.tool_id === n.args.tool_id)?.body;
61✔
133
          jsToLua(L, clean(slot));
32✔
134
          break;
21✔
135
        default:
136
          actions.push({
24✔
137
            type: "send_message",
31✔
138
            args: [
19✔
139
              "error",
20✔
140
              `Variable "${variableName}" of type ${n?.kind} not implemented.`,
74✔
141
            ],
9✔
142
          });
11✔
143
          lua.lua_pushnil(L);
27✔
144
          break;
9✔
145
      }
146
      return 1;
8✔
147
    });
4✔
148
    lua.lua_setfield(L, envIndex, to_luastring("variable"));
58✔
149

150
    // stack: [env]
151
    lauxlib.luaL_requiref(L, to_luastring("os"), lualib.luaopen_os, 1);
69✔
152
    // stack: [env, os]
153
    const osIndex = lua.lua_gettop(L);
36✔
154
    lua.lua_newtable(L);
22✔
155
    const envOsIndex = lua.lua_gettop(L);
39✔
156
    lua.lua_getfield(L, osIndex, to_luastring("time"));
53✔
157
    const rawTime = lua.lua_toproxy(L, -1);
41✔
158
    lua.lua_pop(L, 1);
20✔
159
    lua.lua_pushjsfunction(L, () => {
36✔
160
      rawTime(L);
15✔
161
      lua.lua_call(L, 0, 1);
26✔
162
      const intTime = luaToJs(L, -1) as number;
35✔
163
      lua.lua_pop(L, 1);
22✔
164
      jsToLua(L, intTime + 0.0);
28✔
165
      return 1;
8✔
166
    });
4✔
167
    lua.lua_setfield(L, envOsIndex, to_luastring("time"));
56✔
168
    lua.lua_getfield(L, osIndex, to_luastring("date"));
53✔
169
    lua.lua_setfield(L, envOsIndex, to_luastring("date"));
56✔
170
    lua.lua_setfield(L, envIndex, to_luastring("os"));
52✔
171
    lua.lua_pop(L, 1); // stack: [env]
20✔
172

173
    lua.lua_pushjsfunction(L, () => {
36✔
174
      lua.lua_getfield(L, 1, to_luastring("method"));
51✔
175
      const rawMethod = lua.lua_isnil(L, -1)
40✔
176
        ? "GET"
7✔
177
        : luaToJs(L, -1) as string;
18✔
178
      const method = rawMethod.toUpperCase();
43✔
179
      lua.lua_pop(L, 1);
22✔
180

181
      lua.lua_getfield(L, 1, to_luastring("url"));
48✔
182
      const rawUrl = luaToJs(L, -1) as string;
34✔
183
      const url = rawUrl.replace(/\/$/, "");
42✔
184
      lua.lua_pop(L, 1);
22✔
185

186
      if (url == "/api/points") {
32✔
187
        const points = selectAllPoints(store.getState().resources.index);
71✔
188
        if (method == "GET") {
29✔
189
          const results = sortGroupBy("yx_alternating", points)
54✔
190
            .map(p => p.body).map(clean);
36✔
191
          jsToLua(L, results);
28✔
192
          return 1;
8✔
193
        }
6✔
194
        if (method == "POST") {
30✔
195
          lua.lua_getfield(L, 1, to_luastring("body"));
53✔
196
          const body = luaToJs(L, -1) as object;
36✔
197
          lua.lua_pop(L, 1);
26✔
198
          const point = JSON.stringify(body);
43✔
199
          actions.push({ type: "create_point", args: [point] });
62✔
200
          jsToLua(L, true);
25✔
201
          return 1;
8✔
202
        }
×
203
      } else if (method == "GET" && url == "/api/tools") {
56✔
204
        const results = selectAllTools(store.getState().resources.index)
65✔
205
          .map(p => p.body).map(clean);
34✔
206
        jsToLua(L, results);
26✔
207
        return 1;
8✔
208
      } else if (method == "GET" && url.startsWith("/api/curves")) {
66✔
209
        const curveId = parseInt("" + last(url.split("/")));
58✔
210
        const curve = selectAllCurves(store.getState().resources.index)
64✔
211
          .map(curve => curve.body)
25✔
212
          .filter(curve => curve.id == curveId)[0];
46✔
213
        jsToLua(L, clean(curve));
31✔
214
        return 1;
8✔
215
      } else {
13✔
216
        actions.push({
22✔
217
          type: "send_message",
29✔
218
          args: [
17✔
219
            "error",
18✔
220
            `API call ${method} ${url} not implemented.`,
52✔
221
          ],
7✔
222
        });
9✔
223
        jsToLua(L, false);
24✔
224
        return 1;
8✔
225
      }
226
    });
4✔
227
    lua.lua_setfield(L, envIndex, to_luastring("api"));
53✔
228

229
    lua.lua_pushjsfunction(L, () => {
36✔
230
      const params = luaToJs(L, 1) as Partial<Record<string, string | number>>;
33✔
231
      const plants = selectAllPlantPointers(store.getState().resources.index)
72✔
232
        .map(plant => plant.body)
25✔
233
        .filter(filterPoint(params, "planted"))
39✔
234
        .map(clean);
15✔
235
      jsToLua(L, plants);
23✔
236
      return 1;
8✔
237
    });
4✔
238
    lua.lua_setfield(L, envIndex, to_luastring("get_plants"));
60✔
239

240
    lua.lua_pushjsfunction(L, () => {
36✔
241
      const params = luaToJs(L, 1) as Partial<Record<string, string | number>>;
33✔
242
      const weeds = selectAllWeedPointers(store.getState().resources.index)
70✔
243
        .map(weed => weed.body)
23✔
244
        .filter(filterPoint(params, "active"))
38✔
245
        .map(clean);
15✔
246
      jsToLua(L, weeds);
22✔
247
      return 1;
8✔
248
    });
4✔
249
    lua.lua_setfield(L, envIndex, to_luastring("get_weeds"));
59✔
250

251
    lua.lua_pushjsfunction(L, () => {
36✔
252
      const params = luaToJs(L, 1) as Partial<Record<string, string | number>>;
33✔
253
      const points = selectAllGenericPointers(store.getState().resources.index)
74✔
254
        .map(point => point.body)
25✔
255
        .filter(filterPoint(params, undefined))
39✔
256
        .map(clean);
15✔
257
      jsToLua(L, points);
23✔
258
      return 1;
8✔
259
    });
4✔
260
    lua.lua_setfield(L, envIndex, to_luastring("get_generic_points"));
68✔
261

262
    lua.lua_pushjsfunction(L, () => {
36✔
263
      const groupId = luaToJs(L, 1) as number;
34✔
264
      const points = getGroupPoints(store.getState().resources.index, groupId)
73✔
265
        .map(point => point.body).map(clean);
40✔
266
      jsToLua(L, points);
23✔
267
      return 1;
8✔
268
    });
4✔
269
    lua.lua_setfield(L, envIndex, to_luastring("get_group"));
59✔
270

271
    lua.lua_pushjsfunction(L, () => {
36✔
272
      const groupId = luaToJs(L, 1) as number;
34✔
273
      const points = getGroupPoints(store.getState().resources.index, groupId)
73✔
274
        .map(point => point.body.id).map(clean);
43✔
275
      jsToLua(L, points);
23✔
276
      return 1;
8✔
277
    });
4✔
278
    lua.lua_setfield(L, envIndex, to_luastring("group"));
55✔
279

280
    lua.lua_pushjsfunction(L, () => {
36✔
281
      const points = luaToJs(L, 1) as Point[];
33✔
282
      const sortMethod = luaToJs(L, 2) as PointGroupSortType;
37✔
283
      const taggedPoints = points.map(point => ({
50✔
284
        body: point,
18✔
285
        uuid: uuid(),
16✔
286
      })) as TaggedPoint[];
7✔
287
      const results = sortGroupBy(sortMethod, taggedPoints)
54✔
288
        .map(p => p.body).map(clean);
32✔
289
      jsToLua(L, results);
24✔
290
      return 1;
8✔
291
    });
4✔
292
    lua.lua_setfield(L, envIndex, to_luastring("sort"));
54✔
293

294
    lua.lua_pushjsfunction(L, () => {
36✔
295
      const datetimeString = luaToJs(L, 1) as string;
41✔
296
      const unix = new Date(datetimeString).getTime() / 1000;
59✔
297
      jsToLua(L, unix);
21✔
298
      return 1;
8✔
299
    });
4✔
300
    lua.lua_setfield(L, envIndex, to_luastring("to_unix"));
57✔
301

302
    lua.lua_pushjsfunction(L, () => {
36✔
303
      const cmd = (luaToJs(L, 1) as RpcRequest).body?.[0];
40✔
304
      if (!cmd) { return 0; }
28✔
305
      if (cmd.kind == "execute") {
33✔
306
        const ri = store.getState().resources.index;
50✔
307
        const sequenceId = cmd.args.sequence_id;
46✔
308
        const seqVariables = cmd.body;
36✔
309
        const seqActions = collectDemoSequenceActions(
46✔
310
          depth + 1, ri, sequenceId, seqVariables);
47✔
311
        actions.push(...seqActions);
32✔
312
      } else {
13✔
313
        const luaActions = runLua(depth, csToLua(cmd), variables);
64✔
314
        actions.push(...luaActions);
36✔
315
      }
316
      return 0;
8✔
317
    });
4✔
318
    lua.lua_setfield(L, envIndex, to_luastring("cs_eval"));
57✔
319

320
    lua.lua_pushjsfunction(L, () => {
36✔
321
      const n = lua.lua_gettop(L);
32✔
322
      const args: string[] = [];
20✔
323
      for (let i = 1; i <= n; i++) {
34✔
324
        args.push(luaToJs(L, i) as string);
29✔
325
      }
4✔
326
      if (Array.isArray(args[2])) {
34✔
327
        args[2] = args[2].join(",");
32✔
328
      }
4✔
329
      actions.push({ type: "send_message", args: args });
49✔
330
      return 0;
8✔
331
    });
4✔
332
    lua.lua_setfield(L, envIndex, to_luastring("send_message"));
62✔
333

334
    lua.lua_pushjsfunction(L, () => {
36✔
335
      const jobName = luaToJs(L, 1) as string;
34✔
336

337
      lua.lua_getfield(L, 2, to_luastring("percent"));
52✔
338
      const percent = luaToJs(L, -1) as number;
35✔
339
      lua.lua_pop(L, 1);
22✔
340

341
      lua.lua_getfield(L, 2, to_luastring("status"));
51✔
342
      const status = luaToJs(L, -1) as string;
34✔
343
      lua.lua_pop(L, 1);
22✔
344

345
      lua.lua_getfield(L, 2, to_luastring("time"));
49✔
346
      const time = luaToJs(L, -1) as number;
32✔
347
      lua.lua_pop(L, 1);
22✔
348

349
      actions.push({
20✔
350
        type: "set_job_progress",
31✔
351
        args: [jobName, percent, status, time],
42✔
352
      });
7✔
353
      return 0;
8✔
354
    });
4✔
355
    lua.lua_setfield(L, envIndex, to_luastring("set_job_progress"));
66✔
356

357
    lua.lua_pushjsfunction(L, () => {
36✔
358
      const jobName = luaToJs(L, 1) as string;
34✔
359
      const job = getJob(jobName);
32✔
360
      jsToLua(L, job);
20✔
361
      return 1;
8✔
362
    });
4✔
363
    lua.lua_setfield(L, envIndex, to_luastring("get_job"));
57✔
364

365
    lua.lua_pushjsfunction(L, () => {
36✔
366
      const jobName = luaToJs(L, 1) as string;
34✔
367
      const params = luaToJs(L, 2) as Partial<PercentageProgress>;
33✔
368
      const time = Date.now();
28✔
369
      const prev = getJob(jobName);
33✔
370
      const existing = prev?.status.toLowerCase() != "complete" ? prev : {};
71✔
371
      const job = { time, ...existing, ...params };
49✔
372
      actions.push({
20✔
373
        type: "set_job_progress",
31✔
374
        args: [jobName, job.percent, job.status, job.time],
54✔
375
      });
7✔
376
      return 0;
8✔
377
    });
4✔
378
    lua.lua_setfield(L, envIndex, to_luastring("set_job"));
57✔
379

380
    lua.lua_pushjsfunction(L, () => {
36✔
381
      const args: number[] = [];
20✔
382
      const n = lua.lua_gettop(L);
32✔
383
      if (n == 1) {
18✔
384
        const params = luaToJs(L, 1) as XyzNumber;
35✔
385
        XYZ.map((axis: Xyz) => args.push(params[axis]));
45✔
386
      } else {
13✔
387
        for (let i = 1; i <= n; i++) {
36✔
388
          args.push(luaToJs(L, i) as number);
31✔
389
        }
8✔
390
      }
391
      actions.push({ type: "move_absolute", args: args });
50✔
392
      return 0;
8✔
393
    });
4✔
394
    lua.lua_setfield(L, envIndex, to_luastring("move_absolute"));
63✔
395

396
    lua.lua_pushjsfunction(L, () => {
36✔
397
      const n = lua.lua_gettop(L);
32✔
398
      const args: number[] = [];
20✔
399
      for (let i = 1; i <= n; i++) {
34✔
400
        args.push(luaToJs(L, i) as number);
29✔
401
      }
4✔
402
      actions.push({ type: "move_relative", args: args });
50✔
403
      return 0;
8✔
404
    });
4✔
405
    lua.lua_setfield(L, envIndex, to_luastring("move_relative"));
63✔
406

407
    lua.lua_pushjsfunction(L, () => {
36✔
408
      const axis = luaToJs(L, -1) as string;
32✔
409
      actions.push({ type: "find_home", args: [axis] });
54✔
410
      return 0;
8✔
411
    });
4✔
412
    lua.lua_setfield(L, envIndex, to_luastring("find_home"));
59✔
413

414
    lua.lua_pushjsfunction(L, () => {
36✔
415
      const axis = luaToJs(L, -1) as string;
32✔
416
      actions.push({ type: "go_to_home", args: [axis] });
55✔
417
      return 0;
8✔
418
    });
4✔
419
    lua.lua_setfield(L, envIndex, to_luastring("go_to_home"));
60✔
420

421
    lua.lua_pushjsfunction(L, () => {
36✔
422
      const axis = luaToJs(L, -1) as string;
32✔
423
      const firmwareSettings = getFirmwareSettings();
51✔
424
      const sign = {
20✔
425
        x: 1,
11✔
426
        y: 1,
11✔
427
        z: firmwareSettings.movement_home_up_z ? -1 : 1,
48✔
428
      };
6✔
429
      actions.push({
20✔
430
        type: "move_relative",
28✔
431
        args: [
15✔
432
          axis == "x" ? sign.x * -9999 : 0,
38✔
433
          axis == "y" ? sign.y * -9999 : 0,
38✔
434
          axis == "z" ? sign.z * -9999 : 0,
35✔
435
        ],
5✔
436
      });
7✔
437
      actions.push({
20✔
438
        type: "move_relative",
28✔
439
        args: [
15✔
440
          axis == "x" ? sign.x * 9999 : 0,
37✔
441
          axis == "y" ? sign.y * 9999 : 0,
37✔
442
          axis == "z" ? sign.z * 9999 : 0,
34✔
443
        ],
5✔
444
      });
7✔
445
      actions.push({
20✔
446
        type: "move_relative",
28✔
447
        args: [
15✔
448
          axis == "x" ? sign.x * -9999 : 0,
38✔
449
          axis == "y" ? sign.y * -9999 : 0,
38✔
450
          axis == "z" ? sign.z * -9999 : 0,
35✔
451
        ],
5✔
452
      });
7✔
453
      return 0;
8✔
454
    });
4✔
455
    lua.lua_setfield(L, envIndex, to_luastring("find_axis_length"));
66✔
456

457
    lua.lua_pushjsfunction(L, () => {
36✔
458
      const ms = luaToJs(L, 1) as number;
29✔
459
      actions.push({ type: "wait_ms", args: [ms] });
50✔
460
      return 0;
8✔
461
    });
4✔
462
    lua.lua_setfield(L, envIndex, to_luastring("wait_ms"));
57✔
463

464
    lua.lua_pushjsfunction(L, () => {
36✔
465
      const key = luaToJs(L, 1) as keyof DeviceAccountSettings;
30✔
466
      const device = getDeviceAccountSettings(store.getState().resources.index);
78✔
467
      const value = device.body[key];
35✔
468
      jsToLua(L, value || false);
31✔
469
      return 1;
8✔
470
    });
4✔
471
    lua.lua_setfield(L, envIndex, to_luastring("get_device"));
60✔
472

473
    lua.lua_pushjsfunction(L, () => {
36✔
474
      const params = luaToJs(L, 1) as object;
33✔
475
      const [key, value] = Object.entries(params)[0];
51✔
476
      actions.push({ type: "update_device", args: [key, value] });
64✔
477
      return 0;
8✔
478
    });
4✔
479
    lua.lua_setfield(L, envIndex, to_luastring("update_device"));
63✔
480

481
    lua.lua_pushjsfunction(L, () => {
36✔
482
      jsToLua(L, getSafeZ());
27✔
483
      return 1;
8✔
484
    });
4✔
485
    lua.lua_setfield(L, envIndex, to_luastring("safe_z"));
56✔
486

487
    lua.lua_pushjsfunction(L, () => {
36✔
488
      jsToLua(L, "");
19✔
489
      return 1;
8✔
490
    });
4✔
491
    lua.lua_setfield(L, envIndex, to_luastring("env"));
53✔
492

493
    lua.lua_pushjsfunction(L, () => {
36✔
494
      const x = luaToJs(L, 1) as number;
28✔
495
      const y = luaToJs(L, 2) as number;
28✔
496
      jsToLua(L, getSoilHeight(x, y));
36✔
497
      return 1;
8✔
498
    });
4✔
499
    lua.lua_setfield(L, envIndex, to_luastring("soil_height"));
61✔
500

501
    lua.lua_pushjsfunction(L, () => {
36✔
502
      const arg = luaToJs(L, 1) as string;
30✔
503
      actions.push({ type: "_move", args: [arg] });
49✔
504
      return 0;
8✔
505
    });
4✔
506
    lua.lua_setfield(L, envIndex, to_luastring("_move"));
55✔
507

508
    lua.lua_pushjsfunction(L, () => {
36✔
509
      actions.push({ type: "take_photo", args: [] });
51✔
510
      return 0;
8✔
511
    });
4✔
512
    lua.lua_setfield(L, envIndex, to_luastring("take_photo"));
60✔
513

514
    lua.lua_pushjsfunction(L, () => {
36✔
515
      actions.push({ type: "calibrate_camera", args: [] });
57✔
516
      return 0;
8✔
517
    });
4✔
518
    lua.lua_setfield(L, envIndex, to_luastring("calibrate_camera"));
66✔
519

520
    lua.lua_pushjsfunction(L, () => {
36✔
521
      actions.push({ type: "detect_weeds", args: [] });
53✔
522
      return 0;
8✔
523
    });
4✔
524
    lua.lua_setfield(L, envIndex, to_luastring("detect_weeds"));
62✔
525

526
    lua.lua_pushjsfunction(L, () => {
36✔
527
      actions.push({ type: "measure_soil_height", args: [] });
60✔
528
      return 0;
8✔
529
    });
4✔
530
    lua.lua_setfield(L, envIndex, to_luastring("measure_soil_height"));
69✔
531

532
    lua.lua_pushjsfunction(L, () => {
36✔
533
      const pin = luaToJs(L, 1) as number;
30✔
534
      if (pin == 63) {
21✔
535
        const toolMounted =
20✔
536
          !!getDeviceAccountSettings(store.getState().resources.index)
61✔
537
            .body.mounted_tool_id;
27✔
538
        jsToLua(L, toolMounted ? 0 : 1);
35✔
539
        return 1;
8✔
540
      }
4✔
541
      actions.push({ type: "read_pin", args: [pin] });
52✔
542
      jsToLua(L, 0);
18✔
543
      return 1;
8✔
544
    });
4✔
545
    lua.lua_setfield(L, envIndex, to_luastring("read_pin"));
58✔
546

547
    lua.lua_pushjsfunction(L, () => {
36✔
548
      const pin = luaToJs(L, 1) as number;
30✔
549
      const mode = luaToJs(L, 2) as number;
31✔
550
      const value = luaToJs(L, 3) as number;
32✔
551
      actions.push({ type: "write_pin", args: [pin, mode, value] });
66✔
552
      return 0;
8✔
553
    });
4✔
554
    lua.lua_setfield(L, envIndex, to_luastring("write_pin"));
59✔
555

556
    lua.lua_pushjsfunction(L, () => {
36✔
557
      const pin = luaToJs(L, 1) as number;
30✔
558
      actions.push({ type: "toggle_pin", args: [pin] });
54✔
559
      return 0;
8✔
560
    });
4✔
561
    lua.lua_setfield(L, envIndex, to_luastring("toggle_pin"));
60✔
562

563
    lua.lua_pushjsfunction(L, () => {
36✔
564
      const lengths = getGardenSize();
36✔
565
      jsToLua(L, lengths);
24✔
566
      return 1;
8✔
567
    });
4✔
568
    lua.lua_setfield(L, envIndex, to_luastring("garden_size"));
61✔
569

570
    lua.lua_pushjsfunction(L, () => {
36✔
571
      const status = getDeviceStatus();
37✔
572
      jsToLua(L, status);
23✔
573
      return 1;
8✔
574
    });
4✔
575
    lua.lua_setfield(L, envIndex, to_luastring("read_status"));
61✔
576

577
    lauxlib.luaL_loadstring(L, to_luastring(LUA_HELPERS));
56✔
578
    lua.lua_pushvalue(L, -2);
27✔
579
    lua.lua_setupvalue(L, -2, 1);
31✔
580
    lua.lua_pcall(L, 0, 0, 0);
28✔
581

582
    lua.lua_pushjsfunction(L, () => {
36✔
583
      actions.push({ type: "emergency_lock", args: [] });
55✔
584
      return 0;
8✔
585
    });
4✔
586
    lua.lua_setfield(L, envIndex, to_luastring("emergency_lock"));
64✔
587

588
    lua.lua_pushjsfunction(L, () => {
36✔
589
      actions.push({ type: "emergency_unlock", args: [] });
57✔
590
      return 0;
8✔
591
    });
4✔
592
    lua.lua_setfield(L, envIndex, to_luastring("emergency_unlock"));
66✔
593

594
    lua.lua_newtable(L);
22✔
595
    lua.lua_pushjsfunction(L, () => {
36✔
596
      const key = luaToJs(L, 2) as string;
30✔
597
      return createRecursiveNotImplemented(L, actions, [key]);
55✔
598
    });
4✔
599
    lua.lua_setfield(L, -2, to_luastring("__index"));
51✔
600
    lua.lua_setmetatable(L, -2);
30✔
601

602
    const statusLoad = lauxlib.luaL_loadstring(L, to_luastring(luaCode));
71✔
603
    if (statusLoad !== lua.LUA_OK) {
35✔
604
      const errorMsg = `Lua load error: ${luaToJs(L, -1)}`;
57✔
605
      error(errorMsg);
20✔
606
      return [];
9✔
607
    }
2✔
608

609
    lua.lua_pushvalue(L, -2);
27✔
610
    lua.lua_setupvalue(L, -2, 1);
31✔
611

612
    const statusCall = lua.lua_pcall(L, 0, lua.LUA_MULTRET, 0);
61✔
613
    if (statusCall !== lua.LUA_OK) {
35✔
614
      const errorMsg = `Lua call error: ${luaToJs(L, -1)}`;
57✔
615
      error(errorMsg);
20✔
616
      return [];
9✔
617
    }
2✔
618
    return actions;
15✔
619
  };
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc