diff --git a/boards/obp40_s3_n8r8.json b/boards/obp40_s3_n8r8.json index 260050c..2ac8c3e 100644 --- a/boards/obp40_s3_n8r8.json +++ b/boards/obp40_s3_n8r8.json @@ -19,8 +19,8 @@ "flash_mode": "qio", "hwids": [ [ - "0x303A", - "0x1001" + "0x1A86", + "0x7523" ] ], "mcu": "esp32s3", diff --git a/extra_script.py b/extra_script.py index ed62c64..b185a93 100644 --- a/extra_script.py +++ b/extra_script.py @@ -10,7 +10,7 @@ from datetime import datetime import re import pprint from platformio.project.config import ProjectConfig - +from platformio.project.exception import InvalidProjectConfError Import("env") #print(env.Dump()) @@ -104,7 +104,17 @@ def writeFileIfChanged(fileName,data): return True def mergeConfig(base,other): + try: + customconfig = env.GetProjectOption("custom_config") + except InvalidProjectConfError: + customconfig = None for bdir in other: + if customconfig and os.path.exists(os.path.join(bdir,customconfig)): + cname=os.path.join(bdir,customconfig) + print("merge custom config {}".format(cname)) + with open(cname,'rb') as ah: + base += json.load(ah) + continue cname=os.path.join(bdir,"config.json") if os.path.exists(cname): print("merge config %s"%cname) @@ -274,9 +284,9 @@ class Grove: def _ss(self,z=False): if z: return self.name - return self.name if self.name is not 'Z' else '' + return self.name if self.name != 'Z' else '' def _suffix(self): - return '_'+self.name if self.name is not 'Z' else '' + return '_'+self.name if self.name != 'Z' else '' def replace(self,line): if line is None: return line diff --git a/extra_script.py.old b/extra_script.py.old new file mode 100644 index 0000000..ed62c64 --- /dev/null +++ b/extra_script.py.old @@ -0,0 +1,518 @@ +print("running extra...") +import gzip +import shutil +import os +import sys +import inspect +import json +import glob +from datetime import datetime +import re +import pprint +from platformio.project.config import ProjectConfig + + +Import("env") +#print(env.Dump()) +OWN_FILE="extra_script.py" +GEN_DIR='lib/generated' +CFG_FILE='web/config.json' +XDR_FILE='web/xdrconfig.json' +INDEXJS="index.js" +INDEXCSS="index.css" +CFG_INCLUDE='GwConfigDefinitions.h' +CFG_INCLUDE_IMPL='GwConfigDefImpl.h' +XDR_INCLUDE='GwXdrTypeMappings.h' +TASK_INCLUDE='GwUserTasks.h' +GROVE_CONFIG="GwM5GroveGen.h" +GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in" +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 + return os.path.dirname(inspect.getfile(lambda: None)) + +def outPath(): + return os.path.join(basePath(),GEN_DIR) +def checkDir(): + dn=outPath() + if not os.path.exists(dn): + os.makedirs(dn) + if not os.path.isdir(dn): + print("unable to create %s"%dn) + return False + return True + +def isCurrent(infile,outfile): + if os.path.exists(outfile): + otime=os.path.getmtime(outfile) + itime=os.path.getmtime(infile) + if (otime >= itime): + own=os.path.join(basePath(),OWN_FILE) + if os.path.exists(own): + owntime=os.path.getmtime(own) + if owntime > otime: + return False + print("%s is newer then %s, no need to recreate"%(outfile,infile)) + return True + return False +def compressFile(inFile,outfile): + if isCurrent(inFile,outfile): + return + print("compressing %s"%inFile) + with open(inFile, 'rb') as f_in: + with gzip.open(outfile, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + +def generateFile(infile,outfile,callback,inMode='rb',outMode='w'): + if isCurrent(infile,outfile): + return + print("creating %s"%outfile) + oh=None + with open(infile,inMode) as ch: + with open(outfile,outMode) as oh: + try: + callback(ch,oh,inFile=infile) + oh.close() + except Exception as e: + try: + oh.close() + except: + pass + os.unlink(outfile) + raise + +def writeFileIfChanged(fileName,data): + if os.path.exists(fileName): + with open(fileName,"r") as ih: + old=ih.read() + ih.close() + if old == data: + return False + print("#generating %s"%fileName) + with open(fileName,"w") as oh: + oh.write(data) + return True + +def mergeConfig(base,other): + for bdir in other: + cname=os.path.join(bdir,"config.json") + if os.path.exists(cname): + print("merge config %s"%cname) + with open(cname,'rb') as ah: + merge=json.load(ah) + base=base+merge + return base + +def replaceTexts(data,replacements): + if replacements is None: + return data + if isinstance(data,str): + for k,v in replacements.items(): + data=data.replace("$"+k,str(v)) + return data + if isinstance(data,list): + rt=[] + for e in data: + rt.append(replaceTexts(e,replacements)) + return rt + if isinstance(data,dict): + rt={} + for k,v in data.items(): + rt[replaceTexts(k,replacements)]=replaceTexts(v,replacements) + return rt + return data +def expandConfig(config): + rt=[] + for item in config: + type=item.get('type') + if type != 'array': + rt.append(item) + continue + replacements=item.get('replace') + children=item.get('children') + name=item.get('name') + if name is None: + name="#unknown#" + if not isinstance(replacements,list): + raise Exception("missing replacements at array %s"%name) + for replace in replacements: + if children is not None: + for c in children: + rt.append(replaceTexts(c,replace)) + return rt + +def generateMergedConfig(inFile,outFile,addDirs=[]): + if not os.path.exists(inFile): + raise Exception("unable to read cfg file %s"%inFile) + data="" + with open(inFile,'rb') as ch: + config=json.load(ch) + config=mergeConfig(config,addDirs) + config=expandConfig(config) + data=json.dumps(config,indent=2) + writeFileIfChanged(outFile,data) + +def generateCfg(inFile,outFile,impl): + if not os.path.exists(inFile): + raise Exception("unable to read cfg file %s"%inFile) + data="" + with open(inFile,'rb') as ch: + config=json.load(ch) + data+="//generated from %s\n"%inFile + l=len(config) + idx=0 + if not impl: + data+='#include "GwConfigItem.h"\n' + data+='class GwConfigDefinitions{\n' + data+=' public:\n' + data+=' 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) + data+=' static constexpr const char* %s="%s";\n'%(n,n) + data+="};\n" + else: + data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n' + for item in config: + name=item.get('name') + if name is None: + continue + data+=' configs[%d]='%(idx) + idx+=1 + secret="false"; + if item.get('type') == 'password': + secret="true" + data+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret) + data+='}\n' + writeFileIfChanged(outFile,data) + +def labelFilter(label): + return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label)) +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,0,%d) /*%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,id,tc,cat,l)) + oh.write("\n") + oh.write("};\n") + for cat in jdoc: + item=jdoc[cat] + cid=item.get('id') + if cid is None: + continue + selectors=item.get('selector') + if selectors is not None: + for selector in selectors: + label=selector.get('l') + value=selector.get('v') + if label is not None and value is not None: + label=labelFilter(label) + define=("GWXDRSEL_%s_%s"%(cat,label)).upper() + oh.write(" #define %s %s\n"%(define,value)) + fields=item.get('fields') + if fields is not None: + idx=0 + for field in fields: + v=field.get('v') + if v is None: + v=idx + else: + v=int(v) + label=field.get('l') + if v is not None and label is not None: + define=("GWXDRFIELD_%s_%s"%(cat,labelFilter(label))).upper(); + oh.write(" #define %s %s\n"%(define,str(v))) + idx+=1 + +class Grove: + def __init__(self,name) -> None: + self.name=name + def _ss(self,z=False): + if z: + return self.name + return self.name if self.name is not 'Z' else '' + def _suffix(self): + return '_'+self.name if self.name is not 'Z' else '' + def replace(self,line): + if line is None: + return line + return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix()) +def generateGroveDefs(inh,outh,inFile=''): + GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')] + definition=[] + started=False + def writeConfig(): + for grove in GROVES: + for cl in definition: + outh.write(grove.replace(cl)) + + for line in inh: + if re.match(" *#GROVE",line): + started=True + if len(definition) > 0: + writeConfig() + definition=[] + continue + if started: + definition.append(line) + if len(definition) > 0: + writeConfig() + + + +userTaskDirs=[] + +def getUserTaskDirs(): + rt=[] + taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*')) + for task in taskdirs: + rt.append(task) + return rt + +def checkAndAdd(file,names,ilist): + if not file.endswith('.h'): + return + match=False + for cmp in names: + #print("##check %s<->%s"%(f.lower(),cmp)) + if file.lower() == cmp: + match=True + if not match: + return + ilist.append(file) +def genereateUserTasks(outfile): + includes=[] + for task in userTaskDirs: + #print("##taskdir=%s"%task) + base=os.path.basename(task) + includeNames=[base.lower()+".h",'gw'+base.lower()+'.h'] + for f in os.listdir(task): + checkAndAdd(f,includeNames,includes) + includeData="" + for i in includes: + print("#task include %s"%i) + includeData+="#include <%s>\n"%i + writeFileIfChanged(outfile,includeData) + +def generateEmbedded(elist,outFile): + content="" + for entry in elist: + content+="EMBED_GZ_FILE(\"%s\",%s,\"%s\");\n"%entry + writeFileIfChanged(outFile,content) + +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" + if (fn.endswith('css')): + return "text/css" + return "application/octet-stream" + + +def getLibs(): + base=os.path.join(basePath(),"lib") + rt=[] + for sd in os.listdir(base): + if sd == '..': + continue + if sd == '.': + continue + fn=os.path.join(base,sd) + if os.path.isdir(fn): + rt.append(sd) + EXTRAS=['generated'] + for e in EXTRAS: + if not e in rt: + rt.append(e) + return rt + + + +def joinFiles(target,pattern,dirlist): + flist=[] + for dir in dirlist: + fn=os.path.join(dir,pattern) + if os.path.exists(fn): + flist.append(fn) + current=False + if os.path.exists(target): + current=True + for f in flist: + if not isCurrent(f,target): + current=False + break + if current: + print("%s is up to date"%target) + return + print("creating %s"%target) + with gzip.open(target,"wb") as oh: + for fn in flist: + print("adding %s to %s"%(fn,target)) + with open(fn,"rb") as rh: + shutil.copyfileobj(rh,oh) + + +OWNLIBS=getLibs()+["FS","WiFi"] +GLOBAL_INCLUDES=[] + +def handleDeps(env): + #overwrite the GetProjectConfig + #to inject all our libs + oldGetProjectConfig=env.GetProjectConfig + def GetProjectConfigX(env): + rt=oldGetProjectConfig() + cenv="env:"+env['PIOENV'] + libs=[] + for section,options in rt.as_tuple(): + if section == cenv: + for key,values in options: + if key == 'lib_deps': + libs=values + + mustUpdate=False + for lib in OWNLIBS: + if not lib in libs: + libs.append(lib) + mustUpdate=True + if mustUpdate: + update=[(cenv,[('lib_deps',libs)])] + rt.update(update) + return rt + env.AddMethod(GetProjectConfigX,"GetProjectConfig") + #store the list of all includes after we resolved + #the dependencies for our main project + #we will use them for all compilations afterwards + oldLibBuilder=env.ConfigureProjectLibBuilder + def ConfigureProjectLibBuilderX(env): + global GLOBAL_INCLUDES + project=oldLibBuilder() + #print("##ConfigureProjectLibBuilderX") + #pprint.pprint(project) + if project.depbuilders: + #print("##depbuilders %s"%",".join(map(lambda x: x.path,project.depbuilders))) + for db in project.depbuilders: + idirs=db.get_include_dirs() + for id in idirs: + if not id in GLOBAL_INCLUDES: + GLOBAL_INCLUDES.append(id) + return project + env.AddMethod(ConfigureProjectLibBuilderX,"ConfigureProjectLibBuilder") + def injectIncludes(env,node): + return env.Object( + node, + CPPPATH=env["CPPPATH"]+GLOBAL_INCLUDES + ) + env.AddBuildMiddleware(injectIncludes) + + +def prebuild(env): + global userTaskDirs + print("#prebuild running") + if not checkDir(): + sys.exit(1) + ldf_mode=env.GetProjectOption("lib_ldf_mode") + if ldf_mode == 'off': + print("##ldf off - own dependency handling") + handleDeps(env) + userTaskDirs=getUserTaskDirs() + mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE)) + generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs) + compressFile(mergedConfig,mergedConfig+".gz") + generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False) + generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True) + joinFiles(os.path.join(outPath(),INDEXJS+".gz"),INDEXJS,["web"]+userTaskDirs) + joinFiles(os.path.join(outPath(),INDEXCSS+".gz"),INDEXCSS,["web"]+userTaskDirs) + 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): + compressFile(inFile,ef) + else: + print("#WARNING: infile %s for %s not found"%(inFile,ef)) + generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE)) + genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE)) + generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings) + generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r') + 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) +board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper() +print("Board=#%s#"%board) +print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"]))) +env.Append( + LINKFLAGS=[ "-u", "custom_app_desc" ], + CPPDEFINES=[(board,"1")] +) +#script does not run on clean yet - maybe in the future +env.AddPostAction("clean",cleangenerated) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 8c4ec27..94a420a 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -70,6 +70,9 @@ bool statusBacklightLED = false;// Actual status of flash LED on/off int uvDuration = 0; // Under voltage duration in n x 100ms +RTC_DATA_ATTR uint8_t RTC_lastpage; // Remember last page while deep sleeping + + LedTaskData *ledTaskData=nullptr; void hardwareInit(GwApi *api) @@ -118,6 +121,61 @@ void startLedTask(GwApi *api){ createSpiLedTask(ledTaskData); } +uint8_t getLastPage() { + return RTC_lastpage; +} + +#ifdef BOARD_OBP60S3 +void deepSleep(CommonData &common){ + RTC_lastpage = common.data.actpage - 1; + // Switch off all power lines + setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off + setFlashLED(false); // Flash LED Off + buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms + // Shutdown EInk display + getdisplay().setFullWindow(); // Set full Refresh + getdisplay().fillScreen(common.bgcolor); // Clear screen + getdisplay().setTextColor(common.fgcolor); + getdisplay().setFont(&Ubuntu_Bold20pt7b); + getdisplay().setCursor(85, 150); + getdisplay().print("Sleep Mode"); + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(65, 175); + getdisplay().print("To wake up press key and wait 5s"); + getdisplay().nextPage(); // Update display contents + getdisplay().powerOff(); // Display power off + setPortPin(OBP_POWER_50, false); // Power off ePaper display + // Stop system + esp_deep_sleep_start(); // Deep Sleep with weakup via touch pin +} +#endif +#ifdef BOARD_OBP40S3 +// Deep sleep funktion +void deepSleep(CommonData &common){ + RTC_lastpage = common.data.actpage - 1; + // Switch off all power lines + setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off + setFlashLED(false); // Flash LED Off + // Shutdown EInk display + getdisplay().setFullWindow(); // Set full Refresh + //getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().fillScreen(common.bgcolor); // Clear screen + getdisplay().setTextColor(common.fgcolor); + getdisplay().setFont(&Ubuntu_Bold20pt7b); + getdisplay().setCursor(85, 150); + getdisplay().print("Sleep Mode"); + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(65, 175); + getdisplay().print("To wake up press wheel and wait 5s"); + getdisplay().nextPage(); // Partial update + getdisplay().powerOff(); // Display power off + setPortPin(OBP_POWER_EPD, false); // Power off ePaper display + setPortPin(OBP_POWER_SD, false); // Power off SD card + // Stop system + esp_deep_sleep_start(); // Deep Sleep with weakup via GPIO pin +} +#endif + // Valid colors see hue Color colorMapping(const String &colorString){ Color color = COLOR_RED; @@ -334,6 +392,25 @@ void displayHeader(CommonData &commonData, GwApi::BoatValue *date, GwApi::BoatVa getdisplay().drawXBitmap(166, 1, swipe_bits, swipe_width, swipe_height, commonData.fgcolor); } #endif +#ifdef LIPO_ACCU_1200 + if (commonData.data.BatteryChargeStatus == 1) { + getdisplay().drawXBitmap(170, 1, battery_loading_bits, battery_width, battery_height, commonData.fgcolor); + } else { +#ifdef VOLTAGE_SENSOR + if (commonData.data.batteryLevelLiPo < 10) { + getdisplay().drawXBitmap(170, 1, battery_0_bits, battery_width, battery_height, commonData.fgcolor); + } else if (commonData.data.batteryLevelLiPo < 25) { + getdisplay().drawXBitmap(170, 1, battery_25_bits, battery_width, battery_height, commonData.fgcolor); + } else if (commonData.data.batteryLevelLiPo < 50) { + getdisplay().drawXBitmap(170, 1, battery_50_bits, battery_width, battery_height, commonData.fgcolor); + } else if (commonData.data.batteryLevelLiPo < 75) { + getdisplay().drawXBitmap(170, 1, battery_75_bits, battery_width, battery_height, commonData.fgcolor); + } else { + getdisplay().drawXBitmap(170, 1, battery_100_bits, battery_width, battery_height, commonData.fgcolor); + } +#endif // VOLTAGE_SENSOR + } +#endif // LIPO_ACCU_1200 // Heartbeat as dot getdisplay().setTextColor(commonData.fgcolor); diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index fa80cc9..4118623 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -64,6 +64,10 @@ Point rotatePoint(const Point& origin, const Point& p, double angle); std::vector rotatePoints(const Point& origin, const std::vector& pts, double angle); void fillPoly4(const std::vector& p4, uint16_t color); +void deepSleep(CommonData &common); + +uint8_t getLastPage(); + void hardwareInit(GwApi *api); void setPortPin(uint pin, bool value); // Set port pin for extension port @@ -96,8 +100,8 @@ void displayFooter(CommonData &commonData); SunData calcSunsetSunrise(GwApi *api, double time, double date, double latitude, double longitude, double timezone); // Calulate sunset and sunrise void batteryGraphic(uint x, uint y, float percent, int pcolor, int bcolor); // Battery graphic with fill level -void solarGraphic(uint x, uint y, int pcolor, int bcolor); // Solar graphic with fill level -void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic with fill level +void solarGraphic(uint x, uint y, int pcolor, int bcolor); // Solar graphic +void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic void startLedTask(GwApi *api); void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request); @@ -156,6 +160,46 @@ static std::map iconmap = { {"AP", ap_bits} }; +// Battery +#define battery_width 24 +#define battery_height 16 + +static unsigned char battery_0_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0x03, 0x00, 0x78, 0x03, 0x00, 0xf8, 0x03, 0x00, 0xd8, + 0x03, 0x00, 0xd8, 0x03, 0x00, 0xd8, 0x03, 0x00, 0xf8, 0x03, 0x00, 0x78, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_25_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0x3b, 0x00, 0x78, 0x3b, 0x00, 0xf8, 0x3b, 0x00, 0xd8, + 0x3b, 0x00, 0xd8, 0x3b, 0x00, 0xd8, 0x3b, 0x00, 0xf8, 0x3b, 0x00, 0x78, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_50_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0xbb, 0x03, 0x78, 0xbb, 0x03, 0xf8, 0xbb, 0x03, 0xd8, + 0xbb, 0x03, 0xd8, 0xbb, 0x03, 0xd8, 0xbb, 0x03, 0xf8, 0xbb, 0x03, 0x78, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_75_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0xbb, 0x3b, 0x78, 0xbb, 0x3b, 0xf8, 0xbb, 0x3b, 0xd8, + 0xbb, 0x3b, 0xd8, 0xbb, 0x3b, 0xd8, 0xbb, 0x3b, 0xf8, 0xbb, 0x3b, 0x78, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_100_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x1f, + 0x03, 0x00, 0x18, 0xbb, 0xbb, 0x7b, 0xbb, 0xbb, 0xfb, 0xbb, 0xbb, 0xdb, + 0xbb, 0xbb, 0xdb, 0xbb, 0xbb, 0xdb, 0xbb, 0xbb, 0xfb, 0xbb, 0xbb, 0x7b, + 0x03, 0x00, 0x18, 0xff, 0xff, 0x1f, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0x00 }; + +static unsigned char battery_loading_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xfe, 0xe4, 0x0f, 0xff, 0xec, 0x1f, + 0x03, 0x08, 0x18, 0x03, 0x18, 0x78, 0x03, 0x30, 0xf8, 0x83, 0x3f, 0xd8, + 0x03, 0x7f, 0xd8, 0x03, 0x03, 0xd8, 0x03, 0x06, 0xf8, 0x03, 0x04, 0x78, + 0x03, 0x0c, 0x18, 0xff, 0xcb, 0x1f, 0xfe, 0xd3, 0x0f, 0x00, 0x10, 0x00 }; + // Other symbols #define swipe_width 24 #define swipe_height 16 diff --git a/lib/obp60task/OBP60Keypad.h b/lib/obp60task/OBP60Keypad.h index 122355c..eafe3a2 100644 --- a/lib/obp60task/OBP60Keypad.h +++ b/lib/obp60task/OBP60Keypad.h @@ -60,7 +60,7 @@ void initKeys(CommonData &commonData) { #ifdef HARDWARE_V21 // Keypad functions for original OBP60 hardware - int readKeypad(GwLog* logger, uint thSensitivity) { + int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) { // Touch sensor values // 35000 - Not touched @@ -261,7 +261,7 @@ void initKeys(CommonData &commonData) { } // Keypad functions for OBP60 clone (thSensitivity is inactiv) - int readKeypad(GwLog* logger, uint thSensitivity) { + int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) { pinMode(UP, INPUT); pinMode(DOWN, INPUT); pinMode(CONF, INPUT); @@ -277,9 +277,13 @@ void initKeys(CommonData &commonData) { starttime = millis(); // Start key pressed keycodeold = keycode; } - // If key pressed longer than 200ms - if(millis() > starttime + 200 && keycode == keycodeold) { - keystatus = keycode; + // If key pressed longer than 100ms + if(millis() > starttime + 100 && keycode == keycodeold) { + if (use_syspage and keycode == 3) { + keystatus = 12; + } else { + keystatus = keycode; + } // Copy keycode keycodeold = keycode; while(readSensorpads() > 0){} // Wait for pad release diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 99071ab..cbbfa15 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -88,8 +88,16 @@ void sensorTask(void *param){ double voffset = (api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asString()).toFloat(); double vslope = (api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asString()).toFloat(); if(String(powsensor1) == "off"){ - sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 - sensors.batteryVoltage = sensors.batteryVoltage * vslope + voffset; // Calibration + #ifdef VOLTAGE_SENSOR + float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + #else + float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + #endif + sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration + #ifdef LIPO_ACCU_1200 + sensors.BatteryChargeStatus = 0; // Set to discharging + sensors.batteryLevelLiPo = 0; // Level 0...100% + #endif sensors.batteryCurrent = 0; sensors.batteryPower = 0; // Fill average arrays with start values @@ -459,19 +467,49 @@ void sensorTask(void *param){ // Send supply voltage value all 1s if(millis() > starttime5 + 1000 && String(powsensor1) == "off"){ starttime5 = millis(); - sensors.batteryVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 - sensors.batteryVoltage = sensors.batteryVoltage * vslope + voffset; // Calibration + #ifdef VOLTAGE_SENSOR + float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + #else + float rawVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + #endif + sensors.batteryVoltage = rawVoltage * vslope + voffset; // Calibration // Save new data in average array batV.reading(int(sensors.batteryVoltage * 100)); // Calculate the average values for different time lines from integer values sensors.batteryVoltage10 = batV.getAvg(10) / 100.0; sensors.batteryVoltage60 = batV.getAvg(60) / 100.0; sensors.batteryVoltage300 = batV.getAvg(300) / 100.0; + #if defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR + // Polynomfit for LiPo capacity calculation for 3,7V LiPo accus, 0...100% + sensors.batteryLevelLiPo = sensors.batteryVoltage60 * 203.8312 -738.1635; + // Limiter + if(sensors.batteryLevelLiPo > 100){ + sensors.batteryLevelLiPo = 100; + } + if(sensors.batteryLevelLiPo < 0){ + sensors.batteryLevelLiPo = 0; + } + // Charging detection + float deltaV = sensors.batteryVoltage - sensors.batteryVoltage10; + if(deltaV > 0.045){ + sensors.BatteryChargeStatus = 1; // Charging active + } + if(deltaV < -0.04){ + sensors.BatteryChargeStatus = 0; // Discharging + } + // Send to NMEA200 bus as instance 10 + if(!isnan(sensors.batteryVoltage)){ + SetN2kDCBatStatus(N2kMsg, 10, sensors.batteryVoltage, N2kDoubleNA, N2kDoubleNA, 0); + api->sendN2kMessage(N2kMsg); + } + #endif + #ifdef BOARD_OBP60S3 // Send to NMEA200 bus if(!isnan(sensors.batteryVoltage)){ SetN2kDCBatStatus(N2kMsg, 0, sensors.batteryVoltage, N2kDoubleNA, N2kDoubleNA, 1); api->sendN2kMessage(N2kMsg); } + #endif } // Send data from environment sensor all 2s diff --git a/lib/obp60task/PageBME280.cpp b/lib/obp60task/PageBME280.cpp index d731005..9bf0df2 100644 --- a/lib/obp60task/PageBME280.cpp +++ b/lib/obp60task/PageBME280.cpp @@ -48,7 +48,7 @@ class PageBME280 : public Page value1 = 23.0 + float(random(0, 10)) / 10.0; } // Display data when sensor activated - if((String(useenvsensor) == "BME280") or (String(useenvsensor) == "BMP280")){ + if((useenvsensor == "BME280") or (useenvsensor == "BMP280") or (useenvsensor == "BMP180")){ svalue1 = String(value1, 1); // Formatted value as string including unit conversion and switching decimal places } else{ @@ -66,7 +66,7 @@ class PageBME280 : public Page value2 = 43 + float(random(0, 4)); } // Display data when sensor activated - if(String(useenvsensor) == "BME280"){ + if(useenvsensor == "BME280"){ svalue2 = String(value2, 0); // Formatted value as string including unit conversion and switching decimal places } else{ @@ -84,7 +84,7 @@ class PageBME280 : public Page value3 = 1006 + float(random(0, 5)); } // Display data when sensor activated - if((String(useenvsensor) == "BME280") or (String(useenvsensor) == "BMP280")){ + if((useenvsensor == "BME280") or (useenvsensor == "BMP280") or (useenvsensor == "BMP180")){ svalue3 = String(value3 / 100, 1); // Formatted value as string including unit conversion and switching decimal places } else{ diff --git a/lib/obp60task/PageSystem.cpp b/lib/obp60task/PageSystem.cpp index 7beb13d..7f601a1 100644 --- a/lib/obp60task/PageSystem.cpp +++ b/lib/obp60task/PageSystem.cpp @@ -4,6 +4,7 @@ #include "OBP60Extensions.h" #include "images/logo64.xbm" #include +#include "qrcode.h" #define STRINGIZE_IMPL(x) #x #define STRINGIZE(x) STRINGIZE_IMPL(x) @@ -48,7 +49,7 @@ public: commonData->keydata[0].label = "EXIT"; commonData->keydata[1].label = "MODE"; commonData->keydata[2].label = ""; - commonData->keydata[3].label = ""; + commonData->keydata[3].label = "RST"; commonData->keydata[4].label = "STBY"; commonData->keydata[5].label = "ILUM"; } @@ -66,18 +67,64 @@ public: if (hasFRAM) fram.write(FRAM_SYSTEM_MODE, mode); return 0; } - // grab cursor keys to disable page navigation - if (key == 3 or key == 4) { +#ifdef BOARD_OBP60S3 + // grab cursor key to disable page navigation + if (key == 3) { return 0; } + // soft reset + if (key == 4) { + ESP.restart(); + } + // standby / deep sleep + if (key == 5) { + deepSleep(*commonData); + } // Code for keylock if (key == 11) { commonData->keylock = !commonData->keylock; return 0; } +#endif +#ifdef BOARD_OBP40S3 + // grab cursor keys to disable page navigation + if (key == 9 or key == 10) { + return 0; + } + // standby / deep sleep + if (key == 12) { + deepSleep(*commonData); + } +#endif return key; } + void displayBarcode(String serialno, uint16_t x, uint16_t y, uint16_t s) { + // Barcode with serial number + // x, y is top left corner + // s is pixel size of a single box + QRCode qrcode; + uint8_t qrcodeData[qrcode_getBufferSize(4)]; + #ifdef BOARD_OBP40S3 + String prefix = "OBP40:SN:"; + #endif + #ifdef BOARD_OBP60S3 + String prefix = "OBP60:SN:"; + #endif + qrcode_initText(&qrcode, qrcodeData, 4, 0, (prefix + serialno).c_str()); + int16_t x0 = x; + for (uint8_t j = 0; j < qrcode.size; j++) { + for (uint8_t i = 0; i < qrcode.size; i++) { + if (qrcode_getModule(&qrcode, i, j)) { + getdisplay().fillRect(x, y, s, s, commonData->fgcolor); + } + x += s; + } + y += s; + x = x0; + } + } + virtual void displayPage(PageData &pageData){ GwConfigHandler *config = commonData->config; GwLog *logger = commonData->logger; @@ -104,66 +151,88 @@ public: if (mode == 'N') { getdisplay().setFont(&Ubuntu_Bold12pt7b); - getdisplay().setCursor(20, 50); + getdisplay().setCursor(8, 50); getdisplay().print("System Information"); getdisplay().drawXBitmap(320, 25, logo64_bits, logo64_width, logo64_height, commonData->fgcolor); getdisplay().setFont(&Ubuntu_Bold8pt7b); - char ssid[23]; - snprintf(ssid, 23, "MCUDEVICE-%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid); - getdisplay().setCursor(20, 70); - getdisplay().print(ssid); - getdisplay().setCursor(20, 100); - getdisplay().print("Press STBY for white page and standby"); + char ssid[13]; + snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid); + displayBarcode(String(ssid), 320, 200, 2); + getdisplay().setCursor(8, 70); + getdisplay().print(String("MUDEVICE-") + String(ssid)); + + getdisplay().setCursor(8, 90); + getdisplay().print("Firmware Version: "); + getdisplay().print(VERSINFO); + + getdisplay().setCursor(8, 265); + #ifdef BOARD_OBP60S3 + getdisplay().print("Press STBY to enter deep sleep mode"); + #endif + #ifdef BOARD_OBP40S3 + getdisplay().print("Press wheel to enter deep sleep mode"); + #endif getdisplay().setCursor(2, y0); getdisplay().print("Simulation:"); getdisplay().setCursor(120, y0); getdisplay().print(simulation ? "on" : "off"); - getdisplay().setCursor(202, y0); - getdisplay().print("Wifi:"); - getdisplay().setCursor(300, y0); - getdisplay().print(commonData->status.wifiApOn ? "On" : "Off"); - getdisplay().setCursor(2, y0 + 16); getdisplay().print("Environment:"); getdisplay().setCursor(120, y0 + 16); getdisplay().print(env_module); + // total RAM free + int Heap_free = esp_get_free_heap_size(); + getdisplay().setCursor(202, y0); + getdisplay().print("Total free:"); + getdisplay().setCursor(300, y0); + getdisplay().print(String(Heap_free)); + getdisplay().setCursor(2, y0 + 32); getdisplay().print("Buzzer:"); getdisplay().setCursor(120, y0 + 32); getdisplay().print(buzzer_mode); - getdisplay().setCursor(2, y0 + 48); + // RAM free for task + int RAM_free = uxTaskGetStackHighWaterMark(NULL); + getdisplay().setCursor(202, y0 + 16); + getdisplay().print("Task free:"); + getdisplay().setCursor(300, y0 + 16); + getdisplay().print(String(RAM_free)); + + // FRAM available / status + getdisplay().setCursor(202, y0 + 32); + getdisplay().print("FRAM:"); + getdisplay().setCursor(300, y0 + 32); + getdisplay().print(hasFRAM ? "available" : "not found"); + + getdisplay().setCursor(202, y0 + 64); getdisplay().print("CPU speed:"); - getdisplay().setCursor(120, y0 + 48); + getdisplay().setCursor(300, y0 + 64); getdisplay().print(cpuspeed); getdisplay().print(" / "); int cpu_freq = esp_clk_cpu_freq() / 1000000; getdisplay().print(String(cpu_freq)); getdisplay().setCursor(2, y0 + 64); - getdisplay().print("RTC:"); - getdisplay().setCursor(120, y0 + 64); - getdisplay().print(rtc_module); - - getdisplay().setCursor(202, y0 + 64); getdisplay().print("GPS:"); - getdisplay().setCursor(300, y0 + 64); + getdisplay().setCursor(120, y0 + 64); getdisplay().print(gps_module); getdisplay().setCursor(2, y0 + 80); - getdisplay().print("FRAM:"); + getdisplay().print("RTC:"); getdisplay().setCursor(120, y0 + 80); - getdisplay().print(hasFRAM ? "available" : "not found"); + getdisplay().print(rtc_module); - getdisplay().setCursor(2, y0 + 120); - getdisplay().print("Firmware Version: "); - getdisplay().print(VERSINFO); + getdisplay().setCursor(2, y0 + 96); + getdisplay().print("Wifi:"); + getdisplay().setCursor(120, y0 + 96); + getdisplay().print(commonData->status.wifiApOn ? "On" : "Off"); } else { diff --git a/lib/obp60task/PageVoltage.cpp b/lib/obp60task/PageVoltage.cpp index d05c903..c98bc96 100644 --- a/lib/obp60task/PageVoltage.cpp +++ b/lib/obp60task/PageVoltage.cpp @@ -205,6 +205,18 @@ public: getdisplay().setCursor(20, 100); getdisplay().print(name1); // Value name + #if defined BOARD_OBP40S3 && defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR + // Show charge status + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(185, 100); + if(commonData->data.BatteryChargeStatus == true){ + getdisplay().print("Charge"); + } + else{ + getdisplay().print("Discharge"); + } + #endif + // Show unit getdisplay().setFont(&Ubuntu_Bold20pt7b); getdisplay().setCursor(270, 100); @@ -213,7 +225,12 @@ public: // Show battery type getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(295, 100); + #ifdef BOARD_OBP60S3 getdisplay().print(batType); + #endif + #if defined BOARD_OBP40S3 && defined LIPO_ACCU_1200 && defined VOLTAGE_SENSOR + getdisplay().print("LiPo"); + #endif // Show average settings printAvg(average, 320, 84, true); diff --git a/lib/obp60task/PageWhite.cpp b/lib/obp60task/PageWhite.cpp index a2b6737..e40a3ac 100644 --- a/lib/obp60task/PageWhite.cpp +++ b/lib/obp60task/PageWhite.cpp @@ -13,6 +13,7 @@ public: PageWhite(CommonData &common){ commonData = &common; common.logger->logDebug(GwLog::LOG,"Instantiate PageWhite"); + refreshtime = 15000; } virtual int handleKey(int key) { @@ -53,7 +54,11 @@ public: int bgcolor = GxEPD_WHITE; // Set display in partial refresh mode - getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + if (mode == 'W') { + getdisplay().setFullWindow(); + } else { + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + } if (mode == 'L') { getdisplay().drawBitmap(0, 0, gImage_Logo_OBP_400x300_sw, getdisplay().width(), getdisplay().height(), commonData->fgcolor); @@ -62,7 +67,10 @@ public: } // Update display - getdisplay().nextPage(); // Partial update (fast) + getdisplay().nextPage(); + if (mode == 'W') { + getdisplay().hibernate(); + } }; }; diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index cc4c45b..d511aee 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -31,6 +31,8 @@ typedef struct{ double batteryVoltage300 = 0; // Sliding average over 300 values double batteryCurrent300 = 0; double batteryPower300 = 0; + double batteryLevelLiPo = 0; // Battery level for OBP40 LiPo accu + int BatteryChargeStatus = 0; // LiPo charge status: 0 = discharge, 1 = loading activ double solarVoltage = 0; double solarCurrent = 0; double solarPower = 0; @@ -100,6 +102,7 @@ class Page{ protected: CommonData *commonData; public: + int refreshtime = 1000; virtual void displayPage(PageData &pageData)=0; virtual void displayNew(PageData &pageData){} virtual void setupKeys() { diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index d18533c..888c746 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -609,10 +609,10 @@ "label": "Undervoltage", "type": "boolean", "default": "false", - "description": "Switch off device if voltage drops below 9V [on|off]", + "description": "Switch off device if LiPo voltage drops below 3.65V [on|off]", "category": "OBP40 Hardware", "capabilities": { - "obp40": "false" + "obp40":"true" } }, { @@ -913,6 +913,17 @@ "obp40": "true" } }, + { + "name": "systemPage", + "label": "System Page", + "type": "boolean", + "default": "false", + "description": "Use wheel button for system page or direct deep sleep mode", + "category":"OBP40 Pages", + "capabilities": { + "obp40": "true" + } + }, { "name": "imageFormat", "label": "Screenshot Format", diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 662942e..cad4867 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -9,7 +9,7 @@ #include // NMEA0183 #include #include -#include // GxEPD2 lib for black 6 white E-Ink displays +#include // GxEPD2 lib for b/w E-Ink displays #include "OBP60Extensions.h" // Functions lib for extension board #include "OBP60Keypad.h" // Functions for keypad @@ -78,7 +78,7 @@ void OBP60Init(GwApi *api){ } #ifdef BOARD_OBP40S3 - //String sdcard = config->getConfigItem(config->useSDCard, true)->asString(); +// String sdcard = config->getConfigItem(config->useSDCard, true)->asString(); String sdcard = "on"; if (sdcard == "on") { SPIClass SD_SPI = SPIClass(HSPI); @@ -117,6 +117,16 @@ void OBP60Init(GwApi *api){ } #endif + #ifdef BOARD_OBP60S3 + touchSleepWakeUpEnable(TP1, 45); // TODO sensitivity should be configurable via web interface + touchSleepWakeUpEnable(TP2, 45); + touchSleepWakeUpEnable(TP3, 45); + touchSleepWakeUpEnable(TP4, 45); + touchSleepWakeUpEnable(TP5, 45); + touchSleepWakeUpEnable(TP6, 45); + esp_sleep_enable_touchpad_wakeup(); + #endif + // Get CPU speed int freq = getCpuFrequencyMhz(); api->getLogger()->logDebug(GwLog::LOG,"CPU speed at boot: %i MHz", freq); @@ -159,6 +169,7 @@ typedef struct { GwLog* logger = NULL; // GwApi* api = NULL; uint sensitivity = 100; + bool use_syspage = true; } MyData; // Keyboard Task @@ -170,7 +181,7 @@ void keyboardTask(void *param){ // Loop for keyboard task while (true){ - keycode = readKeypad(data->logger, data->sensitivity); + keycode = readKeypad(data->logger, data->sensitivity, data->use_syspage); //send a key event if(keycode != 0){ xQueueSend(data->queue, &keycode, 0); @@ -302,9 +313,36 @@ void underVoltageDetection(GwApi *api, CommonData &common){ float vslope = uint(api->getConfig()->getConfigItem(api->getConfig()->vSlope,true)->asFloat()); float voffset = uint(api->getConfig()->getConfigItem(api->getConfig()->vOffset,true)->asFloat()); // Read supply voltage - float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // V = 1/20 * Vin - actVoltage = actVoltage * vslope + voffset; - if(actVoltage < MIN_VOLTAGE){ + #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 + float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.53) * 2; // Vin = 1/2 for OBP40 + float minVoltage = 3.65; // Absolut minimum volatge for 3,7V LiPo accu + #else + float actVoltage = (float(analogRead(OBP_ANALOG0)) * 3.3 / 4096 + 0.17) * 20; // Vin = 1/20 for OBP60 + float minVoltage = MIN_VOLTAGE; + #endif + float corrVoltage = actVoltage * vslope + voffset; // Calibration + if(corrVoltage < minVoltage){ + #if defined VOLTAGE_SENSOR && defined LIPO_ACCU_1200 + // Switch off all power lines + setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off + setFlashLED(false); // Flash LED Off + buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms + // Shutdown EInk display + getdisplay().setFullWindow(); // Set full Refresh + //getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().fillScreen(common.bgcolor);// Clear screen + getdisplay().setTextColor(common.fgcolor); + getdisplay().setFont(&Ubuntu_Bold20pt7b); + getdisplay().setCursor(65, 150); + getdisplay().print("Undervoltage"); + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(65, 175); + getdisplay().print("Charge battery and restart"); + getdisplay().nextPage(); // Partial update + getdisplay().powerOff(); // Display power off + setPortPin(OBP_POWER_EPD, false); // Power off ePaper display + setPortPin(OBP_POWER_SD, false); // Power off SD card + #else // Switch off all power lines setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off setFlashLED(false); // Flash LED Off @@ -317,8 +355,12 @@ void underVoltageDetection(GwApi *api, CommonData &common){ getdisplay().setFont(&Ubuntu_Bold20pt7b); getdisplay().setCursor(65, 150); getdisplay().print("Undervoltage"); + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(65, 175); + getdisplay().print("To wake up repower system"); getdisplay().nextPage(); // Partial update getdisplay().powerOff(); // Display power off + #endif // Stop system while(true){ esp_deep_sleep_start(); // Deep Sleep without weakup. Weakup only after power cycle (restart). @@ -326,36 +368,6 @@ void underVoltageDetection(GwApi *api, CommonData &common){ } } -#ifdef BOARD_OBP40S3 -// Deep sleep funktion -void deepSleep(CommonData &common){ - // Switch off all power lines - setPortPin(OBP_BACKLIGHT_LED, false); // Backlight Off - setFlashLED(false); // Flash LED Off - buzzer(TONE4, 20); // Buzzer tone 4kHz 20ms - // Shutdown EInk display - getdisplay().setFullWindow(); // Set full Refresh - //getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update - getdisplay().fillScreen(common.bgcolor); // Clear screen - getdisplay().setTextColor(common.fgcolor); - getdisplay().setFont(&Ubuntu_Bold20pt7b); - getdisplay().setCursor(85, 150); - getdisplay().print("Sleep Mode"); - getdisplay().setFont(&Ubuntu_Bold8pt7b); - getdisplay().setCursor(65, 175); - getdisplay().print("For wakeup press wheel and wait 5s"); - getdisplay().nextPage(); // Partial update - getdisplay().powerOff(); // Display power off - setPortPin(OBP_POWER_EPD, false); // Power off ePaper display - setPortPin(OBP_POWER_SD, false); // Power off SD card - // Stop system - while(true){ - esp_deep_sleep_start(); // Deep Sleep with weakup via GPIO pin - } - -} -#endif - // OBP60 Task //#################################################################################### void OBP60Task(GwApi *api){ @@ -400,6 +412,10 @@ void OBP60Task(GwApi *api){ bool refreshmode = api->getConfig()->getConfigItem(api->getConfig()->refresh,true)->asBoolean(); String fastrefresh = api->getConfig()->getConfigItem(api->getConfig()->fastRefresh,true)->asString(); uint fullrefreshtime = uint(api->getConfig()->getConfigItem(api->getConfig()->fullRefreshTime,true)->asInt()); + #ifdef BOARD_OBP40S3 +// bool syspage_enabled = config->getBool(config->systemPage); + bool syspage_enabled = true; + #endif #ifdef DISPLAY_GDEY042T81 getdisplay().init(115200, true, 2, false); // Use this for Waveshare boards with "clever" reset circuit, 2ms reset pulse @@ -440,6 +456,26 @@ void OBP60Task(GwApi *api){ PageStruct pages[MAX_PAGE_NUMBER]; // Set start page int pageNumber = int(api->getConfig()->getConfigItem(api->getConfig()->startPage,true)->asInt()) - 1; + + LOG_DEBUG(GwLog::LOG,"Checking wakeup..."); +#ifdef BOARD_OBP60S3 + if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TOUCHPAD) { + LOG_DEBUG(GwLog::LOG,"Wake up by touch pad %d",esp_sleep_get_touchpad_wakeup_status()); + pageNumber = getLastPage(); + } else { + LOG_DEBUG(GwLog::LOG,"Other wakeup reason"); + } +#endif +#ifdef BOARD_OBP40S3 + if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0) { + LOG_DEBUG(GwLog::LOG,"Wake up by key"); + pageNumber = getLastPage(); + } else { + LOG_DEBUG(GwLog::LOG,"Other wakeup reason"); + } +#endif + LOG_DEBUG(GwLog::LOG,"...done"); + int lastPage=pageNumber; BoatValueList boatValues; //all the boat values for the api query @@ -501,6 +537,9 @@ void OBP60Task(GwApi *api){ allParameters.page0=3; allParameters.queue=xQueueCreate(10,sizeof(int)); allParameters.sensitivity= api->getConfig()->getInt(GwConfigDefinitions::tSensitivity); + #ifdef BOARD_OBP40S3 + allParameters.use_syspage = syspage_enabled; + #endif xTaskCreate(keyboardTask,"keyboard",2000,&allParameters,configMAX_PRIORITIES-1,NULL); SharedData *shared=new SharedData(api); createSensorTask(shared); @@ -550,8 +589,10 @@ void OBP60Task(GwApi *api){ //#################################################################################### bool systemPage = false; + Page *currentPage; while (true){ delay(100); // Delay 100ms (loop time) + bool keypressed = false; // Undervoltage detection if(uvoltage == true){ @@ -593,12 +634,13 @@ void OBP60Task(GwApi *api){ int keyboardMessage=0; while (xQueueReceive(allParameters.queue,&keyboardMessage,0)){ LOG_DEBUG(GwLog::LOG,"new key from keyboard %d",keyboardMessage); + keypressed = true; - Page *currentPage; - if (keyboardMessage == 12) { + if (keyboardMessage == 12 and !systemPage) { LOG_DEBUG(GwLog::LOG, "Calling system page"); systemPage = true; // System page is out of band syspage->setupKeys(); + keyboardMessage = 0; } else { currentPage = pages[pageNumber].page; @@ -606,6 +648,7 @@ void OBP60Task(GwApi *api){ // exit system mode with exit key number 1 systemPage = false; currentPage->setupKeys(); + keyboardMessage = 0; } } if (systemPage) { @@ -626,7 +669,7 @@ void OBP60Task(GwApi *api){ } #ifdef BOARD_OBP40S3 // #3 Deep sleep mode for OBP40 - if (keyboardMessage == 3){ + if ((keyboardMessage == 3) and !syspage_enabled){ deepSleep(commonData); } #endif @@ -725,9 +768,17 @@ void OBP60Task(GwApi *api){ } } - // Refresh display data all 1s - if(millis() > starttime3 + 1000){ + // Refresh display data, default all 1s + currentPage = pages[pageNumber].page; + int pagetime = 1000; + if ((lastPage == pageNumber) and (!keypressed)) { + // same page we use page defined time + pagetime = currentPage->refreshtime; + } + if(millis() > starttime3 + pagetime){ + LOG_DEBUG(GwLog::DEBUG,"Page with refreshtime=%d", pagetime); starttime3 = millis(); + //refresh data from api api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); api->getStatus(commonData.status); @@ -749,7 +800,6 @@ void OBP60Task(GwApi *api){ syspage->displayPage(sysparams); } else { - Page *currentPage = pages[pageNumber].page; if (currentPage == NULL){ LOG_DEBUG(GwLog::ERROR,"page number %d not found", pageNumber); // Error handling for missing page diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index da78f39..2259b61 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -2,7 +2,10 @@ #if you want a pio run to only build #your special environments you can set this here #by uncommenting the next line -default_envs = obp60_s3 + +default_envs = + obp60_s3 + obp40_s3 [env:obp60_s3] platform = espressif32@6.8.1 @@ -91,6 +94,8 @@ build_flags= -D DISABLE_DIAGNOSTIC_OUTPUT #Disable diagnostic output for GxEPD2 lib -D BOARD_OBP40S3 #Board OBP40 V1.0 with ESP32S3 SKU:DIE07300S (CrowPanel 4.2) -D DISPLAY_GDEY042T81 #new E-Ink display from Waveshare, R10 2.2 ohm + -D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh + -D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors ${env.build_flags} upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27