diff --git a/extra_script.py b/extra_script.py index cb0f57d..a47c8ac 100644 --- a/extra_script.py +++ b/extra_script.py @@ -10,10 +10,18 @@ Import("env") GEN_DIR='generated' CFG_FILE='web/config.json' XDR_FILE='web/xdrconfig.json' -FILES=['web/index.html',CFG_FILE,XDR_FILE,'web/index.js','web/index.css'] CFG_INCLUDE='GwConfigDefinitions.h' XDR_INCLUDE='GwXdrTypeMappings.h' +EMBEDDED_INCLUDE="GwEmbeddedFiles.h" +def getEmbeddedFiles(env): + rt=[] + efiles=env.GetProjectOption("board_build.embed_files") + for f in efiles.split("\n"): + if f == '': + continue + rt.append(f) + return rt def basePath(): #see: https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined @@ -38,126 +46,163 @@ def isCurrent(infile,outfile): print("%s is newer then %s, no need to recreate"%(outfile,infile)) return True return False -def compressFile(inFile): - outfile=os.path.basename(inFile)+".gz" - inFile=os.path.join(basePath(),inFile) - outfile=os.path.join(outPath(),outfile) +def compressFile(inFile,outfile): if isCurrent(inFile,outfile): return with open(inFile, 'rb') as f_in: with gzip.open(outfile, 'wb') as f_out: shutil.copyfileobj(f_in, f_out) - -def generateCfg(): - outfile=os.path.join(outPath(),CFG_INCLUDE) - infile=os.path.join(basePath(),CFG_FILE) +def generateFile(infile,outfile,callback,inMode='rb',outMode='w'): if isCurrent(infile,outfile): return - print("creating %s"%CFG_INCLUDE) + print("creating %s"%outfile) oh=None - with open(CFG_FILE,'rb') as ch: - config=json.load(ch) - try: - with open(outfile,'w') as oh: - oh.write("//generated from %s\n"%CFG_FILE) - oh.write('#include "GwConfigItem.h"\n') - l=len(config) - oh.write('class GwConfigDefinitions{\n') - oh.write(' public:\n') - oh.write(' int getNumConfig() const{return %d;}\n'%(l)) - for item in config: - n=item.get('name') - if n is None: - continue - if len(n) > 15: - raise Exception("%s: config names must be max 15 caracters"%n) - oh.write(' const String %s=F("%s");\n'%(n,n)) - oh.write(' protected:\n') - oh.write(' GwConfigItem *configs[%d]={\n'%(l)) - first=True - for item in config: - if not first: - oh.write(',\n') - first=False - oh.write(" new GwConfigItem(%s,\"%s\")"%(item.get('name'),item.get('default'))) - oh.write('};\n') - oh.write('};\n') - oh.close() - except Exception as e: - if oh is not None: + with open(infile,inMode) as ch: + with open(outfile,'w') as oh: + try: + callback(ch,oh,inFile=infile) + oh.close() + except Exception as e: try: oh.close() except: pass - os.unlink(outfile) - raise + os.unlink(outfile) + raise -def generateXdrMappings(): - outfile=os.path.join(outPath(),XDR_INCLUDE) - infile=os.path.join(basePath(),XDR_FILE) - if isCurrent(infile,outfile): - return - print("creating %s"%XDR_INCLUDE) - oh=None +def writeFileIfChanged(fileName,data): + if os.path.exists(fileName): + with open(fileName,"r") as ih: + old=ih.read() + ih.close() + if old == data: + return + print("#generating %s"%fileName) + with open(fileName,"w") as oh: + oh.write(data) - with open(infile,"rb") as fp: - jdoc=json.load(fp) - try: - with open(outfile,"w") as oh: - oh.write("static GwXDRTypeMapping* typeMappings[]={\n") - first=True - for cat in jdoc: - item=jdoc[cat] - cid=item.get('id') - if cid is None: - continue - tc=item.get('type') - if tc is not None: - if first: - first=False - else: - oh.write(",\n") - oh.write(" new GwXDRTypeMapping(%d,%d,0) /*%s*/"%(cid,tc,cat)) - fields=item.get('fields') - if fields is None: - continue - idx=0 - for fe in fields: - if not isinstance(fe,dict): - continue - tc=fe.get('t') - id=fe.get('v') - if id is None: - id=idx - idx+=1 - l=fe.get('l') or '' - if tc is None or id is None: - continue - if first: - first=False - else: - oh.write(",\n") - oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,tc,id,cat,l)) - oh.write("\n") - oh.write("};\n") - except Exception as e: - if oh: - try: - oh.close() - except: - pass - os.unlink(outfile) - raise +def generateCfg(ch,oh,inFile=''): + config=json.load(ch) + oh.write("//generated from %s\n"%inFile) + oh.write('#include "GwConfigItem.h"\n') + l=len(config) + oh.write('class GwConfigDefinitions{\n') + oh.write(' public:\n') + oh.write(' int getNumConfig() const{return %d;}\n'%(l)) + for item in config: + n=item.get('name') + if n is None: + continue + if len(n) > 15: + raise Exception("%s: config names must be max 15 caracters"%n) + oh.write(' const String %s=F("%s");\n'%(n,n)) + oh.write(' protected:\n') + oh.write(' GwConfigItem *configs[%d]={\n'%(l)) + first=True + for item in config: + if not first: + oh.write(',\n') + first=False + oh.write(" new GwConfigItem(%s,\"%s\")"%(item.get('name'),item.get('default'))) + oh.write('};\n') + oh.write('};\n') + +def generateXdrMappings(fp,oh,inFile=''): + jdoc=json.load(fp) + oh.write("static GwXDRTypeMapping* typeMappings[]={\n") + first=True + for cat in jdoc: + item=jdoc[cat] + cid=item.get('id') + if cid is None: + continue + tc=item.get('type') + if tc is not None: + if first: + first=False + else: + oh.write(",\n") + oh.write(" new GwXDRTypeMapping(%d,%d,0) /*%s*/"%(cid,tc,cat)) + fields=item.get('fields') + if fields is None: + continue + idx=0 + for fe in fields: + if not isinstance(fe,dict): + continue + tc=fe.get('t') + id=fe.get('v') + if id is None: + id=idx + idx+=1 + l=fe.get('l') or '' + if tc is None or id is None: + continue + if first: + first=False + else: + oh.write(",\n") + oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,tc,id,cat,l)) + oh.write("\n") + oh.write("};\n") +def generateEmbedded(elist,outFile): + content="" + for entry in elist: + content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry + writeFileIfChanged(outFile,content) -if not checkDir(): - sys.exit(1) -for f in FILES: - print("compressing %s"%f) - compressFile(f) -generateCfg() -generateXdrMappings() -version="dev"+datetime.now().strftime("%Y%m%d") -env.Append(CPPDEFINES=[('GWDEVVERSION',version)]) +def getContentType(fn): + if (fn.endswith('.gz')): + fn=fn[0:-3] + if (fn.endswith('html')): + return "text/html" + if (fn.endswith('json')): + return "application/json" + if (fn.endswith('js')): + return "text/javascript" + return "application/octet-stream" + +def prebuild(env): + print("#prebuild running") + if not checkDir(): + sys.exit(1) + embedded=getEmbeddedFiles(env) + filedefs=[] + for ef in embedded: + print("#checking embedded file %s"%ef) + (dn,fn)=os.path.split(ef) + pureName=fn + if pureName.endswith('.gz'): + pureName=pureName[0:-3] + ct=getContentType(pureName) + usname=ef.replace('/','_').replace('.','_') + filedefs.append((pureName,usname,ct)) + inFile=os.path.join(basePath(),"web",pureName) + if os.path.exists(inFile): + print("compressing %s"%inFile) + compressFile(inFile,ef) + else: + print("#WARNING: infile %s for %s not found"%(inFile,ef)) + generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE)) + generateFile(os.path.join(basePath(),CFG_FILE),os.path.join(outPath(),CFG_INCLUDE),generateCfg) + generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings) + version="dev"+datetime.now().strftime("%Y%m%d") + env.Append(CPPDEFINES=[('GWDEVVERSION',version)]) + +def cleangenerated(source, target, env): + od=outPath() + if os.path.isdir(od): + print("#cleaning up %s"%od) + for f in os.listdir(od): + if f == "." or f == "..": + continue + fn=os.path.join(od,f) + os.unlink(f) + +print("#prescript...") +prebuild(env) +#script does not run on clean yet - maybe in the future +env.AddPostAction("clean",cleangenerated) diff --git a/lib/webserver/GwWebServer.cpp b/lib/webserver/GwWebServer.cpp index 0dafd09..a09c711 100644 --- a/lib/webserver/GwWebServer.cpp +++ b/lib/webserver/GwWebServer.cpp @@ -16,15 +16,12 @@ class EmbeddedFile { embeddedFiles[name]=this; } } ; -#define EMBED_GZ_FILE(fileName, fileExt, contentType) \ - extern const uint8_t fileName##_##fileExt##_File[] asm("_binary_generated_" #fileName "_" #fileExt "_gz_start"); \ - extern const uint8_t fileName##_##fileExt##_FileLen[] asm("_binary_generated_" #fileName "_" #fileExt "_gz_size"); \ - const EmbeddedFile fileName##_##fileExt##_Config(#fileName "." #fileExt,contentType,(const uint8_t*)fileName##_##fileExt##_File,(int)fileName##_##fileExt##_FileLen); +#define EMBED_GZ_FILE(fileName, binName, contentType) \ + extern const uint8_t binName##_File[] asm("_binary_" #binName "_start"); \ + extern const uint8_t binName##_FileLen[] asm("_binary_" #binName "_size"); \ + const EmbeddedFile binName##_Config(fileName,contentType,(const uint8_t*)binName##_File,(int)binName##_FileLen); -EMBED_GZ_FILE(index,html,"text/html") -EMBED_GZ_FILE(config,json,"application/json") -EMBED_GZ_FILE(index,js,"text/javascript") -EMBED_GZ_FILE(index,css,"text/css") +#include "GwEmbeddedFiles.h" void sendEmbeddedFile(String name,String contentType,AsyncWebServerRequest *request){ std::map::iterator it=embeddedFiles.find(name); diff --git a/platformio.ini b/platformio.ini index 4bf75a3..f3f6732 100644 --- a/platformio.ini +++ b/platformio.ini @@ -23,6 +23,7 @@ board_build.embed_files = generated/index.js.gz generated/index.css.gz generated/config.json.gz + generated/xdrconfig.json.gz board_build.partitions = partitions_custom.csv extra_scripts = pre:extra_script.py