mirror of https://github.com/arendst/Tasmota.git
443 lines
14 KiB
C++
443 lines
14 KiB
C++
/*
|
|
xdrv_52_3_berry_embedded.ino - Berry scripting language, embedded code
|
|
|
|
Copyright (C) 2021 Stephan Hadinger, Berry language by Guan Wenliang https://github.com/Skiars/berry
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#ifdef USE_BERRY
|
|
|
|
/*********************************************************************************************\
|
|
* Handlers for Berry calls and async
|
|
*
|
|
\*********************************************************************************************/
|
|
|
|
const char berry_prog[] =
|
|
""
|
|
// create a 'ntv' module to allow functions to be registered in a safe namespace
|
|
// "ntv = module('ntv') "
|
|
|
|
// auto-import modules
|
|
// // import alias
|
|
#ifdef USE_ENERGY_SENSOR
|
|
"import energy "
|
|
#endif
|
|
|
|
// Phase 1
|
|
// "class Tasmota: Tasmota_ntv "
|
|
// for now the variables are built, need to find a way to push in Flash
|
|
// "def init() "
|
|
// "end "
|
|
// // add `chars_in_string(s:string,c:string) -> int``
|
|
// // looks for any char in c, and return the position of the first char
|
|
// // or -1 if not found
|
|
// // inv is optional and inverses the behavior, i.e. look for chars not in the list
|
|
// "def chars_in_string(s,c,inv) "
|
|
// "var inverted = inv ? true : false "
|
|
// "for i:0..size(s)-1 "
|
|
// "var found = false "
|
|
// "for j:0..size(c)-1 "
|
|
// "if s[i] == c[j] found = true end "
|
|
// "end "
|
|
// "if inverted != found return i end "
|
|
// "end "
|
|
// "return -1 "
|
|
// "end "
|
|
|
|
// // find a key in map, case insensitive, return actual key or nil if not found
|
|
// "def find_key_i(m,keyi) "
|
|
// "import string "
|
|
// "var keyu = string.toupper(keyi) "
|
|
// "if classof(m) == map "
|
|
// "for k:m.keys() "
|
|
// "if string.toupper(k)==keyu || keyi=='?' "
|
|
// "return k "
|
|
// "end "
|
|
// "end "
|
|
// "end "
|
|
// "end "
|
|
|
|
// # split the item when there is an operator, returns a list of (left,op,right)
|
|
// // # ex: "Dimmer>50" -> ["Dimmer",tasmota_gt,"50"]
|
|
// "def find_op(item) "
|
|
// "import string "
|
|
// "var op_chars = '=<>!' "
|
|
// "var pos = self.chars_in_string(item, op_chars) "
|
|
// "if pos >= 0 "
|
|
// "var op_split = string.split(item,pos) "
|
|
// "var op_left = op_split[0] "
|
|
// "var op_rest = op_split[1] "
|
|
// "pos = self.chars_in_string(op_rest, op_chars, true) "
|
|
// "if pos >= 0 "
|
|
// "var op_split2 = string.split(op_rest,pos) "
|
|
// "var op_middle = op_split2[0] "
|
|
// "var op_right = op_split2[1] "
|
|
// "return [op_left,op_middle,op_right] "
|
|
// "end "
|
|
// "end "
|
|
// "return [item, nil, nil] "
|
|
// "end "
|
|
|
|
// // Rules
|
|
// "def add_rule(pat,f) "
|
|
// "if !self._rules "
|
|
// "self._rules={} "
|
|
// "end "
|
|
// "if type(f) == 'function' "
|
|
// "self._rules[pat] = f "
|
|
// "else "
|
|
// "raise 'value_error', 'the second argument is not a function' "
|
|
// "end "
|
|
// "end "
|
|
|
|
// "def remove_rule(pat) "
|
|
// "if self._rules "
|
|
// "self._rules.remove(pat) "
|
|
// "end "
|
|
// "end "
|
|
|
|
// // Rules trigger if match. return true if match, false if not
|
|
// "def try_rule(event, rule, f) "
|
|
// "import string "
|
|
// "var rl_list = self.find_op(rule) "
|
|
// "var sub_event = event "
|
|
// "var rl = string.split(rl_list[0],'#') "
|
|
// "for it:rl "
|
|
// "found=self.find_key_i(sub_event,it) "
|
|
// "if found == nil return false end "
|
|
// "sub_event = sub_event[found] "
|
|
// "end "
|
|
// "var op=rl_list[1]"
|
|
// "var op2=rl_list[2]"
|
|
// "if op "
|
|
// "if op=='==' "
|
|
// "if str(sub_event) != str(op2) return false end "
|
|
// "elif op=='!==' "
|
|
// "if str(sub_event) == str(op2) return false end "
|
|
// "elif op=='=' "
|
|
// "if real(sub_event) != real(op2) return false end "
|
|
// "elif op=='!=' "
|
|
// "if real(sub_event) == real(op2) return false end "
|
|
// "elif op=='>' "
|
|
// "if real(sub_event) <= real(op2) return false end "
|
|
// "elif op=='>=' "
|
|
// "if real(sub_event) < real(op2) return false end "
|
|
// "elif op=='<' "
|
|
// "if real(sub_event) >= real(op2) return false end "
|
|
// "elif op=='<=' "
|
|
// "if real(sub_event) > real(op2) return false end "
|
|
// "end "
|
|
// "end "
|
|
// "f(sub_event, rl_list[0], event) "
|
|
// "return true "
|
|
// "end "
|
|
|
|
// // Run rules, i.e. check each individual rule
|
|
// // Returns true if at least one rule matched, false if none
|
|
// "def exec_rules(ev_json) "
|
|
// "if self._rules "
|
|
// "import json "
|
|
// "var ev = json.load(ev_json) "
|
|
// "var ret = false "
|
|
// "if ev == nil "
|
|
// "print('BRY: ERROR, bad json: '+ev_json, 3) "
|
|
// "else "
|
|
// "for r: self._rules.keys() "
|
|
// "ret = self.try_rule(ev,r,self._rules[r]) || ret "
|
|
// "end "
|
|
// "end "
|
|
// "return ret "
|
|
// "end "
|
|
// "return false "
|
|
// "end "
|
|
|
|
// "def set_timer(delay,f) "
|
|
// "if !self._timers self._timers=[] end "
|
|
// "self._timers.push([self.millis(delay),f]) "
|
|
// "end "
|
|
|
|
// // run every 50ms tick
|
|
// "def run_deferred() "
|
|
// "if self._timers "
|
|
// "var i=0 "
|
|
// "while i<self._timers.size() "
|
|
// "if self.time_reached(self._timers[i][0]) "
|
|
// "f=self._timers[i][1] "
|
|
// "self._timers.remove(i) "
|
|
// "f() "
|
|
// "else "
|
|
// "i=i+1 "
|
|
// "end "
|
|
// "end "
|
|
// "end "
|
|
// "end "
|
|
|
|
// // Delay function, internally calls yield() every 10ms to avoid WDT
|
|
// "def delay(ms) "
|
|
// "var tend = self.millis(ms) "
|
|
// "while !self.time_reached(tend) "
|
|
// "self.yield() "
|
|
// "end "
|
|
// "end "
|
|
|
|
// // Add command to list
|
|
// "def add_cmd(c,f) "
|
|
// "if !self._ccmd "
|
|
// "self._ccmd={} "
|
|
// "end "
|
|
// "if type(f) == 'function' "
|
|
// "self._ccmd[c]=f "
|
|
// "else "
|
|
// "raise 'value_error', 'the second argument is not a function' "
|
|
// "end "
|
|
// "end "
|
|
|
|
// // Remove command from list
|
|
// "def remove_cmd(c) "
|
|
// "if self._ccmd "
|
|
// "self._ccmd.remove(c) "
|
|
// "end "
|
|
// "end "
|
|
|
|
// // Execute custom command
|
|
// "def exec_cmd(cmd, idx, payload) "
|
|
// "if self._ccmd "
|
|
// "import json "
|
|
// "var payload_json = json.load(payload) "
|
|
// "var cmd_found = self.find_key_i(self._ccmd, cmd) "
|
|
// "if cmd_found != nil "
|
|
// "self.resolvecmnd(cmd_found) " // set the command name in XdrvMailbox.command
|
|
// "self._ccmd[cmd_found](cmd_found, idx, payload, payload_json) "
|
|
// "return true "
|
|
// "end "
|
|
// "end "
|
|
// "return false "
|
|
// "end "
|
|
|
|
// // Force gc and return allocated memory
|
|
// "def gc() "
|
|
// "import gc "
|
|
// "gc.collect() "
|
|
// "return gc.allocated() "
|
|
// // "end "
|
|
// // simple wrapper to load a file
|
|
// // prefixes '/' if needed, and simpler to use than `compile()`
|
|
// "def load(f) "
|
|
// "import string "
|
|
// "try "
|
|
// // check that the file ends with '.be' of '.bec'
|
|
// "var fl = string.split(f,'.') "
|
|
// "if (size(fl) <= 1 || (fl[-1] != 'be' && fl[-1] != 'bec')) "
|
|
// "raise \"file extension is not '.be' or '.bec'\" "
|
|
// "end "
|
|
// "var native = f[size(f)-1] == 'c' "
|
|
// // add prefix if needed
|
|
// "if f[0] != '/' f = '/' + f end "
|
|
// // load - works the same for .be and .bec
|
|
// "var c = compile(f,'file') "
|
|
// // save the compiled bytecode
|
|
// "if !native "
|
|
// "try "
|
|
// "self.save(f+'c', c) "
|
|
// "except .. as e "
|
|
// "self.log(string.format('BRY: could not save compiled file %s (%s)',f+'c',e)) "
|
|
// "end "
|
|
// "end "
|
|
// // call the compiled code
|
|
// "c() "
|
|
// "self.log(string.format(\"BRY: sucessfully loaded '%s'\",f)) "
|
|
// "except .. as e "
|
|
// "raise \"io_error\",string.format(\"Could not load file '%s'\",f) "
|
|
// "end "
|
|
|
|
// "end "
|
|
|
|
// //
|
|
// // Event from Tasmota is:
|
|
// // 1. event:string -- type of event (cmd, rule, ...)
|
|
// // 2. cmd:string -- name of the command to process
|
|
// // 3. index:int -- index number
|
|
// // 4. payload:string -- payload as text, analyzed as json
|
|
// // //
|
|
// "def event(type, cmd, idx, payload) "
|
|
// "if type=='cmd' return self.exec_cmd(cmd, idx, payload) "
|
|
// "elif type=='rule' return self.exec_rules(payload) "
|
|
// "elif type=='mqtt_data' return nil " // not yet implemented
|
|
// "elif type=='gc' return self.gc() "
|
|
// "elif type=='every_50ms' return self.run_deferred() "
|
|
// "elif self._drivers "
|
|
// "for d:self._drivers "
|
|
// "try "
|
|
// "if type=='every_second' && d.every_second return d.every_second() "
|
|
// "elif type=='every_100ms' && d.every_100ms return d.every_100ms() "
|
|
// "elif type=='web_add_button' && d.web_add_button return d.web_add_button() "
|
|
// "elif type=='web_add_main_button' && d.web_add_main_button return d.web_add_main_button() "
|
|
// "elif type=='save_before_restart' && d.save_before_restart return d.save_before_restart() "
|
|
// "elif type=='web_sensor' && d.web_sensor return d.web_sensor() "
|
|
// "elif type=='json_append' && d.json_append return d.json_append() "
|
|
// "elif type=='button_pressed' && d.button_pressed return d.button_pressed() "
|
|
// "end "
|
|
// "except .. as e,m "
|
|
// "import string "
|
|
// "self.log(string.format('BRY: exception %s - %m',3)) "
|
|
// "end "
|
|
// "end "
|
|
// "end "
|
|
// "end "
|
|
//
|
|
// add driver to the queue of event dispatching
|
|
//
|
|
// "def add_driver(d) "
|
|
// "if self._drivers "
|
|
// "self._drivers.push(d) "
|
|
// "else "
|
|
// "self._drivers = [d]"
|
|
// "end "
|
|
// "end "
|
|
|
|
// // tasmota.wire_scan(addr:int [, index:int]) -> wire1 or wire2 or nil
|
|
// // scan for the first occurrence of the addr, starting with bus1 then bus2
|
|
// // optional: skip if index is disabled via I2CEnable
|
|
// "def wire_scan(addr,idx) "
|
|
// // skip if the I2C index is disabled
|
|
// "if idx != nil && !self.i2c_enabled(idx) return nil end "
|
|
// "if self.wire1.detect(addr) return self.wire1 end "
|
|
// "if self.wire2.detect(addr) return self.wire2 end "
|
|
// "return nil "
|
|
// "end "
|
|
|
|
// // set_light and get_light deprecetaion
|
|
// "def set_light(v,l) "
|
|
// "print('tasmota.set_light() is deprecated, use light.set()') "
|
|
// "import light "
|
|
// "if l != nil "
|
|
// "return light.set(v,l) "
|
|
// "else "
|
|
// "return light.set(v) "
|
|
// "end "
|
|
// "end "
|
|
|
|
// "def get_light(l) "
|
|
// "print('tasmota.get_light() is deprecated, use light.get()') "
|
|
// "import light "
|
|
// "if l != nil "
|
|
// "return light.get(l) "
|
|
// "else "
|
|
// "return light.get() "
|
|
// "end "
|
|
// "end "
|
|
|
|
// // cmd high-level function
|
|
// "def cmd(command) "
|
|
// "import json "
|
|
// "var ret = self._cmd(command) "
|
|
// "var j = json.load(ret) "
|
|
// "if type(j) == 'instance' "
|
|
// "return j "
|
|
// "else "
|
|
// "return {'response':j} "
|
|
// "end "
|
|
// "end "
|
|
|
|
// "end "
|
|
|
|
// // Monkey patch `Driver` class - To be continued
|
|
// "class Driver2 : Driver "
|
|
// "def add_cmd(c, f) "
|
|
// "var tasmota = self.get_tasmota() "
|
|
// "tasmota.add_cmd(c, / cmd, idx, payload, payload_json -> f(self, cmd, idx, payload, payload_json)) "
|
|
// "end "
|
|
// "end "
|
|
// "Driver = Driver2 "
|
|
|
|
// Instantiate tasmota object
|
|
"tasmota = Tasmota() "
|
|
"def log(m,l) tasmota.log(m,l) end "
|
|
"def load(f) tasmota.load(f) end "
|
|
|
|
#ifdef USE_LVGL
|
|
// instanciate singleton
|
|
// "class lvgl : lvgl_ntv "
|
|
// "end "
|
|
// "lv = lvgl() "
|
|
"import lvgl as lv "
|
|
"_lvgl_cb = [ {}, {}, {}, {}, {}, {} ] "
|
|
"_lvgl_cb_obj = [ {}, {}, {}, {}, {}, {} ] "
|
|
"def _lvgl_cb_dispatch(idx, obj, v1, v2, v3, v4) "
|
|
"var func = _lvgl_cb[idx].find(obj) "
|
|
"var inst = _lvgl_cb_obj[idx].find(obj) "
|
|
"if func != nil "
|
|
"return func(inst, v1, v2, v3, v4) "
|
|
"end "
|
|
"return nil "
|
|
"end "
|
|
// array of 6 callback types, each with key (lv_obj pointer converted to int, closure)
|
|
|
|
#endif // USE_LVGL
|
|
|
|
// Wire class
|
|
// "class Wire : Wire_ntv "
|
|
// // read bytes as `bytes()` object
|
|
// "def read_bytes(addr,reg,size) "
|
|
// "self._begin_transmission(addr) "
|
|
// "self._write(reg) "
|
|
// "self._end_transmission(false) "
|
|
// "self._request_from(addr,size) "
|
|
// "var ret=bytes(size) "
|
|
// "while (self._available()) "
|
|
// "ret..self._read() "
|
|
// "end "
|
|
// "return ret "
|
|
// "end "
|
|
// // write bytes from `bytes` object
|
|
// "def write_bytes(addr,reg,b) "
|
|
// "self._begin_transmission(addr) "
|
|
// "self._write(reg) "
|
|
// "self._write(b) "
|
|
// "self._end_transmission() "
|
|
// "end "
|
|
// "end "
|
|
|
|
#ifdef USE_I2C
|
|
"tasmota.wire1 = Wire(1) "
|
|
"tasmota.wire2 = Wire(2) "
|
|
"wire1 = tasmota.wire1 "
|
|
"wire2 = tasmota.wire2 "
|
|
#endif // USE_I2C
|
|
|
|
// auto-import gpio
|
|
"import gpio "
|
|
|
|
#ifdef USE_LIGHT
|
|
"import light "
|
|
#endif // USE_LIGHT
|
|
;
|
|
|
|
const char berry_autoexec[] =
|
|
// load "autoexec.be" using import, which loads either .be or .bec file
|
|
"import string "
|
|
"try "
|
|
"load('autoexec.be') "
|
|
"except .. as e,m "
|
|
"if e=='io_error' && string.find(m, \"autoexec.be\")>0 "
|
|
"log(\"BRY: no autoexec.be\") "
|
|
"else "
|
|
"log(\"BRY: exception in autoexec.be: \"+e+\": \"+m) "
|
|
"end "
|
|
"end "
|
|
;
|
|
#endif // USE_BERRY
|