diff --git a/bundle b/bundle new file mode 100755 index 0000000..80bb76a --- /dev/null +++ b/bundle @@ -0,0 +1,12 @@ +#!/bin/bash +# todo set utility directory and also customize start.be +MODULES=/data/coding/berry/modules +# todo bash lower case no space function +rm ./utils.tapp +zip -j -Z store ./utils.tapp \ +$MODULES/util/stringe.be \ +$MODULES/util/file.be \ +$MODULES/util/object.be \ +$MODULES/util/time.be \ +$MODULES/util/ha.be + diff --git a/file.be b/file.be new file mode 100644 index 0000000..fb6d979 --- /dev/null +++ b/file.be @@ -0,0 +1,139 @@ +# load and save a json file + +import json +# ########################## +# uncomment on dev machine, commented on esp +# import os +# path=os.path +import path # uncommented on esp, commented on dev + +import string +############################# + +class File + var _filename + var _ext + + # must use nil for filename if only setting extension + def init(filename,ext) + self._ext = ext ? '.'+str(ext) : '.json' + self._filename = self.addext(filename) + end + + def addext(filename) + if !filename filename=self._filename end + # print ('extension', self._ext, filename) + var name = string.find(filename,'.') < 0 ? filename+self._ext : filename + return name + end + + def delete(filename) + filename = filename ? self.addext(filename) : self._filename + if path.remove(filename) + print('deleted file', filename) + else + print('unable to delte file', filename) + end + end + + def name(filename) + if filename self._filename = self.addext(filename) end + return self._filename + end + + # returns parsed json + def load(filename) + filename = filename ? self.addext(filename) : self._filename + var f # file object + if path.exists(filename) + var obj + try + f = open(filename, "r") + obj = json.load(f.read()) + f.close() + except .. as e, m + if f != nil f.close() end + raise e, m + end + if debug[1] + print ('=============') + print ('loaded file ',filename, '. Returning parsed object') + print (obj) + print ('=============') + end + return obj + else + print('Warning: file does not exist, nothing loaded', filename) + end + + + return nil + end + + # obj is requried + def save(obj,filename) + if ! obj + print('ERROR: must pass a object to save as json') + return nil + end + filename = filename ? self.addext(filename) : self._filename + if ! filename + print('ERROR: no filename set to save object as json') + return nil + end + var f # file object + try + f = open(filename, "w") + self._json_fdump_any(f,obj) + f.close() + except .. as e, m + if f != nil f.close() end + f.close() + raise e, m + end + if debug[3] print('success writing to file',filename) end + end + + # internal utility methods + + def _json_fdump_map(f, v) + f.write('{') + var sep = nil + for k:v.keys() + if sep != nil f.write(sep) end + f.write(json.dump(str(k))) + f.write(':') + self._json_fdump_any(f, v[k]) + sep = "," + end + f.write('}') + end + + def _json_fdump_list(f, v) + f.write('[') + var i = 0 + while i < size(v) + if i > 0 f.write(',') end + self._json_fdump_any(f, v[i]) + i += 1 + end + f.write(']') + end + + def _json_fdump_any(f, v) + import json + if isinstance(v, map) + self._json_fdump_map(f, v) + elif isinstance(v, list)v + self._json_fdump_list(f, v) + else + f.write(json.dump(v)) + end + end + +end # FILE class + +var file = module('file') +# file.create = File +file = File +return file \ No newline at end of file diff --git a/ha.be b/ha.be new file mode 100644 index 0000000..41a4bd7 --- /dev/null +++ b/ha.be @@ -0,0 +1,62 @@ +import string +import mqtt +import json +import object + +def entity_create(Name,opts) + # opts = { type: (sensor/binary-sensor), jsonkey: , icon:, topic_prefix, topic, unit, custom} + if !Name + print('must pass a name for the home assistant entity') + return nil + end + opts = object(opts) + opts.type = opts.type ? opts.type : 'sensor' + print('######### creating Home Assitant entity:',Name, '#############') + var mac = string.tolower(string.split(tasmota.wifi()['mac'],':').concat()) + # var mac = '1q2w3e4r5t' + var name = string.tolower(string.split(Name,' ').concat('_')) + var id = mac+'-'+name + # print(mac,name, id) + var topic = tasmota.cmd('topic')['Topic'] + var Device = tasmota.cmd('status')['Status']['DeviceName'] + var device = string.tolower(string.split(Device,' ').concat('_')) + # var topic = 'tastmotatesting' + # var dis_topic = 'tasmota/discovery/'+id+'/sensors/config' + var dis_topic = 'homeassistant/sensor/'+mac+'/'+ name +'/config' + print(dis_topic) + var p = object() # payload + p.name = Device + ' ' + Name + p.uniq_id = id + p.device = {'cns': [['mac',mac]]} + if opts.topic + p.state_topic = opts.topic + else + if opts.topic_prefix + p.state_topic = opts.topic_prefix + '/' + device + '/' + name + else + p.state_topic = 'stat/'+ topic + '/'+ name + end + end + if opts.unit p.unit_of_measurement = opts.unit end + if opts.icon p.icon = opts.icon end + if opts.jsonkey p.value_template = '{{ value_json.'+ opts.jsonkey + ' }}' end + if opts.value_template p.value_template = opts.value_template end + if opts.custom + # opts.custom = type(opts.custom == 'string') ? json.load(opts.custom) : opts.custom + print('merged custom', opts.custom) + p.merge(opts.custom,true) + print(p.get()) + end + print(p.dump()) + print("############ Entity Created #######################") + + # (topic:string, payload:string[, retain:bool, start:int, len:int]) - + mqtt.publish(dis_topic,p.dump(), true) + + return [p.get(), dis_topic, mac , device] + +end + +var ha = module('ha') +ha.entity_create = entity_create +return ha diff --git a/object.be b/object.be new file mode 100644 index 0000000..d7dfcd5 --- /dev/null +++ b/object.be @@ -0,0 +1,252 @@ +# uci +import file +# native +import string +import json + +# really a a nested map designe to work like a javascript object with . notation +class Object + var _obj + var _autosave # will save object(map) whenever changes are made, default: false + var _autoload # if true will attempt to load from file on init, default: true + var _status + var file # attached file instance + var _deep # depth to follow nested maps, default = -1 which mean walk until no more + + # forbidden key names + # any key starting with _ + # init + # member + # setmember + # assign + # retrieve + # load + # save + # get + # set + # file + # filename + # exporti + # importi + + # for passing only opts must set obj to {} or nil, e.g. (nil,{'autosave':true}) + def init(obj,filename,opts) + if (type(obj) == 'string') + if debug[3] print('no object passed a filename was passed') end + opts = filename + filename = obj + obj = nil + end + if isinstance(filename, map) + opts = filename + filename = nil + end + if ! opts opts = {} end + self.assign(self._isMap(obj,true)) # will set to empty map if obj is nil + if filename + if debug[3] print('filename passsed, adding file instance', filename) end + self.file = file(filename,opts.find('ext')) # create file instance + end + self._autosave = opts.find('autosave') == nil ? false : opts.find('autosave') + self._autoload = opts.find('autoload') == nil ? true : opts.find('autoload') + if self.file + if self._isMap(obj) + if self._autosave + if debug[3] print('autosaving passed object map to', self.file.name()) end + self.save() + end + else + if self._autoload + if debug[3] print('autoloading', self.file.name()) end + return self.load() + end + end + end + end + # + def load (filename) + if self.file + if debug[3] print ('loading before', self._obj) end + var obj = self.file.load(filename) + if isinstance(obj,map) + self._obj = obj + return obj + # walk object looking for additional maps? + else + if debug[3] print ('Warning: Unable to load file or file does not contain a map') end + end + if debug[3] print ('loading after',self._obj) end + end + return nil + end + + def save (filename) + if self.file + if debug[3] + print ('saving', self.file.addext(filename)) + print ('object to be saved', self._obj) + # print ('contents of current file', self.load(filename)) + end + self.file.save(self._obj,filename) + # if debug[3] print ('after saving',self.load(filename)) end + end + end + + + def _isMap (m,init) + if isinstance(m, map) + return m + else + if init + return {} + else + return nil + end + end + end + + def merge(m,overwrite) + for k: m.keys() + if overwrite + self._obj.setitem(k,m[k]) + else + self._obj.insert(k,m[k]) + end + end + return self._obj + end + + def assign(obj) + if ! obj obj={} end + if isinstance(obj, map) + self._obj=obj + else + + end + return self._obj + end + + def retrieve() + return self._obj + end + + def exporti(save) + var obj = self._obj + if save && self.file + self.save() + obj = nil + end + return { 'filename': self.filename(), 'obj':obj, 'opts':{'autosave':self._autosave, 'autoload':self._autoload} } + end + + def importi(s) + print ('setting to import',s) + return Object(s.item('obj'),s.item('filename'),s.item('opts')) + end + + def filename() + if self.file + return self.file.name() + else + return nil + end + end + + def del(keys) + self.set(keys,'__remove__') + end + + # # sets a nested property + def set(keys,value) + + def _set(keys,value,obj) + if !obj obj = self._obj end + var key = keys.pop() + if !obj.find(key) + obj[key] = {} + end + if keys.size() + return _set(keys,value,obj[key]) + else + if value == '__remove__' + obj.remove(key) + else + obj[key]=value + end + return true + end + end + + if type(keys) == 'string' keys = string.split(keys,".") end + if !isinstance(keys,list) + print('ERROR: list of keys not passed', keys) + return nil + end + keys.reverse() + if debug[3] print('setting',keys,'to',value) end + if _set(keys,value) + if self.file && self._autosave self.save() end + end + + end + + def dump(subkey) + if subkey == nil return json.dump(self.get()) end + return json.dump(self.get(subkey)) + end + + # gets a nested property + def get(keys) + + if keys == nil return self._obj end + + def _get(keys,obj) + if !obj + obj = self._obj + end + var key = keys.pop() + var _obj = obj.find(key) + if debug[1] + print('current key:', key, 'remaining keys:', keys ) + print('current nested object: ',_obj) + end + if isinstance(_obj,map) + if keys.size() + return _get(keys,_obj) + else + return _obj + end + else + return _obj + end + end + + if type(keys) == 'string' keys = string.split(keys,".") end + if !isinstance(keys,list) + print('ERROR: list of keys not passed', keys) + return nil + end + keys.reverse() + if debug[3] print('getting',keys) end + return _get(keys) + end + + # - virtual member getter, if a key does not exists return `nil`-# + def member(key) + return self._obj.find(key) + end + #- virtual member setter -# + def setmember(key, value) + if debug[3] + print ('setting key=>', key,':', value) + end + self._obj[key] = value + if self._autosave self.save() end + return self.member(key) + end + +end # Object Class + +var object = module('object') +object = Object +return object \ No newline at end of file diff --git a/stringe.be b/stringe.be new file mode 100644 index 0000000..5b7054e --- /dev/null +++ b/stringe.be @@ -0,0 +1,16 @@ +import string + +def tolower(astr) + return string.tr(astr,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz") +end + +def nospaces(astr) + return string.tr(astr," ","") +end + +var stringe = module('stringe') +stringe.tolower=tolower +stringe.nospaces=nospaces + +return stringe + diff --git a/time.be b/time.be new file mode 100644 index 0000000..1b19f83 --- /dev/null +++ b/time.be @@ -0,0 +1,208 @@ +import string + +class Time + + var utc + var last + var times + + def init () + self.utc = false + self.last = nil + self.times = {} + end + + static def iso(epoch) + return tasmota.time_str(epoch) + end + + static def zone(offset) + return tasmota.cmd("timezone "+str(offset))['Timezone'] + end + + # dynamic methods + + + def prop(epoch,prop) + if type(epoch) != 'int' + prop = epoch + epoch = self.now('e') + end + prop = prop ? prop : "" + var t = tasmota.time_dump(epoch) + if t.contains(prop) + return t.item(prop) + else + return t + end + end + + def isLocal(epoch) + return ! self.utc + end + + def save (name,epoch) + if ! name + return false + end + if epoch == 'last' + epoch = self.last + end + if type(epoch) != 'integer' + epoch = self.now('e') + end + self.times.setitem(name,epoch) + return self.get(name) + end + + def remove (name) + self.times.remove(name) + end + + def get (name) + return name ? self.times.find(name) : self.times + end + + def elapsed (name, unit) + if self.get(name) + var elapsed = self.now('e') - self.get(name) + if unit == 'hms' return self.hms(elapsed) end + return elapsed + end + + end + + def now(format) + var e = tasmota.rtc() + if format == 'r' + return e + end + e = e[self.utc ? 'utc' : 'local'] + self.last = e + if format == nil + return self.iso(e) + end + if format == 'full' + return tasmota.strftime(e) + end + if format == 'e' + return e + end + + end + + def hms(t) # in seconds + var h = t / 3600 + var s = (t % 3600) + var m = s / 60 + s = s % 60 + return string.format('%d:%d:%d',h,m,s) + end + + def tomorrow(unit) + return self.day(unit,self.now('e') + 86400) + end + + def yesterday(unit) + return self.day(unit,self.now('e') - 86400) + end + + def today(unit) + return self.day(unit) + end + + + + + def day(unit,e) + if type(unit) == int + e = unit + unit = nil + end + e = e ? e : self.now('e') + if unit == 'json' + var d = tasmota.time_dump(e) + d.remove('hour') + d.remove('min') + d.remove('sec') + return d + end + if unit == 'full' return tasmota.strftime("%d %B %Y",e) end + if unit == 'dmy' return tasmota.strftime("%d/%m/%Y",e) end + if unit == 'ymd' return tasmota.strftime("%Y-%m-%d",e) end + return tasmota.strftime("%m/%d/%Y",e) + end + + + def tod(unit) + var t = {} + t = tasmota.time_dump(self.now('e')) + if unit == 'json' + t.remove('weekday') + t.remove('month') + t.remove('year') + t.remove('day') + return t + end + if unit == 'sec' + return t['hour']*3600+t['min']*60+t['sec'] + end + if unit == 'hour' + return t['hour']+t['min']*1.0/60+t['sec']*1.0/3600 + end + if unit == 'min' + return t['hour']*60+t['min']+t['sec']*1.0/60 + end + if unit == '24' + return string.format('%02d:%02d:%02d',t['hour'],t['min'],t['sec']) + end + var half = t['hour'] > 11 ? "P.M" : "A.M" + var hour = t['hour'] > 12 ? t['hour'] - 12 : t['hour'] + return string.format('%02d:%02d:%02d %s',hour,t['min'],t['sec'],half) + end + + def test () + + print('epoch: ',self.now('e')) + print('iso: ',self.now()) + print('raw: ',self.now('r')) + + print('props: ',self.prop()) + print('prop year: ',self.prop('year')) + print('prop year 0: ',self.prop(0,'year')) + + + print('day default(mdy): ',self.day()) + print('day json: ',self.day('json')) + print('day dmy: ',self.day('dmy')) + print('day ymd: ',self.day('ymd')) + print('day full: ',self.day('full')) + + print('tomorrow ymd: ',self.tomorrow('ymd')) + print('today ymd: ',self.today('ymd')) + print('yesterda ymd: ',self.yesterday('ymd')) + + print('tod default: ',self.tod()) + print('tod json: ',self.tod('json')) + print('tod sec: ',self.tod('sec')) + print('tod minutes: ',self.tod('min')) + print('tod hours: ',self.tod('hour')) + print('tod 24: ',self.tod('24')) + self.save('test1') + print('test1: ',self.get('test1')) + print('last: ',self.last) + self.save('test2') + print('test2: ',self.get('test2')) + print('last: ',self.last) + print('all saved:', self.get()) + print('hms',self.hms(1),self.hms(61),self.hms(3661)) + + end + +end + + +var time = module('time') +time = Time() +if debug[3] print('returning time instance') end +return time \ No newline at end of file