2021-11-13 19:45:27 +00:00
|
|
|
#- autocong module for Berry -#
|
|
|
|
#- -#
|
|
|
|
#- To solidify: -#
|
|
|
|
#-
|
|
|
|
# load only persis_module and persist_module.init
|
|
|
|
import autoconf
|
|
|
|
solidify.dump(autoconf_module)
|
|
|
|
# copy and paste into `be_autoconf_lib.c`
|
|
|
|
-#
|
|
|
|
#-
|
|
|
|
|
|
|
|
# For external compile:
|
|
|
|
|
|
|
|
display = module("display")
|
|
|
|
self = nil
|
|
|
|
tasmota = nil
|
|
|
|
def load() end
|
|
|
|
|
|
|
|
-#
|
|
|
|
|
|
|
|
var autoconf_module = module("autoconf")
|
|
|
|
|
|
|
|
autoconf_module.init = def (m)
|
|
|
|
|
|
|
|
class Autoconf
|
|
|
|
var _archive
|
|
|
|
var _error
|
|
|
|
|
|
|
|
def init()
|
|
|
|
import path
|
|
|
|
import string
|
|
|
|
|
|
|
|
var dir = path.listdir("/")
|
|
|
|
var entry
|
|
|
|
tasmota.add_driver(self)
|
|
|
|
|
|
|
|
var i = 0
|
|
|
|
while i < size(dir)
|
2021-11-15 21:11:04 +00:00
|
|
|
if string.find(dir[i], ".autoconf") > 0 # does the file contain '*.autoconf', >0 to skip `.autoconf`
|
2021-11-13 19:45:27 +00:00
|
|
|
if entry != nil
|
|
|
|
# we have multiple configuration files, not allowed
|
|
|
|
print(string.format("CFG: multiple autoconf files found, aborting ('%s' + '%s')", entry, dir[i]))
|
|
|
|
self._error = true
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
entry = dir[i]
|
|
|
|
end
|
|
|
|
i += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
if entry == nil
|
2021-11-27 10:01:24 +00:00
|
|
|
tasmota.log("CFG: no '*.autoconf' file found", 2)
|
2021-11-13 19:45:27 +00:00
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
self._archive = entry
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# ####################################################################################################
|
|
|
|
# Manage first time marker
|
|
|
|
# ####################################################################################################
|
|
|
|
def is_first_time()
|
|
|
|
import path
|
|
|
|
return !path.exists("/.autoconf")
|
|
|
|
end
|
|
|
|
def set_first_time()
|
|
|
|
var f = open("/.autoconf", "w")
|
|
|
|
f.close()
|
|
|
|
end
|
|
|
|
def clear_first_time()
|
|
|
|
import path
|
|
|
|
path.remove("/.autoconf")
|
|
|
|
end
|
|
|
|
|
|
|
|
# ####################################################################################################
|
|
|
|
# Delete all autoconfig files present
|
|
|
|
# ####################################################################################################
|
|
|
|
def delete_all_configs()
|
|
|
|
import path
|
|
|
|
import string
|
|
|
|
var dir = path.listdir("/")
|
|
|
|
|
|
|
|
for d:dir
|
2021-11-15 21:11:04 +00:00
|
|
|
if string.find(d, ".autoconf") > 0 # does the file contain '*.autoconf'
|
2021-11-13 19:45:27 +00:00
|
|
|
path.remove(d)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# ####################################################################################################
|
|
|
|
# Get current module
|
2021-11-15 21:11:04 +00:00
|
|
|
# contains the name of the archive without leading `/`, ex: `M5Stack_Fire.autoconf`
|
2021-11-13 19:45:27 +00:00
|
|
|
# or `nil` if none
|
|
|
|
# ####################################################################################################
|
|
|
|
def get_current_module_path()
|
|
|
|
return self._archive
|
|
|
|
end
|
|
|
|
def get_current_module_name()
|
2021-11-15 21:11:04 +00:00
|
|
|
return self._archive[0..-10]
|
2021-11-13 19:45:27 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# ####################################################################################################
|
|
|
|
# Load templates from Github
|
|
|
|
# ####################################################################################################
|
|
|
|
def load_templates()
|
|
|
|
import string
|
|
|
|
import json
|
|
|
|
try
|
|
|
|
var url = string.format("https://raw.githubusercontent.com/tasmota/autoconf/main/%s_manifest.json", tasmota.arch())
|
|
|
|
tasmota.log(string.format("CFG: loading '%s'", url), 3)
|
|
|
|
# load the template
|
|
|
|
var cl = webclient()
|
|
|
|
cl.begin(url)
|
|
|
|
var r = cl.GET()
|
|
|
|
if r != 200
|
|
|
|
tasmota.log(string.format("CFG: return_code=%i", r), 2)
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
var s = cl.get_string()
|
|
|
|
cl.close()
|
|
|
|
# convert to json
|
|
|
|
var j = json.load(s)
|
|
|
|
tasmota.log(string.format("CFG: loaded '%s'", str(j)), 3)
|
|
|
|
|
2021-11-15 21:11:04 +00:00
|
|
|
var t = j.find("files")
|
2021-11-13 19:45:27 +00:00
|
|
|
if isinstance(t, list)
|
|
|
|
return t
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
except .. as e, m
|
|
|
|
tasmota.log(string.format("CFG: exception '%s' - '%s'", e, m), 2)
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# ####################################################################################################
|
|
|
|
# Init web handlers
|
|
|
|
# ####################################################################################################
|
|
|
|
# Displays a "Autocong" button on the configuration page
|
|
|
|
def web_add_config_button()
|
|
|
|
import webserver
|
|
|
|
webserver.content_send("<p><form id=ac action='ac' style='display: block;' method='get'><button>🪄 Auto-configuration</button></form></p>")
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# This HTTP GET manager controls which web controls are displayed
|
|
|
|
def page_autoconf_mgr()
|
|
|
|
import webserver
|
|
|
|
import string
|
|
|
|
if !webserver.check_privileged_access() return nil end
|
|
|
|
|
|
|
|
webserver.content_start('Auto-configuration')
|
|
|
|
webserver.content_send_style()
|
|
|
|
webserver.content_send("<p><small> (This feature requires an internet connection)</small></p>")
|
|
|
|
|
|
|
|
var cur_module = self.get_current_module_path()
|
2021-11-15 21:11:04 +00:00
|
|
|
var cur_module_display = cur_module ? string.tr(self.get_current_module_name(), "_", " ") : self._error ? "<Error: apply new or remove>" : "<None>"
|
2021-11-13 19:45:27 +00:00
|
|
|
|
|
|
|
webserver.content_send("<fieldset><style>.bdis{background:#888;}.bdis:hover{background:#888;}</style>")
|
|
|
|
webserver.content_send(string.format("<legend><b title='Autoconfiguration'> Current auto-configuration</b></legend>"))
|
|
|
|
webserver.content_send(string.format("<p>Current configuration: </p><p><b>%s</b></p>", cur_module_display))
|
|
|
|
|
|
|
|
if cur_module
|
|
|
|
# add button to reapply template
|
|
|
|
webserver.content_send("<p><form id=reapply style='display: block;' action='/ac' method='post' ")
|
|
|
|
webserver.content_send("onsubmit='return confirm(\"This will cause a restart.\");'>")
|
|
|
|
webserver.content_send("<button name='reapply' class='button bgrn'>Re-apply current configuration</button>")
|
|
|
|
webserver.content_send("</form></p>")
|
|
|
|
end
|
|
|
|
webserver.content_send("<p></p></fieldset><p></p>")
|
|
|
|
|
|
|
|
webserver.content_send("<fieldset><style>.bdis{background:#888;}.bdis:hover{background:#888;}</style>")
|
|
|
|
webserver.content_send(string.format("<legend><b title='New autoconf'> Select new auto-configuration</b></legend>"))
|
|
|
|
|
|
|
|
webserver.content_send("<p><form id=zip style='display: block;' action='/ac' method='post' ")
|
|
|
|
webserver.content_send("onsubmit='return confirm(\"This will change the current configuration and cause a restart.\");'>")
|
|
|
|
webserver.content_send("<label>Choose a device configuration:</label><br>")
|
|
|
|
webserver.content_send("<select name='zip'>")
|
|
|
|
|
|
|
|
var templates = self.load_templates()
|
|
|
|
webserver.content_send("<option value='reset'><Remove autoconf></option>")
|
|
|
|
for t:templates
|
|
|
|
webserver.content_send(string.format("<option value='%s'>%s</option>", t, string.tr(t, "_", " ")))
|
|
|
|
end
|
|
|
|
|
|
|
|
webserver.content_send("</select><p></p>")
|
|
|
|
|
|
|
|
webserver.content_send("<button name='zipapply' class='button bgrn'>Apply configuration</button>")
|
|
|
|
# webserver.content_send(string.format("<input name='ota' type='hidden' value='%d'>", ota_num))
|
|
|
|
webserver.content_send("</form></p>")
|
|
|
|
|
|
|
|
|
|
|
|
webserver.content_send("<p></p></fieldset><p></p>")
|
|
|
|
webserver.content_button(webserver.BUTTON_CONFIGURATION)
|
|
|
|
webserver.content_stop()
|
|
|
|
end
|
|
|
|
|
|
|
|
# ####################################################################################################
|
|
|
|
# Web controller
|
|
|
|
#
|
|
|
|
# Applies the changes and restart
|
|
|
|
# ####################################################################################################
|
|
|
|
# This HTTP POST manager handles the submitted web form data
|
|
|
|
def page_autoconf_ctl()
|
|
|
|
import webserver
|
|
|
|
import string
|
|
|
|
import path
|
|
|
|
if !webserver.check_privileged_access() return nil end
|
|
|
|
|
|
|
|
try
|
|
|
|
if webserver.has_arg("reapply")
|
|
|
|
tasmota.log("CFG: removing first time marker", 2);
|
|
|
|
# print("CFG: removing first time marker")
|
|
|
|
self.clear_first_time()
|
|
|
|
#- and force restart -#
|
|
|
|
webserver.redirect("/?rst=")
|
|
|
|
|
|
|
|
elif webserver.has_arg("zip")
|
|
|
|
# remove any remaining autoconf file
|
|
|
|
tasmota.log("CFG: removing autoconf files", 2);
|
|
|
|
# print("CFG: removing autoconf files")
|
|
|
|
self.delete_all_configs()
|
|
|
|
|
|
|
|
# get the name of the configuration file
|
|
|
|
var arch_name = webserver.arg("zip")
|
|
|
|
|
|
|
|
if arch_name != "reset"
|
2021-11-15 21:11:04 +00:00
|
|
|
var url = string.format("https://raw.githubusercontent.com/tasmota/autoconf/main/%s/%s.autoconf", tasmota.arch(), arch_name)
|
2021-11-13 19:45:27 +00:00
|
|
|
tasmota.log(string.format("CFG: downloading '%s'", url), 2);
|
|
|
|
|
2021-11-15 21:11:04 +00:00
|
|
|
var local_file = string.format("%s.autoconf", arch_name)
|
2021-11-13 19:45:27 +00:00
|
|
|
|
|
|
|
# download file and write directly to file system
|
|
|
|
var cl = webclient()
|
|
|
|
cl.begin(url)
|
|
|
|
var r = cl.GET()
|
|
|
|
if r != 200 raise "connection_error", string.format("return code=%i", r) end
|
|
|
|
cl.write_file(local_file)
|
|
|
|
cl.close()
|
|
|
|
end
|
|
|
|
|
|
|
|
# remove marker to reapply template
|
|
|
|
self.clear_first_time()
|
|
|
|
|
|
|
|
#- and force restart -#
|
|
|
|
webserver.redirect("/?rst=")
|
|
|
|
else
|
|
|
|
raise "value_error", "Unknown command"
|
|
|
|
end
|
|
|
|
except .. as e, m
|
|
|
|
print(string.format("CFG: Exception> '%s' - %s", e, m))
|
|
|
|
#- display error page -#
|
|
|
|
webserver.content_start("Parameter error") #- title of the web page -#
|
|
|
|
webserver.content_send_style() #- send standard Tasmota styles -#
|
|
|
|
|
|
|
|
webserver.content_send(string.format("<p style='width:340px;'><b>Exception:</b><br>'%s'<br>%s</p>", e, m))
|
|
|
|
|
|
|
|
webserver.content_button(webserver.BUTTON_CONFIGURATION) #- button back to management page -#
|
|
|
|
webserver.content_stop() #- end of web page -#
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Add HTTP POST and GET handlers
|
|
|
|
def web_add_handler()
|
|
|
|
import webserver
|
|
|
|
webserver.on('/ac', / -> self.page_autoconf_mgr(), webserver.HTTP_GET)
|
|
|
|
webserver.on('/ac', / -> self.page_autoconf_ctl(), webserver.HTTP_POST)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# reset the configuration information (but don't restart)
|
|
|
|
# i.e. remove any autoconf file
|
|
|
|
def reset()
|
|
|
|
import path
|
|
|
|
import string
|
|
|
|
|
|
|
|
var dir = path.listdir("/")
|
|
|
|
var entry
|
|
|
|
|
|
|
|
var i = 0
|
|
|
|
while i < size(dir)
|
|
|
|
var fname = dir[i]
|
2021-11-15 21:11:04 +00:00
|
|
|
if string.find(fname, ".autoconf") > 0 # does the file contain '*.autoconf'
|
2021-11-13 19:45:27 +00:00
|
|
|
path.remove(fname)
|
|
|
|
print(string.format("CFG: removed file '%s'", fname))
|
|
|
|
end
|
|
|
|
i += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
self._archive = nil
|
|
|
|
self._error = nil
|
|
|
|
end
|
|
|
|
|
2021-11-16 20:46:42 +00:00
|
|
|
# called by the synthetic event `preinit`
|
2021-11-13 19:45:27 +00:00
|
|
|
def preinit()
|
|
|
|
if self._archive == nil return end
|
|
|
|
# try to launch `preinit.be`
|
|
|
|
import path
|
|
|
|
|
|
|
|
var fname = self._archive + '#preinit.be'
|
|
|
|
if path.exists(fname)
|
|
|
|
tasmota.log("CFG: loading "+fname, 3)
|
|
|
|
load(fname)
|
|
|
|
tasmota.log("CFG: loaded "+fname, 3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def run_bat(fname) # read a '*.bat' file and run each command
|
|
|
|
import string
|
|
|
|
var f
|
|
|
|
try
|
|
|
|
f = open(fname, "r") # open file in read-only mode, it is expected to exist
|
|
|
|
while true
|
|
|
|
var line = f.readline() # read each line, can contain a terminal '\n', empty if end of file
|
|
|
|
if size(line) == 0 break end # end of file
|
|
|
|
|
|
|
|
if line[-1] == "\n" line = line[0..-2] end # remove any trailing '\n'
|
|
|
|
if size(line) > 0
|
|
|
|
tasmota.cmd(line) # run the command
|
|
|
|
end
|
|
|
|
end
|
|
|
|
f.close() # close, we don't expect exception with read-only, could be added later though
|
|
|
|
except .. as e, m
|
|
|
|
print(string.format('CFG: could not run %s (%s - %s)', fname, e, m))
|
|
|
|
f.close()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-11-16 20:46:42 +00:00
|
|
|
# called by the synthetic event `autoexec`
|
2021-11-13 19:45:27 +00:00
|
|
|
def autoexec()
|
|
|
|
if self._archive == nil return end
|
|
|
|
# try to launch `preinit.be`
|
|
|
|
import path
|
|
|
|
|
|
|
|
# Step 1. if first run, only apply `init.bat`
|
|
|
|
var fname = self._archive + '#init.bat'
|
|
|
|
if self.is_first_time() && path.exists(fname)
|
|
|
|
# create the '.autoconf' file to avoid running it again, even if it crashed
|
|
|
|
self.set_first_time()
|
|
|
|
|
|
|
|
# if path.exists(fname) # we know it exists from initial test
|
|
|
|
self.run_bat(fname)
|
|
|
|
tasmota.log("CFG: 'init.bat' done, restarting", 2)
|
|
|
|
tasmota.cmd("Restart 1")
|
|
|
|
return # if init was run, force a restart anyways and don't run the remaining code
|
|
|
|
# end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Step 2. if 'display.ini' is present, launch Universal Display
|
|
|
|
fname = self._archive + '#display.ini'
|
|
|
|
if gpio.pin_used(gpio.OPTION_A, 2) && path.exists(fname)
|
|
|
|
if path.exists("display.ini")
|
|
|
|
tasmota.log("CFG: skipping 'display.ini' because already present in file-system", 2)
|
|
|
|
else
|
|
|
|
import display
|
|
|
|
var f = open(fname,"r")
|
|
|
|
var desc = f.read()
|
|
|
|
f.close()
|
|
|
|
display.start(desc)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Step 3. if 'autoexec.bat' is present, run it
|
|
|
|
fname = self._archive + '#autoexec.bat'
|
|
|
|
if path.exists(fname)
|
|
|
|
tasmota.log("CFG: running "+fname, 3)
|
|
|
|
self.run_bat(fname)
|
|
|
|
tasmota.log("CFG: ran "+fname, 3)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Step 4. if 'autoexec.be' is present, load it
|
|
|
|
fname = self._archive + '#autoexec.be'
|
|
|
|
if path.exists(fname)
|
|
|
|
tasmota.log("CFG: loading "+fname, 3)
|
|
|
|
load(fname)
|
|
|
|
tasmota.log("CFG: loaded "+fname, 3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return Autoconf() # return an instance of this class
|
|
|
|
end
|
|
|
|
|
|
|
|
aa = autoconf_module.init(autoconf_module)
|
|
|
|
import webserver
|
|
|
|
webserver.on('/ac2', / -> aa.page_autoconf_mgr(), webserver.HTTP_GET)
|
|
|
|
return autoconf_module
|