Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
3241ef2e20
|
@ -0,0 +1,77 @@
|
||||||
|
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
||||||
|
# See: https://circleci.com/docs/configuration-reference
|
||||||
|
version: 2.1
|
||||||
|
parameters:
|
||||||
|
run_build:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
environment:
|
||||||
|
type: string
|
||||||
|
default: "m5stack-atom"
|
||||||
|
build_flags:
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
config:
|
||||||
|
type: string
|
||||||
|
default: "{}"
|
||||||
|
suffix:
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
orbs:
|
||||||
|
python: circleci/python@1.4.0
|
||||||
|
|
||||||
|
# Define a job to be invoked later in a workflow.
|
||||||
|
# See: https://circleci.com/docs/configuration-reference/#jobs
|
||||||
|
jobs:
|
||||||
|
pio-build:
|
||||||
|
executor: python/default
|
||||||
|
# Add steps to the job
|
||||||
|
# See: https://circleci.com/docs/configuration-reference/#steps
|
||||||
|
environment:
|
||||||
|
PLATFORMIO_BUILD_FLAGS: << pipeline.parameters.build_flags >>
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: "platformio install"
|
||||||
|
command: "pip install --upgrade platformio"
|
||||||
|
- run:
|
||||||
|
name: "build"
|
||||||
|
command: "pio run -e << pipeline.parameters.environment >>"
|
||||||
|
- run:
|
||||||
|
name: "save config"
|
||||||
|
working_directory: ".pio/build/<< pipeline.parameters.environment >>"
|
||||||
|
command: "echo '<< pipeline.parameters.config >>' > config.json"
|
||||||
|
- run:
|
||||||
|
name: "save build config"
|
||||||
|
working_directory: ".pio/build/<< pipeline.parameters.environment >>"
|
||||||
|
command: "echo 'GIT_SHA=\"<< pipeline.git.revision >>\" PLATFORMIO_BUILD_FLAGS=\"<< pipeline.parameters.build_flags >>\" pio run -e << pipeline.parameters.environment >> ' > buildconfig.txt"
|
||||||
|
- run:
|
||||||
|
name: "rename"
|
||||||
|
working_directory: ".pio/build/<< pipeline.parameters.environment >>"
|
||||||
|
command: "mv firmware.bin << pipeline.parameters.environment >><< pipeline.parameters.suffix >>-update.bin"
|
||||||
|
- when:
|
||||||
|
condition:
|
||||||
|
not:
|
||||||
|
equal: [ << pipeline.parameters.suffix >> ,""]
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: "rename2"
|
||||||
|
working_directory: ".pio/build/<< pipeline.parameters.environment >>"
|
||||||
|
command: "mv << pipeline.parameters.environment >>-all.bin << pipeline.parameters.environment >><< pipeline.parameters.suffix >>-all.bin"
|
||||||
|
- run:
|
||||||
|
name: "compress"
|
||||||
|
working_directory: ".pio/build/<< pipeline.parameters.environment >>"
|
||||||
|
command: "zip << pipeline.parameters.environment >><< pipeline.parameters.suffix >>.zip << pipeline.parameters.environment >>*.bin config.json buildconfig.txt"
|
||||||
|
- store_artifacts:
|
||||||
|
path: .pio/build/<< pipeline.parameters.environment >>/<< pipeline.parameters.environment >><< pipeline.parameters.suffix >>.zip
|
||||||
|
destination: << pipeline.parameters.environment >><< pipeline.parameters.suffix >>.zip
|
||||||
|
# Orchestrate jobs using workflows
|
||||||
|
# See: https://circleci.com/docs/configuration-reference/#workflows
|
||||||
|
workflows:
|
||||||
|
build-workflow:
|
||||||
|
when: << pipeline.parameters.run_build >>
|
||||||
|
jobs:
|
||||||
|
- pio-build:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /.*/
|
|
@ -7,7 +7,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -5,3 +5,4 @@
|
||||||
.vscode/ipch
|
.vscode/ipch
|
||||||
generated/*
|
generated/*
|
||||||
lib/generated
|
lib/generated
|
||||||
|
webinstall/token.php
|
||||||
|
|
16
Readme.md
16
Readme.md
|
@ -38,6 +38,7 @@ What is included
|
||||||
* a WEB UI to configure the gateway and to show the data that has been received
|
* a WEB UI to configure the gateway and to show the data that has been received
|
||||||
* a USB Actisense to NMEA2000 gateway
|
* a USB Actisense to NMEA2000 gateway
|
||||||
* a NMEA2000 to USB Actisense gateway
|
* a NMEA2000 to USB Actisense gateway
|
||||||
|
* starting with 201311xx some I2C Sensors
|
||||||
|
|
||||||
For the details of the mapped PGNs and NMEA sentences refer to [Conversions](doc/Conversions.pdf).
|
For the details of the mapped PGNs and NMEA sentences refer to [Conversions](doc/Conversions.pdf).
|
||||||
|
|
||||||
|
@ -46,12 +47,16 @@ Hardware
|
||||||
The software is prepared to run on different kinds of ESP32 based modules and accessoirs. For some of them prebuild binaries are available that only need to be flashed, others would require to add some definitions of the used PINs and features and to build the binary.
|
The software is prepared to run on different kinds of ESP32 based modules and accessoirs. For some of them prebuild binaries are available that only need to be flashed, others would require to add some definitions of the used PINs and features and to build the binary.
|
||||||
For the list of hardware set ups refer to [Hardware](doc/Hardware.md).
|
For the list of hardware set ups refer to [Hardware](doc/Hardware.md).
|
||||||
|
|
||||||
|
There is a couple of prebuild binaries that you can directly flash to your device. For other combinations of hardware there is an [online build service](doc/BuildService.md) that will allow you to select your hardware and trigger a build.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
In the [release section](../../releases) you can find a couple of pre-build binaries.<br>
|
In the [release section](../../releases) you can find a couple of pre-build binaries.<br>
|
||||||
They are devided into binaries for an initial flash (xxx-all.bin) and binaries for updating an existing device (xxx-update.bin).
|
They are devided into binaries for an initial flash (xxx-all.bin) and binaries for updating an existing device (xxx-update.bin).
|
||||||
|
|
||||||
|
For other Hardware refer to the [online build service](https://circleci.com/).
|
||||||
|
|
||||||
Initial Flash
|
Initial Flash
|
||||||
*************
|
*************
|
||||||
|
|
||||||
|
@ -160,6 +165,17 @@ For details refer to the [example description](lib/exampletask/Readme.md).
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
---------
|
---------
|
||||||
|
[20231105](../../releases/tag/20231105)
|
||||||
|
**********
|
||||||
|
* support for ESP32-S3
|
||||||
|
* own [TWAI](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/twai.html) based driver for the NMEA2000 bus
|
||||||
|
* add NMEA2000 node address and status to the status tab
|
||||||
|
* ability to change the AP ip address
|
||||||
|
* [online build service ](doc/BuildService.md) to select the components you need
|
||||||
|
* restructuring of the lib_deps handling (much shorter compile time <br>__Hint__: if this introduces problems for your build, revert back the [lib_ldf_mode](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/platformio.ini#L50) to chain+
|
||||||
|
* integration of a couple of I2C sensors (e.g. M5 ENVIII, BME280)
|
||||||
|
* More functionality for user tasks (counter, interface between tasks, dynamic registration, adding fixed XDR mappings) - refer to the [example description](lib/exampletask/Readme.md)
|
||||||
|
|
||||||
[20230317](../../releases/tag/20230317)
|
[20230317](../../releases/tag/20230317)
|
||||||
**********
|
**********
|
||||||
* correctly convert bar to Pascal in XDR records
|
* correctly convert bar to Pascal in XDR records
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
Online Build Service
|
||||||
|
--------------------
|
||||||
|
As the supported hardware combinations are already a lot and to support the suage of the converter also in your own designs there is an online build service available that will allow you to select the hardware combinations you would like to have and to start a build.
|
||||||
|
|
||||||
|
__Access__
|
||||||
|
|
||||||
|
To access the build service go to [wellenvogel.de](https://www.wellenvogel.net/software/esp32/cibuild.html).
|
||||||
|
To prevent the build service from being misused you need to authenticate with a username and password.
|
||||||
|
* User: ESP32NMEA2K
|
||||||
|
* Pass: esp32ci
|
||||||
|
|
||||||
|
Just consider that the user and password could change in the future - so when bookmarking just always start here and check for the current username and password.
|
||||||
|
|
||||||
|
__Workflow__
|
||||||
|
|
||||||
|
On the page you select the Hardware combination that fits your needs.
|
||||||
|
.
|
||||||
|
|
||||||
|
The selected board type and the build flags will be show at the bottom.
|
||||||
|
Whenever an existing build with the same parameters is found it will be shown and you will be able to download the build results - or directly go to the [WebInstaller](https://www.wellenvogel.net/software/esp32/install.html).
|
||||||
|
If there is no existing build the "Start" button is active and you can trigger a build.
|
||||||
|
|
||||||
|
At the bottom you see the status of the build and you get a link to the [used build service](https://app.circleci.com/pipelines/github/wellenvogel/esp32-nmea2000).
|
||||||
|
|
||||||
|
.
|
||||||
|
|
||||||
|
Once the build is complete you will get the buttons to download the results or directly use them for the web installer.
|
||||||
|
|
||||||
|
__Build Results__
|
||||||
|
|
||||||
|
The build result is always a zip file that contains a flash for the initial installation, an flash for the update installation and the used configuration for the build.
|
||||||
|
This zip file should be stored on your side as the build results will be deleted after 30 days.
|
||||||
|
If you later on would like to update with a new release you can upload your configuration using the "LOADCFG" button (either the zip file or the json file from within the zip).
|
||||||
|
You can also separately store the configuration of your build using the "SAVECFG" button.
|
||||||
|
|
||||||
|
__Hardware__
|
||||||
|
|
||||||
|
Currently a couple of different hardware configurations are supported. For M5 devices you typically select the processor unit, the "base" and some groove units.
|
||||||
|
For other node mcu boards you can freely configure the peripherals that you would like to connect.
|
||||||
|
|
||||||
|
__Local Build__
|
||||||
|
|
||||||
|
As the selection of hardware is basically controlled by defines (-D flags on the command line) you can also use the build GUI to create a build command that you run locally (assumimng you have the build environment installed).
|
||||||
|
Just click the "?" button beside "Build Flags". The PopUp will give you a "Copy" button that will copy the complete command line you can use for the build.
|
||||||
|
|
||||||
|
__Remarks__
|
||||||
|
|
||||||
|
Although the online build service tries to make a couple of checks to prevent impossible hardware configurations you still need to check your design (especially if you are using the "free" nodemcu settings).
|
||||||
|
The free build service at [CircleCi](https://circleci.com/) has a couple of limitation - so please be careful and avoid too many bauilds that you potentially don't need.
|
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
|
@ -78,6 +78,13 @@ Can be used e.g. as an NMEA2000 Adapter for a laptop running e.g. OpenCPN with t
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
M5 Stack AtomS3Lite Canunit (experimental since dev20230826)
|
||||||
|
---------------------
|
||||||
|
* Hardware: [M5_ATOMS3 Lite](http://docs.m5stack.com/en/core/AtomS3%20Lite) + [CAN Unit](http://docs.m5stack.com/en/unit/can)
|
||||||
|
* Prebuild Binary: m5stack-atoms3-canunit-all.bin
|
||||||
|
* Build Define: BOARD_M5ATOMS3_CANUNIT
|
||||||
|
* Power: Via USB
|
||||||
|
|
||||||
M5 Stick C Canunit
|
M5 Stick C Canunit
|
||||||
------------------
|
------------------
|
||||||
* Hardware: [M5_StickC+](http://docs.m5stack.com/en/core/m5stickc_plus) + [CAN Unit](http://docs.m5stack.com/en/unit/can)
|
* Hardware: [M5_StickC+](http://docs.m5stack.com/en/core/m5stickc_plus) + [CAN Unit](http://docs.m5stack.com/en/unit/can)
|
||||||
|
|
262
extra_script.py
262
extra_script.py
|
@ -7,6 +7,11 @@ import inspect
|
||||||
import json
|
import json
|
||||||
import glob
|
import glob
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import re
|
||||||
|
import pprint
|
||||||
|
from platformio.project.config import ProjectConfig
|
||||||
|
|
||||||
|
|
||||||
Import("env")
|
Import("env")
|
||||||
#print(env.Dump())
|
#print(env.Dump())
|
||||||
OWN_FILE="extra_script.py"
|
OWN_FILE="extra_script.py"
|
||||||
|
@ -14,6 +19,7 @@ GEN_DIR='lib/generated'
|
||||||
CFG_FILE='web/config.json'
|
CFG_FILE='web/config.json'
|
||||||
XDR_FILE='web/xdrconfig.json'
|
XDR_FILE='web/xdrconfig.json'
|
||||||
CFG_INCLUDE='GwConfigDefinitions.h'
|
CFG_INCLUDE='GwConfigDefinitions.h'
|
||||||
|
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
|
||||||
XDR_INCLUDE='GwXdrTypeMappings.h'
|
XDR_INCLUDE='GwXdrTypeMappings.h'
|
||||||
TASK_INCLUDE='GwUserTasks.h'
|
TASK_INCLUDE='GwUserTasks.h'
|
||||||
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
|
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
|
||||||
|
@ -102,6 +108,44 @@ def mergeConfig(base,other):
|
||||||
base=base+merge
|
base=base+merge
|
||||||
return base
|
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=[]):
|
def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||||
if not os.path.exists(inFile):
|
if not os.path.exists(inFile):
|
||||||
raise Exception("unable to read cfg file %s"%inFile)
|
raise Exception("unable to read cfg file %s"%inFile)
|
||||||
|
@ -109,45 +153,70 @@ def generateMergedConfig(inFile,outFile,addDirs=[]):
|
||||||
with open(inFile,'rb') as ch:
|
with open(inFile,'rb') as ch:
|
||||||
config=json.load(ch)
|
config=json.load(ch)
|
||||||
config=mergeConfig(config,addDirs)
|
config=mergeConfig(config,addDirs)
|
||||||
|
config=expandConfig(config)
|
||||||
data=json.dumps(config,indent=2)
|
data=json.dumps(config,indent=2)
|
||||||
writeFileIfChanged(outFile,data)
|
writeFileIfChanged(outFile,data)
|
||||||
|
|
||||||
def generateCfg(inFile,outFile,addDirs=[]):
|
def generateCfg(inFile,outFile,impl):
|
||||||
if not os.path.exists(inFile):
|
if not os.path.exists(inFile):
|
||||||
raise Exception("unable to read cfg file %s"%inFile)
|
raise Exception("unable to read cfg file %s"%inFile)
|
||||||
data=""
|
data=""
|
||||||
with open(inFile,'rb') as ch:
|
with open(inFile,'rb') as ch:
|
||||||
config=json.load(ch)
|
config=json.load(ch)
|
||||||
config=mergeConfig(config,addDirs)
|
|
||||||
data+="//generated from %s\n"%inFile
|
data+="//generated from %s\n"%inFile
|
||||||
data+='#include "GwConfigItem.h"\n'
|
|
||||||
l=len(config)
|
l=len(config)
|
||||||
data+='class GwConfigDefinitions{\n'
|
idx=0
|
||||||
data+=' public:\n'
|
if not impl:
|
||||||
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
data+='#include "GwConfigItem.h"\n'
|
||||||
for item in config:
|
data+='class GwConfigDefinitions{\n'
|
||||||
n=item.get('name')
|
data+=' public:\n'
|
||||||
if n is None:
|
data+=' int getNumConfig() const{return %d;}\n'%(l)
|
||||||
continue
|
for item in config:
|
||||||
if len(n) > 15:
|
n=item.get('name')
|
||||||
raise Exception("%s: config names must be max 15 caracters"%n)
|
if n is None:
|
||||||
data+=' static constexpr const __FlashStringHelper* %s=F("%s");\n'%(n,n)
|
continue
|
||||||
data+=' protected:\n'
|
if len(n) > 15:
|
||||||
data+=' GwConfigInterface *configs[%d]={\n'%(l)
|
raise Exception("%s: config names must be max 15 caracters"%n)
|
||||||
first=True
|
data+=' static constexpr const char* %s="%s";\n'%(n,n)
|
||||||
for item in config:
|
data+="};\n"
|
||||||
if not first:
|
else:
|
||||||
data+=',\n'
|
data+='void GwConfigHandler::populateConfigs(GwConfigInterface **config){\n'
|
||||||
first=False
|
for item in config:
|
||||||
secret="false";
|
name=item.get('name')
|
||||||
if item.get('type') == 'password':
|
if name is None:
|
||||||
secret="true"
|
continue
|
||||||
data+=" new GwConfigInterface(%s,\"%s\",%s)"%(item.get('name'),item.get('default'),secret)
|
data+=' configs[%d]=\n'%(idx)
|
||||||
data+='};\n'
|
idx+=1
|
||||||
data+='};\n'
|
secret="false";
|
||||||
|
if item.get('type') == 'password':
|
||||||
|
secret="true"
|
||||||
|
data+=" #undef __CFGMODE\n"
|
||||||
|
data+=" #ifdef CFGMODE_%s\n"%(name)
|
||||||
|
data+=" #define __CFGMODE CFGMODE_%s\n"%(name)
|
||||||
|
data+=" #else\n"
|
||||||
|
data+=" #define __CFGMODE GwConfigInterface::NORMAL\n"
|
||||||
|
data+=" #endif\n"
|
||||||
|
data+=" #ifdef CFGDEFAULT_%s\n"%(name)
|
||||||
|
data+=" new GwConfigInterface(%s,CFGDEFAULT_%s,%s,__CFGMODE)\n"%(name,name,secret)
|
||||||
|
data+=" #else\n"
|
||||||
|
data+=" new GwConfigInterface(%s,\"%s\",%s,__CFGMODE)\n"%(name,item.get('default'),secret)
|
||||||
|
data+=" #endif\n"
|
||||||
|
data+=";\n"
|
||||||
|
data+='}\n'
|
||||||
|
for item in config:
|
||||||
|
name=item.get('name')
|
||||||
|
if name is None:
|
||||||
|
continue
|
||||||
|
data+="#ifdef CFGMODE_%s\n"%(name)
|
||||||
|
data+=" __MSG(\"CFGMODE_%s=\" __XSTR(CFGMODE_%s))\n"%(name,name)
|
||||||
|
data+="#endif\n"
|
||||||
|
data+="#ifdef CFGDEFAULT_%s\n"%(name)
|
||||||
|
data+=" __MSG(\"CFGDEFAULT_%s=\" CFGDEFAULT_%s)\n"%(name,name)
|
||||||
|
data+="#endif\n"
|
||||||
writeFileIfChanged(outFile,data)
|
writeFileIfChanged(outFile,data)
|
||||||
|
|
||||||
|
def labelFilter(label):
|
||||||
|
return re.sub("[^a-zA-Z0-9]","",re.sub("\([0-9]*\)","",label))
|
||||||
def generateXdrMappings(fp,oh,inFile=''):
|
def generateXdrMappings(fp,oh,inFile=''):
|
||||||
jdoc=json.load(fp)
|
jdoc=json.load(fp)
|
||||||
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
|
oh.write("static GwXDRTypeMapping* typeMappings[]={\n")
|
||||||
|
@ -186,15 +255,56 @@ def generateXdrMappings(fp,oh,inFile=''):
|
||||||
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
|
oh.write(" new GwXDRTypeMapping(%d,%d,%d) /*%s:%s*/"%(cid,id,tc,cat,l))
|
||||||
oh.write("\n")
|
oh.write("\n")
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
userTaskDirs=[]
|
userTaskDirs=[]
|
||||||
|
|
||||||
def getUserTaskDirs():
|
def getUserTaskDirs():
|
||||||
rt=[]
|
rt=[]
|
||||||
taskdirs=glob.glob(os.path.join('lib','*task*'))
|
taskdirs=glob.glob(os.path.join( basePath(),'lib','*task*'))
|
||||||
for task in taskdirs:
|
for task in taskdirs:
|
||||||
rt.append(task)
|
rt.append(task)
|
||||||
return rt
|
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):
|
def genereateUserTasks(outfile):
|
||||||
includes=[]
|
includes=[]
|
||||||
for task in userTaskDirs:
|
for task in userTaskDirs:
|
||||||
|
@ -202,16 +312,7 @@ def genereateUserTasks(outfile):
|
||||||
base=os.path.basename(task)
|
base=os.path.basename(task)
|
||||||
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
includeNames=[base.lower()+".h",'gw'+base.lower()+'.h']
|
||||||
for f in os.listdir(task):
|
for f in os.listdir(task):
|
||||||
if not f.endswith('.h'):
|
checkAndAdd(f,includeNames,includes)
|
||||||
continue
|
|
||||||
match=False
|
|
||||||
for cmp in includeNames:
|
|
||||||
#print("##check %s<->%s"%(f.lower(),cmp))
|
|
||||||
if f.lower() == cmp:
|
|
||||||
match=True
|
|
||||||
if not match:
|
|
||||||
continue
|
|
||||||
includes.append(f)
|
|
||||||
includeData=""
|
includeData=""
|
||||||
for i in includes:
|
for i in includes:
|
||||||
print("#task include %s"%i)
|
print("#task include %s"%i)
|
||||||
|
@ -237,16 +338,92 @@ def getContentType(fn):
|
||||||
return "text/css"
|
return "text/css"
|
||||||
return "application/octet-stream"
|
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
|
||||||
|
|
||||||
|
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):
|
def prebuild(env):
|
||||||
global userTaskDirs
|
global userTaskDirs
|
||||||
print("#prebuild running")
|
print("#prebuild running")
|
||||||
if not checkDir():
|
if not checkDir():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
ldf_mode=env.GetProjectOption("lib_ldf_mode")
|
||||||
|
if ldf_mode == 'off':
|
||||||
|
print("##ldf off - own dependency handling")
|
||||||
|
handleDeps(env)
|
||||||
userTaskDirs=getUserTaskDirs()
|
userTaskDirs=getUserTaskDirs()
|
||||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||||
compressFile(mergedConfig,mergedConfig+".gz")
|
compressFile(mergedConfig,mergedConfig+".gz")
|
||||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE))
|
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE),False)
|
||||||
|
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE_IMPL),True)
|
||||||
embedded=getEmbeddedFiles(env)
|
embedded=getEmbeddedFiles(env)
|
||||||
filedefs=[]
|
filedefs=[]
|
||||||
for ef in embedded:
|
for ef in embedded:
|
||||||
|
@ -280,10 +457,15 @@ def cleangenerated(source, target, env):
|
||||||
fn=os.path.join(od,f)
|
fn=os.path.join(od,f)
|
||||||
os.unlink(f)
|
os.unlink(f)
|
||||||
|
|
||||||
|
|
||||||
print("#prescript...")
|
print("#prescript...")
|
||||||
prebuild(env)
|
prebuild(env)
|
||||||
|
board="PLATFORM_BOARD_%s"%env["BOARD"].replace("-","_").upper()
|
||||||
|
print("Board=#%s#"%board)
|
||||||
|
print("BuildFlags=%s"%(" ".join(env["BUILD_FLAGS"])))
|
||||||
env.Append(
|
env.Append(
|
||||||
LINKFLAGS=[ "-u", "custom_app_desc" ]
|
LINKFLAGS=[ "-u", "custom_app_desc" ],
|
||||||
|
CPPDEFINES=[(board,"1")]
|
||||||
)
|
)
|
||||||
#script does not run on clean yet - maybe in the future
|
#script does not run on clean yet - maybe in the future
|
||||||
env.AddPostAction("clean",cleangenerated)
|
env.AddPostAction("clean",cleangenerated)
|
||||||
|
|
141
lib/api/GwApi.h
141
lib/api/GwApi.h
|
@ -5,6 +5,10 @@
|
||||||
#include "NMEA0183Msg.h"
|
#include "NMEA0183Msg.h"
|
||||||
#include "GWConfig.h"
|
#include "GWConfig.h"
|
||||||
#include "GwBoatData.h"
|
#include "GwBoatData.h"
|
||||||
|
#include "GwXDRMappings.h"
|
||||||
|
#include <map>
|
||||||
|
class GwApi;
|
||||||
|
typedef void (*GwUserTaskFunction)(GwApi *);
|
||||||
//API to be used for additional tasks
|
//API to be used for additional tasks
|
||||||
class GwApi{
|
class GwApi{
|
||||||
public:
|
public:
|
||||||
|
@ -32,7 +36,47 @@ class GwApi{
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* an interface for the data exchange between tasks
|
||||||
|
* the core part will not handle this data at all but
|
||||||
|
* this interface ensures that there is a correct locking of the
|
||||||
|
* data to correctly handle multi threading
|
||||||
|
* The user code should not use this intterface directly
|
||||||
|
* but instead it should use the static functions
|
||||||
|
* apiGetXXX(GwApi *,...)
|
||||||
|
* apiSetXXX(GwApi *,...)
|
||||||
|
* that will be created by the macro DECLARE_TASK_INTERFACE
|
||||||
|
*/
|
||||||
|
class TaskInterfaces
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class Base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using Ptr = std::shared_ptr<Base>;
|
||||||
|
protected:
|
||||||
|
virtual bool iset(const String &file, const String &name, Ptr v) = 0;
|
||||||
|
virtual Ptr iget(const String &name, int &result) = 0;
|
||||||
|
virtual bool iclaim(const String &name, const String &task)=0;
|
||||||
|
public:
|
||||||
|
template <typename T>
|
||||||
|
bool set(const T &v){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
T get(int &res){
|
||||||
|
res=-1;
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
bool claim(const String &task){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
class Status{
|
class Status{
|
||||||
public:
|
public:
|
||||||
bool wifiApOn=false;
|
bool wifiApOn=false;
|
||||||
|
@ -47,6 +91,8 @@ class GwApi{
|
||||||
unsigned long usbTx=0;
|
unsigned long usbTx=0;
|
||||||
unsigned long serRx=0;
|
unsigned long serRx=0;
|
||||||
unsigned long serTx=0;
|
unsigned long serTx=0;
|
||||||
|
unsigned long ser2Rx=0;
|
||||||
|
unsigned long ser2Tx=0;
|
||||||
unsigned long tcpSerRx=0;
|
unsigned long tcpSerRx=0;
|
||||||
unsigned long tcpSerTx=0;
|
unsigned long tcpSerTx=0;
|
||||||
int tcpClients=0;
|
int tcpClients=0;
|
||||||
|
@ -68,6 +114,8 @@ class GwApi{
|
||||||
usbTx=0;
|
usbTx=0;
|
||||||
serRx=0;
|
serRx=0;
|
||||||
serTx=0;
|
serTx=0;
|
||||||
|
ser2Rx=0;
|
||||||
|
ser2Tx=0;
|
||||||
tcpSerRx=0;
|
tcpSerRx=0;
|
||||||
tcpSerTx=0;
|
tcpSerTx=0;
|
||||||
tcpClients=0;
|
tcpClients=0;
|
||||||
|
@ -109,7 +157,32 @@ class GwApi{
|
||||||
/**
|
/**
|
||||||
* fill the status information
|
* fill the status information
|
||||||
*/
|
*/
|
||||||
virtual void getStatus(Status &status);
|
virtual void getStatus(Status &status)=0;
|
||||||
|
/**
|
||||||
|
* access to counters for a task
|
||||||
|
* thread safe
|
||||||
|
* use the value returned from addCounter for the other operations
|
||||||
|
*/
|
||||||
|
virtual int addCounter(const String &){return -1;}
|
||||||
|
virtual void increment(int idx,const String &name,bool failed=false){}
|
||||||
|
virtual void reset(int idx){}
|
||||||
|
virtual void remove(int idx){}
|
||||||
|
virtual TaskInterfaces * taskInterfaces()=0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* only allowed during init methods
|
||||||
|
*/
|
||||||
|
virtual bool addXdrMapping(const GwXDRMappingDef &)=0;
|
||||||
|
|
||||||
|
virtual void addCapability(const String &name, const String &value)=0;
|
||||||
|
/**
|
||||||
|
* add a user task
|
||||||
|
* this allows you decide based on defines/config if a user task really should be added
|
||||||
|
* so this is the preferred solution over DECLARE_USERTASK
|
||||||
|
* The name should be similar to the function name of the user task (although not mandatory)
|
||||||
|
*/
|
||||||
|
virtual bool addUserTask(GwUserTaskFunction task,const String Name, int stackSize=2000)=0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* not thread safe methods
|
* not thread safe methods
|
||||||
* accessing boat data must only be executed from within the main thread
|
* accessing boat data must only be executed from within the main thread
|
||||||
|
@ -118,6 +191,14 @@ class GwApi{
|
||||||
virtual GwBoatData *getBoatData()=0;
|
virtual GwBoatData *getBoatData()=0;
|
||||||
virtual ~GwApi(){}
|
virtual ~GwApi(){}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a simple generic function to create runtime errors if some necessary values are not defined
|
||||||
|
*/
|
||||||
|
template<typename... T>
|
||||||
|
static void checkDef(T... args){};
|
||||||
|
|
||||||
|
|
||||||
#ifndef DECLARE_USERTASK
|
#ifndef DECLARE_USERTASK
|
||||||
#define DECLARE_USERTASK(task)
|
#define DECLARE_USERTASK(task)
|
||||||
#endif
|
#endif
|
||||||
|
@ -133,4 +214,60 @@ class GwApi{
|
||||||
#ifndef DECLARE_STRING_CAPABILITY
|
#ifndef DECLARE_STRING_CAPABILITY
|
||||||
#define DECLARE_STRING_CAPABILITY(name,value)
|
#define DECLARE_STRING_CAPABILITY(name,value)
|
||||||
#endif
|
#endif
|
||||||
|
/**
|
||||||
|
* macro to declare an interface that a task provides to other tasks
|
||||||
|
* this macro must be used in an File I<taskname>.h or GwI<taskname>.h
|
||||||
|
* the first parameter must be the name of the task that should be able
|
||||||
|
* to write this data (the same name as being used in DECLARE_TASK).
|
||||||
|
* The second parameter must be a type (class) that inherits from
|
||||||
|
* GwApi::TaskInterfaces::Base.
|
||||||
|
* This class must provide copy constructors and empty constructors.
|
||||||
|
* The attributes should only be simple values or structs but no pointers.
|
||||||
|
* example:
|
||||||
|
* class TestTaskApi: public GwApi::TaskInterfaces::Base{
|
||||||
|
* public:
|
||||||
|
* int ival1=0;
|
||||||
|
* int ival2=99;
|
||||||
|
* String sval="unset";
|
||||||
|
* };
|
||||||
|
* DECLARE_TASKIF(testTask,TestTaskApi);
|
||||||
|
* The macro will generate 2 static funtions:
|
||||||
|
*
|
||||||
|
* bool apiSetTestTaskApi(GwApi *api, const TestTaskApi &v);
|
||||||
|
* TestTaskApi apiGetTestTaskApi(GwApi *api, int &result);
|
||||||
|
*
|
||||||
|
* The setter will return true on success.
|
||||||
|
* It is intended to be used by the task that did declare the api.
|
||||||
|
* The getter will set the result to -1 if no data is available, otherwise
|
||||||
|
* it will return the update count (so you can check if there was a change
|
||||||
|
* compared to the last call).
|
||||||
|
* It is intended to be used by any task.
|
||||||
|
* Be aware that all the apis share a common namespace - so be sure to somehow
|
||||||
|
* make your API names unique.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define DECLARE_TASKIF_IMPL(type) \
|
||||||
|
template<> \
|
||||||
|
inline bool GwApi::TaskInterfaces::set(const type & v) {\
|
||||||
|
return iset(__FILE__,#type,GwApi::TaskInterfaces::Ptr(new type(v))); \
|
||||||
|
}\
|
||||||
|
template<> \
|
||||||
|
inline type GwApi::TaskInterfaces::get<type>(int &result) {\
|
||||||
|
auto ptr=iget(#type,result); \
|
||||||
|
if (!ptr) {\
|
||||||
|
result=-1; \
|
||||||
|
return type(); \
|
||||||
|
}\
|
||||||
|
type *tp=(type*)ptr.get(); \
|
||||||
|
return type(*tp); \
|
||||||
|
}\
|
||||||
|
template<> \
|
||||||
|
inline bool GwApi::TaskInterfaces::claim<type>(const String &task) {\
|
||||||
|
return iclaim(#type,task);\
|
||||||
|
}\
|
||||||
|
|
||||||
|
#ifndef DECLARE_TASKIF
|
||||||
|
#define DECLARE_TASKIF(type) DECLARE_TASKIF_IMPL(type)
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -16,3 +16,4 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define FIRMWARE_TYPE GWSTRINGIFY(PIO_ENV_BUILD)
|
#define FIRMWARE_TYPE GWSTRINGIFY(PIO_ENV_BUILD)
|
||||||
|
#define IDF_VERSION GWSTRINGIFY(ESP_IDF_VERSION_MAJOR) "." GWSTRINGIFY(ESP_IDF_VERSION_MINOR) "." GWSTRINGIFY(ESP_IDF_VERSION_PATCH)
|
|
@ -4,8 +4,9 @@
|
||||||
#include "GwLog.h"
|
#include "GwLog.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
#define GW_BOAT_VALUE_LEN 32
|
#define GW_BOAT_VALUE_LEN 32
|
||||||
#define GWSC(name) static constexpr const __FlashStringHelper* name=F(#name)
|
#define GWSC(name) static constexpr const char* name=#name
|
||||||
|
|
||||||
//see https://github.com/wellenvogel/esp32-nmea2000/issues/44
|
//see https://github.com/wellenvogel/esp32-nmea2000/issues/44
|
||||||
//factor to convert from N2k/SI rad/s to current NMEA rad/min
|
//factor to convert from N2k/SI rad/s to current NMEA rad/min
|
||||||
|
@ -164,10 +165,10 @@ public:
|
||||||
virtual ~GwBoatItemNameProvider() {}
|
virtual ~GwBoatItemNameProvider() {}
|
||||||
};
|
};
|
||||||
#define GWBOATDATA(type,name,time,fmt) \
|
#define GWBOATDATA(type,name,time,fmt) \
|
||||||
static constexpr const __FlashStringHelper* _##name=F(#name); \
|
static constexpr const char* _##name=#name; \
|
||||||
GwBoatItem<type> *name=new GwBoatItem<type>(F(#name),GwBoatItemBase::fmt,time,&values) ;
|
GwBoatItem<type> *name=new GwBoatItem<type>(#name,GwBoatItemBase::fmt,time,&values) ;
|
||||||
#define GWSPECBOATDATA(clazz,name,time,fmt) \
|
#define GWSPECBOATDATA(clazz,name,time,fmt) \
|
||||||
clazz *name=new clazz(F(#name),GwBoatItemBase::fmt,time,&values) ;
|
clazz *name=new clazz(#name,GwBoatItemBase::fmt,time,&values) ;
|
||||||
class GwBoatData{
|
class GwBoatData{
|
||||||
private:
|
private:
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
#ifndef _GWBUTTONS_H
|
|
||||||
#define _GWBUTTONS_H
|
|
||||||
//task function
|
|
||||||
void handleButtons(void *param);
|
|
||||||
#endif
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include "GwButtons.h"
|
#include "GwButtonTask.h"
|
||||||
#include "GwHardware.h"
|
#include "GwHardware.h"
|
||||||
#include "GwApi.h"
|
#include "GwApi.h"
|
||||||
#include "GwLeds.h"
|
#include "GwLedTask.h"
|
||||||
|
|
||||||
class FactoryResetRequest: public GwMessage{
|
class FactoryResetRequest: public GwMessage{
|
||||||
private:
|
private:
|
||||||
|
@ -22,9 +22,13 @@ class FactoryResetRequest: public GwMessage{
|
||||||
},"reset",1000,NULL,0,NULL);
|
},"reset",1000,NULL,0,NULL);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
void handleButtons(void *param){
|
void handleButtons(GwApi *api){
|
||||||
GwApi *api=(GwApi*)param;
|
|
||||||
GwLog *logger=api->getLogger();
|
GwLog *logger=api->getLogger();
|
||||||
|
GwApi::TaskInterfaces *interfaces=api->taskInterfaces();
|
||||||
|
IButtonTask state;
|
||||||
|
if (!interfaces->set(state)){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unable to set button state");
|
||||||
|
}
|
||||||
#ifndef GWBUTTON_PIN
|
#ifndef GWBUTTON_PIN
|
||||||
LOG_DEBUG(GwLog::LOG,"no button pin defined, do not watch");
|
LOG_DEBUG(GwLog::LOG,"no button pin defined, do not watch");
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
|
@ -50,46 +54,51 @@ void handleButtons(void *param){
|
||||||
unsigned long lastReport=0;
|
unsigned long lastReport=0;
|
||||||
const unsigned long OFF_TIME=20;
|
const unsigned long OFF_TIME=20;
|
||||||
const unsigned long REPORT_TIME=1000;
|
const unsigned long REPORT_TIME=1000;
|
||||||
const unsigned long HARD_REST_TIME=10000;
|
const unsigned long PRESS_5_TIME=5000;
|
||||||
GwLedMode ledMode=LED_OFF;
|
const unsigned long PRESS_10_TIME=10000;
|
||||||
|
const unsigned long PRESS_RESET_TIME=12000;
|
||||||
|
LOG_DEBUG(GwLog::LOG,"button task started");
|
||||||
while(true){
|
while(true){
|
||||||
delay(10);
|
delay(10);
|
||||||
int current=digitalRead(GWBUTTON_PIN);
|
int current=digitalRead(GWBUTTON_PIN);
|
||||||
unsigned long now=millis();
|
unsigned long now=millis();
|
||||||
|
IButtonTask::ButtonState lastState=state.state;
|
||||||
if (current != activeState){
|
if (current != activeState){
|
||||||
if (lastPressed != 0 && (lastPressed+OFF_TIME) < now){
|
if (lastPressed != 0 && (lastPressed+OFF_TIME) < now){
|
||||||
lastPressed=0; //finally off
|
lastPressed=0; //finally off
|
||||||
firstPressed=0;
|
firstPressed=0;
|
||||||
if (ledMode != LED_OFF){
|
state.state=IButtonTask::OFF;
|
||||||
setLedMode(LED_GREEN); //TODO: better "go back"
|
|
||||||
ledMode=LED_OFF;
|
|
||||||
}
|
|
||||||
LOG_DEBUG(GwLog::LOG,"Button press stopped");
|
LOG_DEBUG(GwLog::LOG,"Button press stopped");
|
||||||
}
|
}
|
||||||
|
if (state.state != lastState){
|
||||||
|
interfaces->set(state);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
lastPressed=now;
|
lastPressed=now;
|
||||||
if (firstPressed == 0) {
|
if (firstPressed == 0) {
|
||||||
firstPressed=now;
|
firstPressed=now;
|
||||||
LOG_DEBUG(GwLog::LOG,"Button press started");
|
LOG_DEBUG(GwLog::LOG,"Button press started");
|
||||||
|
state.pressCount++;
|
||||||
|
state.state=IButtonTask::PRESSED;
|
||||||
|
interfaces->set(state);
|
||||||
lastReport=now;
|
lastReport=now;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (lastReport != 0 && (lastReport + REPORT_TIME) < now ){
|
if (lastReport != 0 && (lastReport + REPORT_TIME) < now ){
|
||||||
LOG_DEBUG(GwLog::LOG,"Button active for %ld",(now-firstPressed));
|
LOG_DEBUG(GwLog::LOG,"Button active for %ld",(now-firstPressed));
|
||||||
lastReport=now;
|
lastReport=now;
|
||||||
}
|
}
|
||||||
GwLedMode nextMode=ledMode;
|
if (now > (firstPressed+PRESS_5_TIME)){
|
||||||
if (now > (firstPressed+HARD_REST_TIME/2)){
|
state.state=IButtonTask::PRESSED_5;
|
||||||
nextMode=LED_BLUE;
|
|
||||||
}
|
}
|
||||||
if (now > (firstPressed+HARD_REST_TIME*0.9)){
|
if (now > (firstPressed+PRESS_10_TIME)){
|
||||||
nextMode=LED_RED;
|
state.state=IButtonTask::PRESSED_10;
|
||||||
}
|
}
|
||||||
if (ledMode != nextMode){
|
if (lastState != state.state){
|
||||||
setLedMode(nextMode);
|
interfaces->set(state);
|
||||||
ledMode=nextMode;
|
|
||||||
}
|
}
|
||||||
if (now > (firstPressed+HARD_REST_TIME)){
|
if (now > (firstPressed+PRESS_RESET_TIME)){
|
||||||
LOG_DEBUG(GwLog::ERROR,"Factory reset by button");
|
LOG_DEBUG(GwLog::ERROR,"Factory reset by button");
|
||||||
GwMessage *r=new FactoryResetRequest(api);
|
GwMessage *r=new FactoryResetRequest(api);
|
||||||
api->getQueue()->sendAndForget(r);
|
api->getQueue()->sendAndForget(r);
|
||||||
|
@ -101,3 +110,13 @@ void handleButtons(void *param){
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void initButtons(GwApi *api){
|
||||||
|
#ifndef GWBUTTON_PIN
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG,"no buttons defined, no button task");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
const String taskname("buttonTask");
|
||||||
|
api->addUserTask(handleButtons,taskname);
|
||||||
|
api->taskInterfaces()->claim<IButtonTask>(taskname);
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef _GWBUTTONTASK_H
|
||||||
|
#define _GWBUTTONTASK_H
|
||||||
|
#include "GwApi.h"
|
||||||
|
//task function
|
||||||
|
void initButtons(GwApi *param);
|
||||||
|
DECLARE_INITFUNCTION(initButtons);
|
||||||
|
|
||||||
|
class IButtonTask : public GwApi::TaskInterfaces::Base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
OFF,
|
||||||
|
PRESSED,
|
||||||
|
PRESSED_5, // 5...10s
|
||||||
|
PRESSED_10 //>10s
|
||||||
|
} ButtonState;
|
||||||
|
ButtonState state=OFF;
|
||||||
|
long pressCount=0;
|
||||||
|
};
|
||||||
|
DECLARE_TASKIF(IButtonTask);
|
||||||
|
#endif
|
|
@ -57,7 +57,7 @@ GwChannel::GwChannel(GwLog *logger,
|
||||||
this->logger = logger;
|
this->logger = logger;
|
||||||
this->name=name;
|
this->name=name;
|
||||||
this->sourceId=sourceId;
|
this->sourceId=sourceId;
|
||||||
this->maxSourceId=sourceId;
|
this->maxSourceId=maxSourceId;
|
||||||
this->countIn=new GwCounter<String>(String("count")+name+String("in"));
|
this->countIn=new GwCounter<String>(String("count")+name+String("in"));
|
||||||
this->countOut=new GwCounter<String>(String("count")+name+String("out"));
|
this->countOut=new GwCounter<String>(String("count")+name+String("out"));
|
||||||
this->impl=NULL;
|
this->impl=NULL;
|
||||||
|
@ -146,12 +146,15 @@ bool GwChannel::canReceive(const char *buffer){
|
||||||
}
|
}
|
||||||
|
|
||||||
int GwChannel::getJsonSize(){
|
int GwChannel::getJsonSize(){
|
||||||
int rt=2;
|
int rt=JSON_OBJECT_SIZE(6);
|
||||||
if (countIn) rt+=countIn->getJsonSize();
|
if (countIn) rt+=countIn->getJsonSize();
|
||||||
if (countOut) rt+=countOut->getJsonSize();
|
if (countOut) rt+=countOut->getJsonSize();
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
void GwChannel::toJson(GwJsonDocument &doc){
|
void GwChannel::toJson(GwJsonDocument &doc){
|
||||||
|
JsonObject jo=doc.createNestedObject("ch"+name);
|
||||||
|
jo["id"]=sourceId;
|
||||||
|
jo["max"]=maxSourceId;
|
||||||
if (countOut) countOut->toJson(doc);
|
if (countOut) countOut->toJson(doc);
|
||||||
if (countIn) countIn->toJson(doc);
|
if (countIn) countIn->toJson(doc);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,13 +58,135 @@ void GwChannelList::allChannels(ChannelAction action){
|
||||||
action(*it);
|
action(*it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
typedef struct {
|
||||||
|
int id;
|
||||||
|
const char *baud;
|
||||||
|
const char *receive;
|
||||||
|
const char *send;
|
||||||
|
const char *direction;
|
||||||
|
const char *toN2K;
|
||||||
|
const char *readF;
|
||||||
|
const char *writeF;
|
||||||
|
const char *name;
|
||||||
|
} SerialParam;
|
||||||
|
|
||||||
|
static SerialParam serialParameters[]={
|
||||||
|
{
|
||||||
|
.id=SERIAL1_CHANNEL_ID,
|
||||||
|
.baud=GwConfigDefinitions::serialBaud,
|
||||||
|
.receive=GwConfigDefinitions::receiveSerial,
|
||||||
|
.send=GwConfigDefinitions::sendSerial,
|
||||||
|
.direction=GwConfigDefinitions::serialDirection,
|
||||||
|
.toN2K=GwConfigDefinitions::serialToN2k,
|
||||||
|
.readF=GwConfigDefinitions::serialReadF,
|
||||||
|
.writeF=GwConfigDefinitions::serialWriteF,
|
||||||
|
.name="Serial"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.id=SERIAL2_CHANNEL_ID,
|
||||||
|
.baud=GwConfigDefinitions::serial2Baud,
|
||||||
|
.receive=GwConfigDefinitions::receiveSerial2,
|
||||||
|
.send=GwConfigDefinitions::sendSerial2,
|
||||||
|
.direction=GwConfigDefinitions::serial2Dir,
|
||||||
|
.toN2K=GwConfigDefinitions::serial2ToN2k,
|
||||||
|
.readF=GwConfigDefinitions::serial2ReadF,
|
||||||
|
.writeF=GwConfigDefinitions::serial2WriteF,
|
||||||
|
.name="Serial2"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static SerialParam *getSerialParam(int id){
|
||||||
|
for (size_t idx=0;idx<sizeof(serialParameters)/sizeof(SerialParam*);idx++){
|
||||||
|
if (serialParameters[idx].id == id) return &serialParameters[idx];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GwChannelList:: addSerial(HardwareSerial *stream,int id,int type,int rx,int tx){
|
||||||
|
const char *mode=nullptr;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case GWSERIAL_TYPE_UNI:
|
||||||
|
mode="UNI";
|
||||||
|
break;
|
||||||
|
case GWSERIAL_TYPE_BI:
|
||||||
|
mode="BI";
|
||||||
|
break;
|
||||||
|
case GWSERIAL_TYPE_RX:
|
||||||
|
mode="RX";
|
||||||
|
break;
|
||||||
|
case GWSERIAL_TYPE_TX:
|
||||||
|
mode="TX";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mode == nullptr) {
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unknown serial type %d",type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addSerial(stream,id,mode,rx,tx);
|
||||||
|
}
|
||||||
|
void GwChannelList::addSerial(HardwareSerial *serialStream,int id,const String &mode,int rx,int tx){
|
||||||
|
SerialParam *param=getSerialParam(id);
|
||||||
|
if (param == nullptr){
|
||||||
|
logger->logDebug(GwLog::ERROR,"trying to set up an unknown serial channel: %d",id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rx < 0 && tx < 0){
|
||||||
|
logger->logDebug(GwLog::ERROR,"useless config for serial %d: both rx/tx undefined");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modes[id]=String(mode);
|
||||||
|
bool canRead=false;
|
||||||
|
bool canWrite=false;
|
||||||
|
if (mode == "BI"){
|
||||||
|
canRead=config->getBool(param->receive);
|
||||||
|
canWrite=config->getBool(param->send);
|
||||||
|
}
|
||||||
|
if (mode == "TX"){
|
||||||
|
canWrite=true;
|
||||||
|
}
|
||||||
|
if (mode == "RX"){
|
||||||
|
canRead=true;
|
||||||
|
}
|
||||||
|
if (mode == "UNI"){
|
||||||
|
String cfgMode=config->getString(param->direction);
|
||||||
|
if (cfgMode == "receive"){
|
||||||
|
canRead=true;
|
||||||
|
}
|
||||||
|
if (cfgMode == "send"){
|
||||||
|
canWrite=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rx < 0) canRead=false;
|
||||||
|
if (tx < 0) canWrite=false;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"serial set up: mode=%s,rx=%d,canRead=%d,tx=%d,canWrite=%d",
|
||||||
|
mode.c_str(),rx,(int)canRead,tx,(int)canWrite);
|
||||||
|
serialStream->begin(config->getInt(param->baud,115200),SERIAL_8N1,rx,tx);
|
||||||
|
GwSerial *serial = new GwSerial(logger, serialStream, id, canRead);
|
||||||
|
LOG_DEBUG(GwLog::LOG, "starting serial %d ", id);
|
||||||
|
GwChannel *channel = new GwChannel(logger, param->name, id);
|
||||||
|
channel->setImpl(serial);
|
||||||
|
channel->begin(
|
||||||
|
canRead || canWrite,
|
||||||
|
canWrite,
|
||||||
|
canRead,
|
||||||
|
config->getString(param->readF),
|
||||||
|
config->getString(param->writeF),
|
||||||
|
false,
|
||||||
|
config->getBool(param->toN2K),
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
LOG_DEBUG(GwLog::LOG, "%s", channel->toString().c_str());
|
||||||
|
theChannels.push_back(channel);
|
||||||
|
}
|
||||||
|
|
||||||
void GwChannelList::begin(bool fallbackSerial){
|
void GwChannelList::begin(bool fallbackSerial){
|
||||||
LOG_DEBUG(GwLog::DEBUG,"GwChannelList::begin");
|
LOG_DEBUG(GwLog::DEBUG,"GwChannelList::begin");
|
||||||
GwChannel *channel=NULL;
|
GwChannel *channel=NULL;
|
||||||
//usb
|
//usb
|
||||||
if (! fallbackSerial){
|
if (! fallbackSerial){
|
||||||
GwSerial *usb=new GwSerial(NULL,0,USB_CHANNEL_ID);
|
GwSerial *usb=new GwSerial(NULL,&USBSerial,USB_CHANNEL_ID);
|
||||||
usb->setup(config->getInt(config->usbBaud),3,1);
|
USBSerial.begin(config->getInt(config->usbBaud));
|
||||||
logger->setWriter(new GwSerialLog(usb,config->getBool(config->usbActisense)));
|
logger->setWriter(new GwSerialLog(usb,config->getBool(config->usbActisense)));
|
||||||
logger->prefix="GWSERIAL:";
|
logger->prefix="GWSERIAL:";
|
||||||
channel=new GwChannel(logger,"USB",USB_CHANNEL_ID);
|
channel=new GwChannel(logger,"USB",USB_CHANNEL_ID);
|
||||||
|
@ -85,7 +207,7 @@ void GwChannelList::begin(bool fallbackSerial){
|
||||||
//TCP server
|
//TCP server
|
||||||
sockets=new GwSocketServer(config,logger,MIN_TCP_CHANNEL_ID);
|
sockets=new GwSocketServer(config,logger,MIN_TCP_CHANNEL_ID);
|
||||||
sockets->begin();
|
sockets->begin();
|
||||||
channel=new GwChannel(logger,"TCP",MIN_TCP_CHANNEL_ID,MIN_TCP_CHANNEL_ID+10);
|
channel=new GwChannel(logger,"TCPserver",MIN_TCP_CHANNEL_ID,MIN_TCP_CHANNEL_ID+10);
|
||||||
channel->setImpl(sockets);
|
channel->setImpl(sockets);
|
||||||
channel->begin(
|
channel->begin(
|
||||||
true,
|
true,
|
||||||
|
@ -102,57 +224,33 @@ void GwChannelList::begin(bool fallbackSerial){
|
||||||
theChannels.push_back(channel);
|
theChannels.push_back(channel);
|
||||||
|
|
||||||
//serial 1
|
//serial 1
|
||||||
bool serCanRead=true;
|
#ifndef GWSERIAL_TX
|
||||||
bool serCanWrite=true;
|
#define GWSERIAL_TX -1
|
||||||
int serialrx=-1;
|
#endif
|
||||||
int serialtx=-1;
|
#ifndef GWSERIAL_RX
|
||||||
#ifdef GWSERIAL_MODE
|
#define GWSERIAL_RX -1
|
||||||
#ifdef GWSERIAL_TX
|
#endif
|
||||||
serialtx=GWSERIAL_TX;
|
#ifdef GWSERIAL_TYPE
|
||||||
#endif
|
addSerial(&Serial1,SERIAL1_CHANNEL_ID,GWSERIAL_TYPE,GWSERIAL_RX,GWSERIAL_TX);
|
||||||
#ifdef GWSERIAL_RX
|
#else
|
||||||
serialrx=GWSERIAL_RX;
|
#ifdef GWSERIAL_MODE
|
||||||
#endif
|
addSerial(&Serial1,SERIAL1_CHANNEL_ID,GWSERIAL_MODE,GWSERIAL_RX,GWSERIAL_TX);
|
||||||
if (serialrx != -1 && serialtx != -1){
|
#endif
|
||||||
serialMode=GWSERIAL_MODE;
|
#endif
|
||||||
}
|
//serial 2
|
||||||
|
#ifndef GWSERIAL2_TX
|
||||||
|
#define GWSERIAL2_TX -1
|
||||||
|
#endif
|
||||||
|
#ifndef GWSERIAL2_RX
|
||||||
|
#define GWSERIAL2_RX -1
|
||||||
|
#endif
|
||||||
|
#ifdef GWSERIAL2_TYPE
|
||||||
|
addSerial(&Serial2,SERIAL2_CHANNEL_ID,GWSERIAL2_TYPE,GWSERIAL2_RX,GWSERIAL2_TX);
|
||||||
|
#else
|
||||||
|
#ifdef GWSERIAL2_MODE
|
||||||
|
addSerial(&Serial2,SERIAL2_CHANNEL_ID,GWSERIAL2_MODE,GWSERIAL2_RX,GWSERIAL2_TX);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
//the serial direction is from the config (only valid for mode UNI)
|
|
||||||
String serialDirection=config->getString(config->serialDirection);
|
|
||||||
//we only consider the direction if mode is UNI
|
|
||||||
if (serialMode != String("UNI")){
|
|
||||||
serialDirection=String("");
|
|
||||||
//if mode is UNI it depends on the selection
|
|
||||||
serCanRead=config->getBool(config->receiveSerial);
|
|
||||||
serCanWrite=config->getBool(config->sendSerial);
|
|
||||||
}
|
|
||||||
if (serialDirection == "receive" || serialDirection == "off" || serialMode == "RX") serCanWrite=false;
|
|
||||||
if (serialDirection == "send" || serialDirection == "off" || serialMode == "TX") serCanRead=false;
|
|
||||||
LOG_DEBUG(GwLog::DEBUG,"serial set up: mode=%s,direction=%s,rx=%d,tx=%d",
|
|
||||||
serialMode.c_str(),serialDirection.c_str(),serialrx,serialtx
|
|
||||||
);
|
|
||||||
if (serialtx != -1 || serialrx != -1 ){
|
|
||||||
LOG_DEBUG(GwLog::LOG,"creating serial interface rx=%d, tx=%d",serialrx,serialtx);
|
|
||||||
GwSerial *serial=new GwSerial(logger,1,SERIAL1_CHANNEL_ID,serCanRead);
|
|
||||||
int rt=serial->setup(config->getInt(config->serialBaud,115200),serialrx,serialtx);
|
|
||||||
LOG_DEBUG(GwLog::LOG,"starting serial returns %d",rt);
|
|
||||||
channel=new GwChannel(logger,"SER",SERIAL1_CHANNEL_ID);
|
|
||||||
channel->setImpl(serial);
|
|
||||||
channel->begin(
|
|
||||||
serCanRead || serCanWrite,
|
|
||||||
serCanWrite,
|
|
||||||
serCanRead,
|
|
||||||
config->getString(config->serialReadF),
|
|
||||||
config->getString(config->serialWriteF),
|
|
||||||
false,
|
|
||||||
config->getBool(config->serialToN2k),
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
LOG_DEBUG(GwLog::LOG,"%s",channel->toString().c_str());
|
|
||||||
theChannels.push_back(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
//tcp client
|
//tcp client
|
||||||
bool tclEnabled=config->getBool(config->tclEnabled);
|
bool tclEnabled=config->getBool(config->tclEnabled);
|
||||||
channel=new GwChannel(logger,"TCPClient",TCP_CLIENT_CHANNEL_ID);
|
channel=new GwChannel(logger,"TCPClient",TCP_CLIENT_CHANNEL_ID);
|
||||||
|
@ -180,6 +278,11 @@ void GwChannelList::begin(bool fallbackSerial){
|
||||||
LOG_DEBUG(GwLog::LOG,"%s",channel->toString().c_str());
|
LOG_DEBUG(GwLog::LOG,"%s",channel->toString().c_str());
|
||||||
logger->flush();
|
logger->flush();
|
||||||
}
|
}
|
||||||
|
String GwChannelList::getMode(int id){
|
||||||
|
auto it=modes.find(id);
|
||||||
|
if (it != modes.end()) return it->second;
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
int GwChannelList::getJsonSize(){
|
int GwChannelList::getJsonSize(){
|
||||||
int rt=0;
|
int rt=0;
|
||||||
allChannels([&](GwChannel *c){
|
allChannels([&](GwChannel *c){
|
||||||
|
@ -219,6 +322,11 @@ void GwChannelList::fillStatus(GwApi::Status &status){
|
||||||
status.serRx=channel->countRx();
|
status.serRx=channel->countRx();
|
||||||
status.serTx=channel->countTx();
|
status.serTx=channel->countTx();
|
||||||
}
|
}
|
||||||
|
channel=getChannelById(SERIAL2_CHANNEL_ID);
|
||||||
|
if (channel){
|
||||||
|
status.ser2Rx=channel->countRx();
|
||||||
|
status.ser2Tx=channel->countTx();
|
||||||
|
}
|
||||||
channel=getChannelById(MIN_TCP_CHANNEL_ID);
|
channel=getChannelById(MIN_TCP_CHANNEL_ID);
|
||||||
if (channel){
|
if (channel){
|
||||||
status.tcpSerRx=channel->countRx();
|
status.tcpSerRx=channel->countRx();
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
#include <WString.h>
|
#include <WString.h>
|
||||||
#include "GwChannel.h"
|
#include "GwChannel.h"
|
||||||
#include "GwLog.h"
|
#include "GwLog.h"
|
||||||
#include "GWConfig.h"
|
#include "GWConfig.h"
|
||||||
#include "GwJsonDocument.h"
|
#include "GwJsonDocument.h"
|
||||||
#include "GwApi.h"
|
#include "GwApi.h"
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
|
||||||
//NMEA message channels
|
//NMEA message channels
|
||||||
#define N2K_CHANNEL_ID 0
|
#define N2K_CHANNEL_ID 0
|
||||||
#define USB_CHANNEL_ID 1
|
#define USB_CHANNEL_ID 1
|
||||||
#define SERIAL1_CHANNEL_ID 2
|
#define SERIAL1_CHANNEL_ID 2
|
||||||
#define TCP_CLIENT_CHANNEL_ID 3
|
#define SERIAL2_CHANNEL_ID 3
|
||||||
#define MIN_TCP_CHANNEL_ID 4
|
#define TCP_CLIENT_CHANNEL_ID 4
|
||||||
|
#define MIN_TCP_CHANNEL_ID 5
|
||||||
|
|
||||||
#define MIN_USER_TASK 200
|
#define MIN_USER_TASK 200
|
||||||
class GwSocketServer;
|
class GwSocketServer;
|
||||||
|
@ -24,10 +27,11 @@ class GwChannelList{
|
||||||
GwConfigHandler *config;
|
GwConfigHandler *config;
|
||||||
typedef std::vector<GwChannel *> ChannelList;
|
typedef std::vector<GwChannel *> ChannelList;
|
||||||
ChannelList theChannels;
|
ChannelList theChannels;
|
||||||
|
std::map<int,String> modes;
|
||||||
GwSocketServer *sockets;
|
GwSocketServer *sockets;
|
||||||
GwTcpClient *client;
|
GwTcpClient *client;
|
||||||
String serialMode=F("NONE");
|
void addSerial(HardwareSerial *stream,int id,const String &mode,int rx,int tx);
|
||||||
|
void addSerial(HardwareSerial *stream,int id,int type,int rx,int tx);
|
||||||
public:
|
public:
|
||||||
GwChannelList(GwLog *logger, GwConfigHandler *config);
|
GwChannelList(GwLog *logger, GwConfigHandler *config);
|
||||||
typedef std::function<void(GwChannel *)> ChannelAction;
|
typedef std::function<void(GwChannel *)> ChannelAction;
|
||||||
|
@ -40,6 +44,6 @@ class GwChannelList{
|
||||||
//single channel
|
//single channel
|
||||||
GwChannel *getChannelById(int sourceId);
|
GwChannel *getChannelById(int sourceId);
|
||||||
void fillStatus(GwApi::Status &status);
|
void fillStatus(GwApi::Status &status);
|
||||||
|
String getMode(int id);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
#define CFG_MESSAGES
|
||||||
|
#include <Preferences.h>
|
||||||
#include "GWConfig.h"
|
#include "GWConfig.h"
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <MD5Builder.h>
|
#include <MD5Builder.h>
|
||||||
|
#include "GwHardware.h"
|
||||||
|
#include "GwConfigDefImpl.h"
|
||||||
|
|
||||||
#define B(v) (v?"true":"false")
|
#define B(v) (v?"true":"false")
|
||||||
|
|
||||||
|
@ -55,14 +59,20 @@ GwConfigInterface * GwConfigHandler::getConfigItem(const String name, bool dummy
|
||||||
GwConfigHandler::GwConfigHandler(GwLog *logger): GwConfigDefinitions(){
|
GwConfigHandler::GwConfigHandler(GwLog *logger): GwConfigDefinitions(){
|
||||||
this->logger=logger;
|
this->logger=logger;
|
||||||
saltBase=esp_random();
|
saltBase=esp_random();
|
||||||
|
configs=new GwConfigInterface*[getNumConfig()];
|
||||||
|
populateConfigs(configs);
|
||||||
|
prefs=new Preferences();
|
||||||
|
}
|
||||||
|
GwConfigHandler::~GwConfigHandler(){
|
||||||
|
delete prefs;
|
||||||
}
|
}
|
||||||
bool GwConfigHandler::loadConfig(){
|
bool GwConfigHandler::loadConfig(){
|
||||||
prefs.begin(PREF_NAME,true);
|
prefs->begin(PREF_NAME,true);
|
||||||
for (int i=0;i<getNumConfig();i++){
|
for (int i=0;i<getNumConfig();i++){
|
||||||
String v=prefs.getString(configs[i]->getName().c_str(),configs[i]->getDefault());
|
String v=prefs->getString(configs[i]->getName().c_str(),configs[i]->getDefault());
|
||||||
configs[i]->value=v;
|
configs[i]->value=v;
|
||||||
}
|
}
|
||||||
prefs.end();
|
prefs->end();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,19 +87,19 @@ bool GwConfigHandler::updateValue(String name, String value){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
LOG_DEBUG(GwLog::LOG,"update config %s=>%s",name.c_str(),i->isSecret()?"***":value.c_str());
|
LOG_DEBUG(GwLog::LOG,"update config %s=>%s",name.c_str(),i->isSecret()?"***":value.c_str());
|
||||||
prefs.begin(PREF_NAME,false);
|
prefs->begin(PREF_NAME,false);
|
||||||
prefs.putString(i->getName().c_str(),value);
|
prefs->putString(i->getName().c_str(),value);
|
||||||
prefs.end();
|
prefs->end();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool GwConfigHandler::reset(){
|
bool GwConfigHandler::reset(){
|
||||||
LOG_DEBUG(GwLog::LOG,"reset config");
|
LOG_DEBUG(GwLog::LOG,"reset config");
|
||||||
prefs.begin(PREF_NAME,false);
|
prefs->begin(PREF_NAME,false);
|
||||||
for (int i=0;i<getNumConfig();i++){
|
for (int i=0;i<getNumConfig();i++){
|
||||||
prefs.putString(configs[i]->getName().c_str(),configs[i]->getDefault());
|
prefs->putString(configs[i]->getName().c_str(),configs[i]->getDefault());
|
||||||
}
|
}
|
||||||
prefs.end();
|
prefs->end();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
String GwConfigHandler::getString(const String name, String defaultv) const{
|
String GwConfigHandler::getString(const String name, String defaultv) const{
|
||||||
|
@ -97,6 +107,11 @@ String GwConfigHandler::getString(const String name, String defaultv) const{
|
||||||
if (!i) return defaultv;
|
if (!i) return defaultv;
|
||||||
return i->asString();
|
return i->asString();
|
||||||
}
|
}
|
||||||
|
const char * GwConfigHandler::getCString(const String name, const char *defaultv) const{
|
||||||
|
GwConfigInterface *i=getConfigItem(name,false);
|
||||||
|
if (!i) return defaultv;
|
||||||
|
return i->asCString();
|
||||||
|
}
|
||||||
bool GwConfigHandler::getBool(const String name, bool defaultv) const{
|
bool GwConfigHandler::getBool(const String name, bool defaultv) const{
|
||||||
GwConfigInterface *i=getConfigItem(name,false);
|
GwConfigInterface *i=getConfigItem(name,false);
|
||||||
if (!i) return defaultv;
|
if (!i) return defaultv;
|
||||||
|
@ -110,11 +125,12 @@ int GwConfigHandler::getInt(const String name,int defaultv) const{
|
||||||
void GwConfigHandler::stopChanges(){
|
void GwConfigHandler::stopChanges(){
|
||||||
allowChanges=false;
|
allowChanges=false;
|
||||||
}
|
}
|
||||||
bool GwConfigHandler::setValue(String name,String value){
|
bool GwConfigHandler::setValue(String name,String value, bool hide){
|
||||||
if (! allowChanges) return false;
|
if (! allowChanges) return false;
|
||||||
GwConfigInterface *i=getConfigItem(name,false);
|
GwConfigInterface *i=getConfigItem(name,false);
|
||||||
if (!i) return false;
|
if (!i) return false;
|
||||||
i->value=value;
|
i->value=value;
|
||||||
|
i->type=hide?GwConfigInterface::HIDDEN:GwConfigInterface::READONLY;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,6 +175,24 @@ void GwConfigHandler::toHex(unsigned long v, char *buffer, size_t bsize)
|
||||||
buffer[2 * i] = 0;
|
buffer[2 * i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<String> GwConfigHandler::getSpecial() const{
|
||||||
|
std::vector<String> rt;
|
||||||
|
rt.reserve(numSpecial());
|
||||||
|
for (int i=0L;i<getNumConfig();i++){
|
||||||
|
if (configs[i]->getType() != GwConfigInterface::NORMAL){
|
||||||
|
rt.push_back(configs[i]->getName());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
int GwConfigHandler::numSpecial() const{
|
||||||
|
int rt=0;
|
||||||
|
for (int i=0L;i<getNumConfig();i++){
|
||||||
|
if (configs[i]->getType() != GwConfigInterface::NORMAL) rt++;
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
|
||||||
void GwNmeaFilter::handleToken(String token, int index){
|
void GwNmeaFilter::handleToken(String token, int index){
|
||||||
switch(index){
|
switch(index){
|
||||||
case 0:
|
case 0:
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
#ifndef _GWCONFIG_H
|
#ifndef _GWCONFIG_H
|
||||||
#define _GWCONFIG_H
|
#define _GWCONFIG_H
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Preferences.h>
|
|
||||||
#include "GwLog.h"
|
#include "GwLog.h"
|
||||||
#include "GwConfigItem.h"
|
#include "GwConfigItem.h"
|
||||||
#include "GwConfigDefinitions.h"
|
#include "GwConfigDefinitions.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Preferences;
|
||||||
class GwConfigHandler: public GwConfigDefinitions{
|
class GwConfigHandler: public GwConfigDefinitions{
|
||||||
private:
|
private:
|
||||||
Preferences prefs;
|
Preferences *prefs;
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
typedef std::map<String,String> StringMap;
|
typedef std::map<String,String> StringMap;
|
||||||
boolean allowChanges=true;
|
boolean allowChanges=true;
|
||||||
|
GwConfigInterface **configs;
|
||||||
public:
|
public:
|
||||||
public:
|
public:
|
||||||
GwConfigHandler(GwLog *logger);
|
GwConfigHandler(GwLog *logger);
|
||||||
|
@ -26,17 +27,69 @@ class GwConfigHandler: public GwConfigDefinitions{
|
||||||
String getString(const String name,const String defaultv="") const;
|
String getString(const String name,const String defaultv="") const;
|
||||||
bool getBool(const String name,bool defaultv=false) const ;
|
bool getBool(const String name,bool defaultv=false) const ;
|
||||||
int getInt(const String name,int defaultv=0) const;
|
int getInt(const String name,int defaultv=0) const;
|
||||||
|
const char * getCString(const String name, const char *defaultv="") const;
|
||||||
GwConfigInterface * getConfigItem(const String name, bool dummy=false) const;
|
GwConfigInterface * getConfigItem(const String name, bool dummy=false) const;
|
||||||
bool checkPass(String hash);
|
bool checkPass(String hash);
|
||||||
|
std::vector<String> getSpecial() const;
|
||||||
|
int numSpecial() const;
|
||||||
/**
|
/**
|
||||||
* change the value of a config item
|
* change the value of a config item
|
||||||
* will become a noop after stopChanges has been called
|
* will become a noop after stopChanges has been called
|
||||||
* !use with care! no checks of the value
|
* !use with care! no checks of the value
|
||||||
*/
|
*/
|
||||||
bool setValue(String name, String value);
|
bool setValue(String name, String value, bool hide=false);
|
||||||
static void toHex(unsigned long v,char *buffer,size_t bsize);
|
static void toHex(unsigned long v,char *buffer,size_t bsize);
|
||||||
unsigned long getSaltBase(){return saltBase;}
|
unsigned long getSaltBase(){return saltBase;}
|
||||||
|
~GwConfigHandler();
|
||||||
|
bool userChangesAllowed(){return allowChanges;}
|
||||||
|
template <typename T>
|
||||||
|
bool getValue(T & target, const String &name, int defaultv=0){
|
||||||
|
GwConfigInterface *i=getConfigItem(name);
|
||||||
|
if (!i){
|
||||||
|
target=(T)defaultv;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
target=(T)(i->asInt());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool getValue(int &target, const String &name, int defaultv=0){
|
||||||
|
GwConfigInterface *i=getConfigItem(name);
|
||||||
|
if (!i){
|
||||||
|
target=defaultv;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
target=i->asInt();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool getValue(long &target, const String &name, long defaultv=0){
|
||||||
|
GwConfigInterface *i=getConfigItem(name);
|
||||||
|
if (!i){
|
||||||
|
target=defaultv;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
target=i->asInt();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool getValue(bool &target, const String name, bool defaultv=false){
|
||||||
|
GwConfigInterface *i=getConfigItem(name);
|
||||||
|
if (!i){
|
||||||
|
target=defaultv;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
target=i->asBoolean();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool getValue(String &target, const String name, const String &defaultv=""){
|
||||||
|
GwConfigInterface *i=getConfigItem(name);
|
||||||
|
if (!i){
|
||||||
|
target=defaultv;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
target=i->asString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
unsigned long saltBase=0;
|
unsigned long saltBase=0;
|
||||||
|
void populateConfigs(GwConfigInterface **);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
|
@ -5,17 +5,25 @@
|
||||||
|
|
||||||
class GwConfigHandler;
|
class GwConfigHandler;
|
||||||
class GwConfigInterface{
|
class GwConfigInterface{
|
||||||
|
public:
|
||||||
|
typedef enum {
|
||||||
|
NORMAL=0,
|
||||||
|
HIDDEN=1,
|
||||||
|
READONLY=2
|
||||||
|
} ConfigType;
|
||||||
private:
|
private:
|
||||||
String name;
|
String name;
|
||||||
const char * initialValue;
|
const char * initialValue;
|
||||||
String value;
|
String value;
|
||||||
bool secret=false;
|
bool secret=false;
|
||||||
|
ConfigType type=NORMAL;
|
||||||
public:
|
public:
|
||||||
GwConfigInterface(const String &name, const char * initialValue, bool secret=false){
|
GwConfigInterface(const String &name, const char * initialValue, bool secret=false,ConfigType type=NORMAL){
|
||||||
this->name=name;
|
this->name=name;
|
||||||
this->initialValue=initialValue;
|
this->initialValue=initialValue;
|
||||||
this->value=initialValue;
|
this->value=initialValue;
|
||||||
this->secret=secret;
|
this->secret=secret;
|
||||||
|
this->type=type;
|
||||||
}
|
}
|
||||||
virtual String asString() const{
|
virtual String asString() const{
|
||||||
return value;
|
return value;
|
||||||
|
@ -41,6 +49,9 @@ class GwConfigInterface{
|
||||||
String getDefault() const {
|
String getDefault() const {
|
||||||
return initialValue;
|
return initialValue;
|
||||||
}
|
}
|
||||||
|
ConfigType getType() const {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
friend class GwConfigHandler;
|
friend class GwConfigHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,5 +73,7 @@ class GwNmeaFilter{
|
||||||
String toString();
|
String toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define __XSTR(x) __STR(x)
|
||||||
|
#define __STR(x) #x
|
||||||
|
#define __MSG(x) _Pragma (__STR(message (x)))
|
||||||
#endif
|
#endif
|
|
@ -11,9 +11,12 @@ template<class T> class GwCounter{
|
||||||
unsigned long globalFail=0;
|
unsigned long globalFail=0;
|
||||||
String name;
|
String name;
|
||||||
public:
|
public:
|
||||||
GwCounter(String name){
|
GwCounter(const String &name){
|
||||||
this->name=name;
|
this->name=name;
|
||||||
};
|
};
|
||||||
|
void setName(const String &name){
|
||||||
|
this->name=name;
|
||||||
|
}
|
||||||
void reset(){
|
void reset(){
|
||||||
okCounter.clear();
|
okCounter.clear();
|
||||||
failCounter.clear();
|
failCounter.clear();
|
||||||
|
|
|
@ -3,20 +3,86 @@
|
||||||
#ifdef BOARD_TEST
|
#ifdef BOARD_TEST
|
||||||
#include "GwExampleTask.h"
|
#include "GwExampleTask.h"
|
||||||
#include "GwApi.h"
|
#include "GwApi.h"
|
||||||
|
#include "GWConfig.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "N2kMessages.h"
|
||||||
|
#include "GwXdrTypeMappings.h"
|
||||||
|
/**
|
||||||
|
* INVALID!!! - the next interface declaration will not work
|
||||||
|
* as it is not in the correct header file
|
||||||
|
* it is just included here to show you how errors
|
||||||
|
* could be created.
|
||||||
|
* if you call the apiSetExampleNotWorkingIf method
|
||||||
|
* it will always return false
|
||||||
|
*/
|
||||||
|
class ExampleNotWorkingIf: public GwApi::TaskInterfaces::Base{
|
||||||
|
public:
|
||||||
|
int someValue=99;
|
||||||
|
};
|
||||||
|
DECLARE_TASKIF(ExampleNotWorkingIf);
|
||||||
|
void exampleTask(GwApi *param);
|
||||||
/**
|
/**
|
||||||
* an init function that ist being called before other initializations from the core
|
* an init function that ist being called before other initializations from the core
|
||||||
*/
|
*/
|
||||||
void exampleInit(GwApi *api){
|
void exampleInit(GwApi *api){
|
||||||
api->getLogger()->logDebug(GwLog::LOG,"example init running");
|
api->getLogger()->logDebug(GwLog::LOG,"example init running");
|
||||||
//this example is a more or less useless example how you could set some
|
// make the task known to the core
|
||||||
//config value to a fixed value
|
// the task function should not return (unless you delete the task - see example code)
|
||||||
//you can only set config values within the init function
|
const String taskName("exampleTask");
|
||||||
//you could also compute this value from some own configuration
|
api->addUserTask(exampleTask, taskName, 4000);
|
||||||
//for this example it would make a lot of sense to declare a capability
|
// this would create our task with a stack size of 4000 bytes
|
||||||
//to hide this config item from the UI - see header file
|
|
||||||
api->getConfig()->setValue(api->getConfig()->minXdrInterval,"50");
|
// we declare some capabilities that we can
|
||||||
|
// use in config.json to only show some
|
||||||
|
// elements when this capability is set correctly
|
||||||
|
api->addCapability("testboard", "true");
|
||||||
|
api->addCapability("testboard2", "true");
|
||||||
|
// hide some config value
|
||||||
|
// and force it's default value
|
||||||
|
auto current=api->getConfig()->getConfigItem(GwConfigDefinitions::minXdrInterval,false);
|
||||||
|
String defaultXdrInt="50";
|
||||||
|
if (current){
|
||||||
|
defaultXdrInt=current->getDefault();
|
||||||
|
}
|
||||||
|
//with the true parameter this config value will be hidden
|
||||||
|
//if you would like the user to be able to see this item, omit the "true", the config value will be read only
|
||||||
|
api->getConfig()->setValue(GwConfigDefinitions::minXdrInterval,defaultXdrInt,true);
|
||||||
|
// example for a user defined help url that will be shown when clicking the help button
|
||||||
|
api->addCapability("HELP_URL", "https://www.wellenvogel.de");
|
||||||
|
|
||||||
|
//we would like to store data with the interfaces that we defined
|
||||||
|
//the name must match the one we used for addUserTask
|
||||||
|
api->taskInterfaces()->claim<ExampleTaskIf>(taskName);
|
||||||
|
//not working interface
|
||||||
|
if (!api->taskInterfaces()->claim<ExampleNotWorkingIf>(taskName)){
|
||||||
|
api->getLogger()->logDebug(GwLog::ERROR,"unable to claim ExampleNotWorkingIf");
|
||||||
|
}
|
||||||
|
//check if we should simulate some voltage measurements
|
||||||
|
//add an XDR mapping in this case
|
||||||
|
String voltageTransducer=api->getConfig()->getString(GwConfigDefinitions::exTransducer);
|
||||||
|
if (!voltageTransducer.isEmpty()){
|
||||||
|
int instance=api->getConfig()->getInt(GwConfigDefinitions::exInstanceId);
|
||||||
|
GwXDRMappingDef xdr;
|
||||||
|
//we send a battery message - so it is category battery
|
||||||
|
xdr.category=GwXDRCategory::XDRBAT;
|
||||||
|
//we only need a conversion from N2K to NMEA0183
|
||||||
|
xdr.direction=GwXDRMappingDef::Direction::M_FROM2K;
|
||||||
|
//you can find the XDR field and selector definitions
|
||||||
|
//in the generated GwXdrTypeMappings.h
|
||||||
|
xdr.field=GWXDRFIELD_BATTERY_BATTERYVOLTAGE;
|
||||||
|
//xdr.selector=-1; //refer to xdrconfig.json - there is no selector under Battery, so we can leave it empty
|
||||||
|
//we just map exactly our instance
|
||||||
|
xdr.instanceMode=GwXDRMappingDef::IS_SINGLE;
|
||||||
|
|
||||||
|
//those are the user configured values
|
||||||
|
//this instance is the one we use for the battery instance when we set up
|
||||||
|
//the N2K message
|
||||||
|
xdr.instanceId=instance;
|
||||||
|
xdr.xdrName=voltageTransducer;
|
||||||
|
if (!api->addXdrMapping(xdr)){
|
||||||
|
api->getLogger()->logDebug(GwLog::ERROR,"unable to set our xdr mapping %s",xdr.toString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#define INVALID_COORD -99999
|
#define INVALID_COORD -99999
|
||||||
class GetBoatDataRequest: public GwMessage{
|
class GetBoatDataRequest: public GwMessage{
|
||||||
|
@ -97,6 +163,15 @@ void exampleTask(GwApi *api){
|
||||||
GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName);
|
GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName);
|
||||||
GwApi::BoatValue *valueList[]={longitude,latitude,testValue};
|
GwApi::BoatValue *valueList[]={longitude,latitude,testValue};
|
||||||
GwApi::Status status;
|
GwApi::Status status;
|
||||||
|
int counter=api->addCounter("usertest");
|
||||||
|
int apiResult=0;
|
||||||
|
ExampleTaskIf e1=api->taskInterfaces()->get<ExampleTaskIf>(apiResult);
|
||||||
|
LOG_DEBUG(GwLog::LOG,"exampleIf before rs=%d,v=%d,s=%s",apiResult,e1.count,e1.someValue.c_str());
|
||||||
|
ExampleNotWorkingIf nw1;
|
||||||
|
bool nwrs=api->taskInterfaces()->set(nw1);
|
||||||
|
LOG_DEBUG(GwLog::LOG,"exampleNotWorking update returned %d",(int)nwrs);
|
||||||
|
String voltageTransducer=api->getConfig()->getString(GwConfigDefinitions::exTransducer);
|
||||||
|
int voltageInstance=api->getConfig()->getInt(GwConfigDefinitions::exInstanceId);
|
||||||
while(true){
|
while(true){
|
||||||
delay(1000);
|
delay(1000);
|
||||||
/*
|
/*
|
||||||
|
@ -194,7 +269,28 @@ void exampleTask(GwApi *api){
|
||||||
status.tcpClTx,
|
status.tcpClTx,
|
||||||
status.n2kRx,
|
status.n2kRx,
|
||||||
status.n2kTx);
|
status.n2kTx);
|
||||||
|
//increment some counter
|
||||||
|
api->increment(counter,"Test");
|
||||||
|
ExampleTaskIf e2=api->taskInterfaces()->get<ExampleTaskIf>(apiResult);
|
||||||
|
LOG_DEBUG(GwLog::LOG,"exampleIf before update rs=%d,v=%d,s=%s",apiResult,e2.count,e2.someValue.c_str());
|
||||||
|
e1.count+=1;
|
||||||
|
e1.someValue="running";
|
||||||
|
bool rs=api->taskInterfaces()->set(e1);
|
||||||
|
LOG_DEBUG(GwLog::LOG,"exampleIf update rs=%d,v=%d,s=%s",(int)rs,e1.count,e1.someValue.c_str());
|
||||||
|
ExampleTaskIf e3=api->taskInterfaces()->get<ExampleTaskIf>(apiResult);
|
||||||
|
LOG_DEBUG(GwLog::LOG,"exampleIf after update rs=%d,v=%d,s=%s",apiResult,e3.count,e3.someValue.c_str());
|
||||||
|
if (!voltageTransducer.isEmpty()){
|
||||||
|
//simulate some voltage measurements...
|
||||||
|
double offset=100.0*(double)std::rand()/RAND_MAX - 50.0;
|
||||||
|
double simVoltage=(1200.0+offset)/100;
|
||||||
|
LOG_DEBUG(GwLog::LOG,"simulated voltage %f",(float)simVoltage);
|
||||||
|
tN2kMsg msg;
|
||||||
|
SetN2kDCBatStatus(msg,voltageInstance,simVoltage);
|
||||||
|
//we send out an N2K message
|
||||||
|
//and as we added an XDR mapping, we will see this in the data dashboard
|
||||||
|
//and on the NMEA0183 stream
|
||||||
|
api->sendN2kMessage(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
|
|
||||||
|
|
|
@ -2,50 +2,30 @@
|
||||||
#include "GwApi.h"
|
#include "GwApi.h"
|
||||||
//we only compile for some boards
|
//we only compile for some boards
|
||||||
#ifdef BOARD_TEST
|
#ifdef BOARD_TEST
|
||||||
|
//we could add the following defines also in our local platformio.ini
|
||||||
|
//CAN base
|
||||||
|
#define M5_CAN_KIT
|
||||||
|
//RS485 on groove
|
||||||
|
#define SERIAL_GROOVE_485
|
||||||
|
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_22
|
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_19
|
|
||||||
//if using tail485
|
|
||||||
#define GWSERIAL_TX 26
|
|
||||||
#define GWSERIAL_RX 32
|
|
||||||
#define GWSERIAL_MODE "UNI"
|
|
||||||
#define GWBUTTON_PIN GPIO_NUM_39
|
|
||||||
#define GWBUTTON_ACTIVE LOW
|
|
||||||
//if GWBUTTON_PULLUPDOWN we enable a pulup/pulldown
|
|
||||||
#define GWBUTTON_PULLUPDOWN
|
|
||||||
//led handling
|
|
||||||
//if we define GWLED_FASTNET the arduino fastnet lib is used
|
|
||||||
#define GWLED_FASTLED
|
|
||||||
#define GWLED_TYPE SK6812
|
|
||||||
//color schema for fastled
|
|
||||||
#define GWLED_SCHEMA GRB
|
|
||||||
#define GWLED_PIN GPIO_NUM_27
|
|
||||||
//brightness 0...255
|
|
||||||
#define GWLED_BRIGHTNESS 64
|
|
||||||
|
|
||||||
void exampleTask(GwApi *param);
|
|
||||||
void exampleInit(GwApi *param);
|
void exampleInit(GwApi *param);
|
||||||
//make the task known to the core
|
|
||||||
//the task function should not return (unless you delete the task - see example code)
|
|
||||||
//DECLARE_USERTASK(exampleTask)
|
|
||||||
//if your task is not happy with the default 2000 bytes of stack, replace the DECLARE_USERTASK
|
|
||||||
DECLARE_USERTASK_PARAM(exampleTask,4000);
|
|
||||||
//this would create our task with a stack size of 4000 bytes
|
|
||||||
|
|
||||||
|
|
||||||
//let the core call an init function before the
|
//let the core call an init function before the
|
||||||
//N2K Stuff and the communication is set up
|
//N2K Stuff and the communication is set up
|
||||||
//normally you should not need this at all
|
//especially this init function will register the real task at the core
|
||||||
|
//this gives you some flexibility to decide based on config or defines whether you
|
||||||
|
//really want to start the task or not
|
||||||
//this function must return when done - otherwise the core will not start up
|
//this function must return when done - otherwise the core will not start up
|
||||||
DECLARE_INITFUNCTION(exampleInit);
|
DECLARE_INITFUNCTION(exampleInit);
|
||||||
//we declare a capability that we can
|
|
||||||
//use in config.json to only show some
|
/**
|
||||||
//elements when this capability is set correctly
|
* an interface for the example task
|
||||||
DECLARE_CAPABILITY(testboard,true);
|
*/
|
||||||
DECLARE_CAPABILITY(testboard2,true);
|
class ExampleTaskIf : public GwApi::TaskInterfaces::Base{
|
||||||
//hide some config value
|
public:
|
||||||
//just set HIDE + the name of the config item to true
|
long count=0;
|
||||||
DECLARE_CAPABILITY(HIDEminXdrInterval,true);
|
String someValue;
|
||||||
//example for a user defined help url that will be shown when clicking the help button
|
};
|
||||||
DECLARE_STRING_CAPABILITY(HELP_URL,"https://www.wellenvogel.de");
|
DECLARE_TASKIF(ExampleTaskIf);
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -15,17 +15,52 @@ Files
|
||||||
This file is completely optional.
|
This file is completely optional.
|
||||||
You only need this if you want to
|
You only need this if you want to
|
||||||
extend the base configuration - we add a dummy library here and define one additional build environment (board)
|
extend the base configuration - we add a dummy library here and define one additional build environment (board)
|
||||||
* [GwExampleTask.h](GwExampleTask.h) the name of this include must match the name of the directory (ignoring case) with a "gw" in front. This file includes our special hardware definitions and registers our task at the core (DECLARE_USERTASK in the code). Avoid including headers from other libraries in this file as this could interfere with the main code. Just only include them in your .cpp files (or in other headers).
|
* [GwExampleTask.h](GwExampleTask.h) the name of this include must match the name of the directory (ignoring case) with a "gw" in front. This file includes our special hardware definitions and registers our task at the core.<br>
|
||||||
|
This registration can be done statically using [DECLARE_USERTASK](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/api/GwApi.h#L202) in the header file. <br>
|
||||||
|
As an alternative we just only register an [initialization function](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/exampletask/GwExampleTask.h#L19) using DECLARE_INITFUNCTION and later on register the task function itself via the [API](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/exampletask/GwExampleTask.cpp#L32).<br>
|
||||||
|
This gives you more flexibility - maybe you only want to start your task if certain config values are set.<br>
|
||||||
|
The init function itself should not interact with external hardware and should run fast. It needs to return otherwise the main code will not start up.<br>
|
||||||
|
Avoid including headers from other libraries in this file as this could interfere with the main code. Just only include them in your .cpp files (or in other headers).
|
||||||
Optionally it can define some capabilities (using DECLARE_CAPABILITY) that can be used in the config UI (see below)
|
Optionally it can define some capabilities (using DECLARE_CAPABILITY) that can be used in the config UI (see below)
|
||||||
There are some special capabilities you can set:
|
There are some special capabilities you can set:
|
||||||
* HIDEsomeName: will hide the configItem "someName"
|
* HIDEsomeName: will hide the configItem "someName"
|
||||||
* HELP_URL: will set the url that is loaded when clicking the HELP tab (user DECLARE_STRING_CAPABILITY)
|
* HELP_URL: will set the url that is loaded when clicking the HELP tab (user DECLARE_STRING_CAPABILITY)<br>
|
||||||
|
|
||||||
* [GwExampleTaks.cpp](GwExampleTask.cpp) includes the implementation of our task. This tasks runs in an own thread - see the comments in the code.
|
* [GwExampleTaks.cpp](GwExampleTask.cpp) includes the implementation of our task. This tasks runs in an own thread - see the comments in the code.
|
||||||
We can have as many cpp (and header files) as we need to structure our code.
|
We can have as many cpp (and header files) as we need to structure our code.
|
||||||
* [config.json](config.json)<br>
|
* [config.json](config.json)<br>
|
||||||
This file allows to add some config definitions that are needed for our task. For the possible options have a look at the global [config.json](../../web/config.json). Be careful not to overwrite config defitions from the global file. A good practice wood be to prefix the names of definitions with parts of the library name. Always put them in a separate category so that they do not interfere with the system ones.
|
This file allows to add some config definitions that are needed for our task. For the possible options have a look at the global [config.json](../../web/config.json). Be careful not to overwrite config defitions from the global file. A good practice wood be to prefix the names of definitions with parts of the library name. Always put them in a separate category so that they do not interfere with the system ones.
|
||||||
The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)).
|
The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)).
|
||||||
|
|
||||||
|
Interfaces
|
||||||
|
----------
|
||||||
|
The task init function and the task function interact with the core using an [API](../api/GwApi.h) that they get when started.
|
||||||
|
The API has a couple of functions that only can be used inside an init function and others that can only be used inside a task function.
|
||||||
|
Avoid any other use of core functions other then via the API - otherwise you carefully have to consider thread synchronization!
|
||||||
|
The API allows you to
|
||||||
|
* access config data
|
||||||
|
* write logs
|
||||||
|
* send NMEA2000 messages
|
||||||
|
* send NMEA0183 messages
|
||||||
|
* get the currently available data values (as shown at the data tab)
|
||||||
|
* get some status information from the core
|
||||||
|
* send some requests to the core (only for very special functionality)
|
||||||
|
* add and increment counter (starting from 20231105)
|
||||||
|
* add some fixed [XDR](../../doc/XdrMappings.md) Mapping - see [example](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/exampletask/GwExampleTask.cpp#L63).
|
||||||
|
* add capabilities (since 20231105 - as an alternative to a static DECLARE_CAPABILITY )
|
||||||
|
* add a user task (since 20231105 - as an alternative to a static DECLARE_USERTASK)
|
||||||
|
* store or read task interface data (see below)
|
||||||
|
|
||||||
|
|
||||||
|
__Interfacing between Task__
|
||||||
|
|
||||||
|
Sometimes you may want to exchange data between different user tasks.<br> As this needs thread sychronization (and a place to store this data) there is an interface to handle this in a safe manner (since 20231105).<br>
|
||||||
|
The task that would like to provide some data for others must declare a [class](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/exampletask/GwExampleTask.h#L24) that stores this data. This must be declared in the task header file and you need to make this known to the code using DECLARE_TASKIF.<br>
|
||||||
|
Before you can use this interface for writing you need to ["claim"](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/exampletask/GwExampleTask.cpp#L55) it - this prevents other tasks from also writing this data.
|
||||||
|
Later on you are able to [write](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/exampletask/GwExampleTask.cpp#L278) this data via the api.<br>
|
||||||
|
Any other task that is interested in your data can read it at any time. The read function will provide an update counter - so the reading side can easily see if the data has been written.
|
||||||
|
The core uses this concept for the interworking between the [button task](../buttontask/) - writing - and the [led task](../ledtask/) - [reading](https://github.com/wellenvogel/esp32-nmea2000/blob/9b955d135d74937a60f2926e8bfb9395585ff8cd/lib/ledtask/GwLedTask.cpp#L52).
|
||||||
|
|
||||||
Hints
|
Hints
|
||||||
-----
|
-----
|
||||||
Just be careful not to interfere with C symbols from the core - so it is a good practice to prefix your files and class like in the example.
|
Just be careful not to interfere with C symbols from the core - so it is a good practice to prefix your files and class like in the example.
|
||||||
|
@ -47,7 +82,4 @@ Files
|
||||||
|
|
||||||
By following the hints in this doc the merge should always succeed without conflicts.
|
By following the hints in this doc the merge should always succeed without conflicts.
|
||||||
|
|
||||||
Future Plans
|
|
||||||
------------
|
|
||||||
If there will be a need we can extend this extension API by means of adding specific java script code and css for the UI.
|
|
||||||
|
|
||||||
|
|
|
@ -20,5 +20,30 @@
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"testboard":"true"
|
"testboard":"true"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exTransducer",
|
||||||
|
"label": "voltage transducer name",
|
||||||
|
"type": "String",
|
||||||
|
"default": "",
|
||||||
|
"description": "set the name for the xdr transducer for the simulated voltage, leave empty to disable ",
|
||||||
|
"category": "example",
|
||||||
|
"capabilities": {
|
||||||
|
"testboard":"true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "exInstanceId",
|
||||||
|
"label": "voltage instance id",
|
||||||
|
"type": "number",
|
||||||
|
"default": 99,
|
||||||
|
"description": "the N2K instance id for the simulated voltage ",
|
||||||
|
"category": "example",
|
||||||
|
"min": 0,
|
||||||
|
"max": 255,
|
||||||
|
"check": "checkMinMax",
|
||||||
|
"capabilities": {
|
||||||
|
"testboard":"true"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -7,7 +7,6 @@
|
||||||
board = m5stack-atom
|
board = m5stack-atom
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
own_lib
|
|
||||||
build_flags=
|
build_flags=
|
||||||
-D BOARD_TEST
|
-D BOARD_TEST
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
|
|
|
@ -64,7 +64,6 @@ void GwWebServer::begin(){
|
||||||
GwWebServer::~GwWebServer(){
|
GwWebServer::~GwWebServer(){
|
||||||
server->end();
|
server->end();
|
||||||
delete server;
|
delete server;
|
||||||
vQueueDelete(queue);
|
|
||||||
}
|
}
|
||||||
void GwWebServer::handleAsyncWebRequest(AsyncWebServerRequest *request, GwRequestMessage *msg)
|
void GwWebServer::handleAsyncWebRequest(AsyncWebServerRequest *request, GwRequestMessage *msg)
|
||||||
{
|
{
|
|
@ -11,10 +11,30 @@ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
||||||
}
|
}
|
||||||
void GwWifi::setup(){
|
void GwWifi::setup(){
|
||||||
LOG_DEBUG(GwLog::LOG,"Wifi setup");
|
LOG_DEBUG(GwLog::LOG,"Wifi setup");
|
||||||
|
IPAddress defaultAddr(192,168,15,1);
|
||||||
IPAddress AP_local_ip(192, 168, 15, 1); // Static address for AP
|
IPAddress AP_local_ip; // Static address for AP
|
||||||
IPAddress AP_gateway(192, 168, 15, 1);
|
const String apip=config->getString(config->apIp);
|
||||||
IPAddress AP_subnet(255, 255, 255, 0);
|
bool cfgIpOk=false;
|
||||||
|
if (!apip.isEmpty()){
|
||||||
|
cfgIpOk= AP_local_ip.fromString(apip);
|
||||||
|
}
|
||||||
|
if (! cfgIpOk){
|
||||||
|
AP_local_ip=IPAddress(192,168,15,1);
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unable to set access point IP %s, falling back to %s",
|
||||||
|
apip.c_str(),AP_local_ip.toString().c_str());
|
||||||
|
}
|
||||||
|
IPAddress AP_gateway(AP_local_ip);
|
||||||
|
bool maskOk=false;
|
||||||
|
IPAddress AP_subnet;
|
||||||
|
const String apMask=config->getString(config->apMask);
|
||||||
|
if (!apMask.isEmpty()){
|
||||||
|
maskOk=AP_subnet.fromString(apMask);
|
||||||
|
}
|
||||||
|
if (! maskOk){
|
||||||
|
AP_subnet=IPAddress(255, 255, 255, 0);
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unable to set access point mask %s, falling back to %s",
|
||||||
|
apMask.c_str(),AP_subnet.toString().c_str());
|
||||||
|
}
|
||||||
WiFi.mode(WIFI_MODE_APSTA); //enable both AP and client
|
WiFi.mode(WIFI_MODE_APSTA); //enable both AP and client
|
||||||
const char *ssid=config->getConfigItem(config->systemName)->asCString();
|
const char *ssid=config->getConfigItem(config->systemName)->asCString();
|
||||||
if (fixedApPass){
|
if (fixedApPass){
|
||||||
|
@ -33,7 +53,7 @@ void GwWifi::setup(){
|
||||||
lastApAccess=millis();
|
lastApAccess=millis();
|
||||||
apShutdownTime=config->getConfigItem(config->stopApTime)->asInt() * 60;
|
apShutdownTime=config->getConfigItem(config->stopApTime)->asInt() * 60;
|
||||||
if (apShutdownTime < 120 && apShutdownTime != 0) apShutdownTime=120; //min 2 minutes
|
if (apShutdownTime < 120 && apShutdownTime != 0) apShutdownTime=120; //min 2 minutes
|
||||||
LOG_DEBUG(GwLog::LOG,"GWWIFI: AP auto shutdown %s (%ds)",apShutdownTime> 0?"enabled":"disabled",apShutdownTime);
|
LOG_DEBUG(GwLog::ERROR,"GWWIFI: AP auto shutdown %s (%ds)",apShutdownTime> 0?"enabled":"disabled",apShutdownTime);
|
||||||
apShutdownTime=apShutdownTime*1000; //ms
|
apShutdownTime=apShutdownTime*1000; //ms
|
||||||
clientIsConnected=false;
|
clientIsConnected=false;
|
||||||
connectInternal();
|
connectInternal();
|
||||||
|
@ -65,7 +85,7 @@ void GwWifi::loop(){
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (! clientIsConnected){
|
if (! clientIsConnected){
|
||||||
LOG_DEBUG(GwLog::LOG,"client %s now connected",wifiSSID->asCString());
|
LOG_DEBUG(GwLog::LOG,"wifiClient %s now connected to",wifiSSID->asCString());
|
||||||
clientIsConnected=true;
|
clientIsConnected=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +95,7 @@ void GwWifi::loop(){
|
||||||
lastApAccess=millis();
|
lastApAccess=millis();
|
||||||
}
|
}
|
||||||
if ((lastApAccess + apShutdownTime) < millis()){
|
if ((lastApAccess + apShutdownTime) < millis()){
|
||||||
LOG_DEBUG(GwLog::LOG,"GWWIFI: shutdown AP");
|
LOG_DEBUG(GwLog::ERROR,"GWWIFI: shutdown AP");
|
||||||
WiFi.softAPdisconnect(true);
|
WiFi.softAPdisconnect(true);
|
||||||
apActive=false;
|
apActive=false;
|
||||||
}
|
}
|
|
@ -11,108 +11,124 @@
|
||||||
License along with this library; if not, write to the Free Software
|
License along with this library; if not, write to the Free Software
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
#ifdef _NOGWHARDWAREUT
|
||||||
|
#error "you are not allowed to include GwHardware.h in your user task header"
|
||||||
|
#endif
|
||||||
#ifndef _GWHARDWARE_H
|
#ifndef _GWHARDWARE_H
|
||||||
#define _GWHARDWARE_H
|
#define _GWHARDWARE_H
|
||||||
|
#define GWSERIAL_TYPE_UNI 1
|
||||||
|
#define GWSERIAL_TYPE_BI 2
|
||||||
|
#define GWSERIAL_TYPE_RX 3
|
||||||
|
#define GWSERIAL_TYPE_TX 4
|
||||||
|
#include <GwConfigItem.h>
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
#include "GwAppInfo.h"
|
||||||
#include "GwUserTasks.h"
|
#include "GwUserTasks.h"
|
||||||
|
|
||||||
//SERIAL_MODE can be: UNI (RX or TX only), BI (both), RX, TX
|
//general definitions for M5AtomLite
|
||||||
//board specific pins
|
//hint for groove pins:
|
||||||
|
//according to some schematics the numbering is 1,2,3(VCC),4(GND)
|
||||||
|
#ifdef PLATFORM_BOARD_M5STACK_ATOM
|
||||||
|
#define GROOVE_PIN_2 GPIO_NUM_26
|
||||||
|
#define GROOVE_PIN_1 GPIO_NUM_32
|
||||||
|
#define GWBUTTON_PIN GPIO_NUM_39
|
||||||
|
#define GWLED_FASTLED
|
||||||
|
#define GWLED_TYPE SK6812
|
||||||
|
//color schema for fastled
|
||||||
|
#define GWLED_SCHEMA GRB
|
||||||
|
#define GWLED_PIN GPIO_NUM_27
|
||||||
|
#define GWBUTTON_ACTIVE LOW
|
||||||
|
//if GWBUTTON_PULLUPDOWN we enable a pulup/pulldown
|
||||||
|
#define GWBUTTON_PULLUPDOWN
|
||||||
|
#define BOARD_LEFT1 GPIO_NUM_22
|
||||||
|
#define BOARD_LEFT2 GPIO_NUM_19
|
||||||
|
#define USBSerial Serial
|
||||||
|
#endif
|
||||||
|
//general definitiones for M5AtomS3
|
||||||
|
#ifdef PLATFORM_BOARD_M5STACK_ATOMS3
|
||||||
|
#define GROOVE_PIN_2 GPIO_NUM_2
|
||||||
|
#define GROOVE_PIN_1 GPIO_NUM_1
|
||||||
|
#define GWBUTTON_PIN GPIO_NUM_41
|
||||||
|
#define GWLED_FASTLED
|
||||||
|
#define GWLED_TYPE WS2812
|
||||||
|
//color schema for fastled
|
||||||
|
#define GWLED_SCHEMA GRB
|
||||||
|
#define GWLED_PIN GPIO_NUM_35
|
||||||
|
#define GWBUTTON_ACTIVE LOW
|
||||||
|
//if GWBUTTON_PULLUPDOWN we enable a pulup/pulldown
|
||||||
|
#define GWBUTTON_PULLUPDOWN
|
||||||
|
#define BOARD_LEFT1 GPIO_NUM_5
|
||||||
|
#define BOARD_LEFT2 GPIO_NUM_6
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//M5Stick C
|
||||||
|
#ifdef PLATFORM_BOARD_M5STICK_C
|
||||||
|
#define GROOVE_PIN_2 GPIO_NUM_32
|
||||||
|
#define GROOVE_PIN_1 GPIO_NUM_31
|
||||||
|
#define USBSerial Serial
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//NodeMCU 32 S
|
||||||
|
#ifdef PLATFORM_BOARD_NODEMCU_32S
|
||||||
|
#define USBSerial Serial
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef BOARD_M5ATOM
|
#ifdef BOARD_M5ATOM
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_22
|
#define M5_CAN_KIT
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_19
|
|
||||||
//150mA if we power from the bus
|
//150mA if we power from the bus
|
||||||
#define N2K_LOAD_LEVEL 3
|
#define N2K_LOAD_LEVEL 3
|
||||||
//if using tail485
|
//if using tail485
|
||||||
#define GWSERIAL_TX 26
|
#define SERIAL_GROOVE_485
|
||||||
#define GWSERIAL_RX 32
|
|
||||||
#define GWSERIAL_MODE "UNI"
|
|
||||||
#define GWBUTTON_PIN GPIO_NUM_39
|
|
||||||
#define GWBUTTON_ACTIVE LOW
|
|
||||||
//if GWBUTTON_PULLUPDOWN we enable a pulup/pulldown
|
|
||||||
#define GWBUTTON_PULLUPDOWN
|
|
||||||
//led handling
|
|
||||||
//if we define GWLED_FASTNET the arduino fastnet lib is used
|
|
||||||
#define GWLED_FASTLED
|
|
||||||
#define GWLED_TYPE SK6812
|
|
||||||
//color schema for fastled
|
|
||||||
#define GWLED_SCHEMA GRB
|
|
||||||
#define GWLED_PIN GPIO_NUM_27
|
|
||||||
//brightness 0...255
|
|
||||||
#define GWLED_BRIGHTNESS 64
|
|
||||||
#endif
|
|
||||||
#ifdef BOARD_M5ATOM_CANUNIT
|
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_26
|
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_32
|
|
||||||
#define GWBUTTON_PIN GPIO_NUM_39
|
|
||||||
#define GWBUTTON_ACTIVE LOW
|
|
||||||
//if GWBUTTON_PULLUPDOWN we enable a pulup/pulldown
|
|
||||||
#define GWBUTTON_PULLUPDOWN
|
|
||||||
//led handling
|
|
||||||
//if we define GWLED_FASTNET the arduino fastnet lib is used
|
|
||||||
#define GWLED_FASTLED
|
|
||||||
#define GWLED_TYPE SK6812
|
|
||||||
//color schema for fastled
|
|
||||||
#define GWLED_SCHEMA GRB
|
|
||||||
#define GWLED_PIN GPIO_NUM_27
|
|
||||||
//brightness 0...255
|
//brightness 0...255
|
||||||
#define GWLED_BRIGHTNESS 64
|
#define GWLED_BRIGHTNESS 64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef BOARD_M5ATOM_RS232_CANUNIT
|
#ifdef BOARD_M5ATOMS3
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_26
|
#define M5_CAN_KIT
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_32
|
//150mA if we power from the bus
|
||||||
//if using rs232
|
#define N2K_LOAD_LEVEL 3
|
||||||
#define GWSERIAL_TX 19
|
//if using tail485
|
||||||
#define GWSERIAL_RX 22
|
#define SERIAL_GROOVE_485
|
||||||
#define GWSERIAL_MODE "BI"
|
|
||||||
#define GWBUTTON_PIN GPIO_NUM_39
|
|
||||||
#define GWBUTTON_ACTIVE LOW
|
|
||||||
//if GWBUTTON_PULLUPDOWN we enable a pulup/pulldown
|
|
||||||
#define GWBUTTON_PULLUPDOWN
|
|
||||||
//led handling
|
|
||||||
//if we define GWLED_FASTNET the arduino fastnet lib is used
|
|
||||||
#define GWLED_FASTLED
|
|
||||||
#define GWLED_TYPE SK6812
|
|
||||||
//color schema for fastled
|
|
||||||
#define GWLED_SCHEMA GRB
|
|
||||||
#define GWLED_PIN GPIO_NUM_27
|
|
||||||
//brightness 0...255
|
//brightness 0...255
|
||||||
#define GWLED_BRIGHTNESS 64
|
#define GWLED_BRIGHTNESS 64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef BOARD_M5ATOM_CANUNIT
|
||||||
|
#define M5_CANUNIT
|
||||||
|
#define GWLED_BRIGHTNESS 64
|
||||||
|
//150mA if we power from the bus
|
||||||
|
#define N2K_LOAD_LEVEL 3
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef BOARD_M5ATOMS3_CANUNIT
|
||||||
|
#define M5_CANUNIT
|
||||||
|
#define GWLED_BRIGHTNESS 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef BOARD_M5ATOM_RS232_CANUNIT
|
||||||
|
#define M5_CANUNIT
|
||||||
|
#define M5_SERIAL_KIT_232
|
||||||
|
#define GWLED_BRIGHTNESS 64
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef BOARD_M5ATOM_RS485_CANUNIT
|
#ifdef BOARD_M5ATOM_RS485_CANUNIT
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_26
|
#define M5_SERIAL_KIT_485
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_32
|
#define M5_CANUNIT
|
||||||
//if using rs232
|
|
||||||
#define GWSERIAL_TX 19
|
|
||||||
#define GWSERIAL_RX 22
|
|
||||||
#define GWSERIAL_MODE "UNI"
|
|
||||||
#define GWBUTTON_PIN GPIO_NUM_39
|
|
||||||
#define GWBUTTON_ACTIVE LOW
|
|
||||||
//if GWBUTTON_PULLUPDOWN we enable a pulup/pulldown
|
|
||||||
#define GWBUTTON_PULLUPDOWN
|
|
||||||
//led handling
|
|
||||||
//if we define GWLED_FASTNET the arduino fastnet lib is used
|
|
||||||
#define GWLED_FASTLED
|
|
||||||
#define GWLED_TYPE SK6812
|
|
||||||
//color schema for fastled
|
|
||||||
#define GWLED_SCHEMA GRB
|
|
||||||
#define GWLED_PIN GPIO_NUM_27
|
|
||||||
//brightness 0...255
|
|
||||||
#define GWLED_BRIGHTNESS 64
|
#define GWLED_BRIGHTNESS 64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#ifdef BOARD_M5STICK_CANUNIT
|
#ifdef BOARD_M5STICK_CANUNIT
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_32
|
#define M5_CANUNIT
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_33
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef BOARD_HOMBERGER
|
#ifdef BOARD_HOMBERGER
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_5
|
#define ESP32_CAN_TX_PIN GPIO_NUM_5
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_4
|
#define ESP32_CAN_RX_PIN GPIO_NUM_4
|
||||||
//serial input only
|
//serial input only
|
||||||
#define GWSERIAL_RX 16
|
#define GWSERIAL_RX GPIO_NUM_16
|
||||||
#define GWSERIAL_MODE "RX"
|
#define GWSERIAL_TYPE GWSERIAL_TYPE_RX
|
||||||
|
|
||||||
#define GWBUTTON_PIN GPIO_NUM_0
|
#define GWBUTTON_PIN GPIO_NUM_0
|
||||||
#define GWBUTTON_ACTIVE LOW
|
#define GWBUTTON_ACTIVE LOW
|
||||||
|
@ -120,4 +136,190 @@
|
||||||
#define GWBUTTON_PULLUPDOWN
|
#define GWBUTTON_PULLUPDOWN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//M5 Serial (Atomic RS232 Base)
|
||||||
|
#ifdef M5_SERIAL_KIT_232
|
||||||
|
#define _GWM5_BOARD
|
||||||
|
#define GWSERIAL_TX BOARD_LEFT2
|
||||||
|
#define GWSERIAL_RX BOARD_LEFT1
|
||||||
|
#define GWSERIAL_TYPE GWSERIAL_TYPE_BI
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//M5 Serial (Atomic RS485 Base)
|
||||||
|
#ifdef M5_SERIAL_KIT_485
|
||||||
|
#ifdef _GWM5_BOARD
|
||||||
|
#error "can only define one M5 base"
|
||||||
|
#endif
|
||||||
|
#define _GWM5_BOARD
|
||||||
|
#define GWSERIAL_TX BOARD_LEFT2
|
||||||
|
#define GWSERIAL_RX BOARD_LEFT1
|
||||||
|
#define GWSERIAL_TYPE GWSERIAL_TYPE_UNI
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//M5 GPS (Atomic GPS Base)
|
||||||
|
#ifdef M5_GPS_KIT
|
||||||
|
#ifdef _GWM5_BOARD
|
||||||
|
#error "can only define one M5 base"
|
||||||
|
#endif
|
||||||
|
#define _GWM5_BOARD
|
||||||
|
#define GWSERIAL_RX BOARD_LEFT1
|
||||||
|
#define GWSERIAL_TYPE GWSERIAL_TYPE_RX
|
||||||
|
#define CFGDEFAULT_serialBaud "9600"
|
||||||
|
#define CFGMODE_serialBaud GwConfigInterface::READONLY
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//below we define the final device config based on the above
|
||||||
|
//boards and peripherals
|
||||||
|
//this allows us to easily also set them from outside
|
||||||
|
//serial adapter at the M5 groove pins
|
||||||
|
//we use serial2 for groove serial if serial1 is already defined
|
||||||
|
//before (e.g. by serial kit)
|
||||||
|
#ifdef SERIAL_GROOVE_485
|
||||||
|
#define _GWM5_GROOVE
|
||||||
|
#ifdef GWSERIAL_TYPE
|
||||||
|
#define GWSERIAL2_TX GROOVE_PIN_2
|
||||||
|
#define GWSERIAL2_RX GROOVE_PIN_1
|
||||||
|
#define GWSERIAL2_TYPE GWSERIAL_TYPE_UNI
|
||||||
|
#else
|
||||||
|
#define GWSERIAL_TX GROOVE_PIN_2
|
||||||
|
#define GWSERIAL_RX GROOVE_PIN_1
|
||||||
|
#define GWSERIAL_TYPE GWSERIAL_TYPE_UNI
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef SERIAL_GROOVE_232
|
||||||
|
#ifdef _GWM5_GROOVE
|
||||||
|
#error "can only have one groove device"
|
||||||
|
#endif
|
||||||
|
#define _GWM5_GROOVE
|
||||||
|
#ifdef GWSERIAL_TYPE
|
||||||
|
#define GWSERIAL2_TX GROOVE_PIN_2
|
||||||
|
#define GWSERIAL2_RX GROOVE_PIN_1
|
||||||
|
#define GWSERIAL2_TYPE GWSERIAL_TYPE_BI
|
||||||
|
#else
|
||||||
|
#define GWSERIAL_TX GROOVE_PIN_2
|
||||||
|
#define GWSERIAL_RX GROOVE_PIN_1
|
||||||
|
#define GWSERIAL_TYPE GWSERIAL_TYPE_BI
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//http://docs.m5stack.com/en/unit/gps
|
||||||
|
#ifdef M5_GPS_UNIT
|
||||||
|
#ifdef _GWM5_GROOVE
|
||||||
|
#error "can only have one M5 groove"
|
||||||
|
#endif
|
||||||
|
#define _GWM5_GROOVE
|
||||||
|
#ifdef GWSERIAL_TYPE
|
||||||
|
#define GWSERIAL2_RX GROOVE_PIN_1
|
||||||
|
#define GWSERIAL2_TYPE GWSERIAL_TYPE_RX
|
||||||
|
#define CFGDEFAULT_serialBaud "9600"
|
||||||
|
#define CFGMODE_serialBaud GwConfigInterface::READONLY
|
||||||
|
#else
|
||||||
|
#define GWSERIAL_RX GROOVE_PIN_1
|
||||||
|
#define GWSERIAL_TYPE GWSERIAL_TYPE_RX
|
||||||
|
#define CFGDEFAULT_serial2Baud "9600"
|
||||||
|
#define CFGMODE_serial2Baud GwConfigInterface::READONLY
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//can kit for M5 Atom
|
||||||
|
#ifdef M5_CAN_KIT
|
||||||
|
#ifdef _GWM5_BOARD
|
||||||
|
#error "can only define one M5 base"
|
||||||
|
#endif
|
||||||
|
#define _GWM5_BOARD
|
||||||
|
#define ESP32_CAN_TX_PIN BOARD_LEFT1
|
||||||
|
#define ESP32_CAN_RX_PIN BOARD_LEFT2
|
||||||
|
#endif
|
||||||
|
//CAN via groove
|
||||||
|
#ifdef M5_CANUNIT
|
||||||
|
#ifdef _GWM5_GROOVE
|
||||||
|
#error "can only have one M5 groove"
|
||||||
|
#endif
|
||||||
|
#define _GWM5_GROOVE
|
||||||
|
#define ESP32_CAN_TX_PIN GROOVE_PIN_2
|
||||||
|
#define ESP32_CAN_RX_PIN GROOVE_PIN_1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef M5_ENV3
|
||||||
|
#ifndef M5_GROOVEIIC
|
||||||
|
#define M5_GROOVEIIC
|
||||||
|
#endif
|
||||||
|
#ifndef GWSHT3X
|
||||||
|
#define GWSHT3X -1
|
||||||
|
#endif
|
||||||
|
#ifndef GWQMP6988
|
||||||
|
#define GWQMP6988 -1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef M5_GROOVEIIC
|
||||||
|
#ifdef _GWM5_GROOVE
|
||||||
|
#error "can only have one M5 groove"
|
||||||
|
#endif
|
||||||
|
#define _GWM5_GROOVE
|
||||||
|
#ifdef GWIIC_SCL
|
||||||
|
#error "you cannot define both GWIIC_SCL and M5_GROOVEIIC"
|
||||||
|
#endif
|
||||||
|
#define GWIIC_SCL GROOVE_PIN_1
|
||||||
|
#ifdef GWIIC_SDA
|
||||||
|
#error "you cannot define both GWIIC_SDA and M5_GROOVEIIC"
|
||||||
|
#endif
|
||||||
|
#define GWIIC_SDA GROOVE_PIN_2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef GWIIC_SDA
|
||||||
|
#ifndef GWIIC_SCL
|
||||||
|
#error "you must both define GWIIC_SDA and GWIIC_SCL"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef GWIIC_SCL
|
||||||
|
#ifndef GWIIC_SDA
|
||||||
|
#error "you must both define GWIIC_SDA and GWIIC_SCL"
|
||||||
|
#endif
|
||||||
|
#define _GWIIC
|
||||||
|
#endif
|
||||||
|
#ifdef GWIIC_SDA2
|
||||||
|
#ifndef GWIIC_SCL2
|
||||||
|
#error "you must both define GWIIC_SDA2 and GWIIC_SCL2"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef GWIIC_SCL2
|
||||||
|
#ifndef GWIIC_SDA2
|
||||||
|
#error "you must both define GWIIC_SDA and GWIIC_SCL2"
|
||||||
|
#endif
|
||||||
|
#define _GWIIC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef GWLED_TYPE
|
||||||
|
#ifdef GWLED_CODE
|
||||||
|
#if GWLED_CODE == 0
|
||||||
|
#define GWLED_TYPE SK6812
|
||||||
|
#endif
|
||||||
|
#if GWLED_CODE == 1
|
||||||
|
#define GWLED_TYPE WS2812
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef GWLED_TYPE
|
||||||
|
#define GWLED_FASTLED
|
||||||
|
#ifndef GWLED_BRIGHTNESS
|
||||||
|
#define GWLED_BRIGHTNESS 64
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP32_CAN_TX_PIN
|
||||||
|
#ifndef N2K_LOAD_LEVEL
|
||||||
|
#define N2K_LOAD_LEVEL 3
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef GWLED_FASTLED
|
||||||
|
#define CFGMODE_ledBrightness GwConfigInterface::NORMAL
|
||||||
|
#ifdef GWLED_BRIGHTNESS
|
||||||
|
#define CFGDEFAULT_ledBrightness GWSTRINGIFY(GWLED_BRIGHTNESS)
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define CFGMODE_ledBrightness GwConfigInterface::HIDDEN
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
#include "GwBME280.h"
|
||||||
|
#ifdef _GWIIC
|
||||||
|
#if defined(GWBME280) || defined(GWBME28011) || defined(GWBME28012)|| defined(GWBME28021)|| defined(GWBME28022)
|
||||||
|
#define _GWBME280
|
||||||
|
#else
|
||||||
|
#undef _GWBME280
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#undef _GWBME280
|
||||||
|
#undef GWBME280
|
||||||
|
#undef GWBME28011
|
||||||
|
#undef GWBME28012
|
||||||
|
#undef GWBME28021
|
||||||
|
#undef GWBME28022
|
||||||
|
#endif
|
||||||
|
#ifdef _GWBME280
|
||||||
|
#include <Adafruit_BME280.h>
|
||||||
|
#endif
|
||||||
|
#ifdef _GWBME280
|
||||||
|
#define PRFX1 "BME28011"
|
||||||
|
#define PRFX2 "BME28012"
|
||||||
|
#define PRFX3 "BME28021"
|
||||||
|
#define PRFX4 "BME28022"
|
||||||
|
class BME280Config : public SensorBase{
|
||||||
|
public:
|
||||||
|
bool prAct=true;
|
||||||
|
bool tmAct=true;
|
||||||
|
bool huAct=true;
|
||||||
|
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
|
||||||
|
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_InsideHumidity;
|
||||||
|
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
|
||||||
|
String tmNam="Temperature";
|
||||||
|
String huNam="Humidity";
|
||||||
|
String prNam="Pressure";
|
||||||
|
float tmOff=0;
|
||||||
|
float prOff=0;
|
||||||
|
Adafruit_BME280 *device=nullptr;
|
||||||
|
uint32_t sensorId=-1;
|
||||||
|
BME280Config(GwApi * api, const String &prfx):SensorBase(api,prfx){
|
||||||
|
}
|
||||||
|
virtual bool isActive(){return prAct||huAct||tmAct;}
|
||||||
|
virtual bool initDevice(GwApi *api,TwoWire *wire){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
device= new Adafruit_BME280();
|
||||||
|
if (! device->begin(addr,wire)){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unable to initialize %s at %d",prefix.c_str(),addr);
|
||||||
|
delete device;
|
||||||
|
device=nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tmOff != 0){
|
||||||
|
device->setTemperatureCompensation(tmOff);
|
||||||
|
}
|
||||||
|
sensorId=device->sensorID();
|
||||||
|
LOG_DEBUG(GwLog::LOG, "initialized %s at %d, sensorId 0x%x", prefix.c_str(), addr, sensorId);
|
||||||
|
return (huAct && sensorId == 0x60) || tmAct || prAct;
|
||||||
|
}
|
||||||
|
virtual bool preinit(GwApi * api){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
|
||||||
|
api->addCapability(prefix,"true");
|
||||||
|
addPressureXdr(api,*this);
|
||||||
|
addTempXdr(api,*this);
|
||||||
|
addHumidXdr(api,*this);
|
||||||
|
return isActive();
|
||||||
|
}
|
||||||
|
virtual void measure(GwApi *api, TwoWire *wire, int counterId)
|
||||||
|
{
|
||||||
|
if (!device)
|
||||||
|
return;
|
||||||
|
GwLog *logger = api->getLogger();
|
||||||
|
if (prAct)
|
||||||
|
{
|
||||||
|
float pressure = device->readPressure();
|
||||||
|
float computed = pressure + prOff;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "%s measure %2.0fPa, computed %2.0fPa", prefix.c_str(), pressure, computed);
|
||||||
|
sendN2kPressure(api, *this, computed, counterId);
|
||||||
|
}
|
||||||
|
if (tmAct)
|
||||||
|
{
|
||||||
|
float temperature = device->readTemperature(); // offset is handled internally
|
||||||
|
temperature = CToKelvin(temperature);
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "%s measure temp=%2.1f", prefix.c_str(), temperature);
|
||||||
|
sendN2kTemperature(api, *this, temperature, counterId);
|
||||||
|
}
|
||||||
|
if (huAct && sensorId == 0x60)
|
||||||
|
{
|
||||||
|
float humidity = device->readHumidity();
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "%s read humid=%02.0f", prefix.c_str(), humidity);
|
||||||
|
sendN2kHumidity(api, *this, humidity, counterId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#define CFG280(prefix) \
|
||||||
|
CFG_GET(prAct,prefix); \
|
||||||
|
CFG_GET(tmAct,prefix);\
|
||||||
|
CFG_GET(huAct,prefix);\
|
||||||
|
CFG_GET(tmSrc,prefix);\
|
||||||
|
CFG_GET(huSrc,prefix);\
|
||||||
|
CFG_GET(iid,prefix);\
|
||||||
|
CFG_GET(intv,prefix);\
|
||||||
|
CFG_GET(tmNam,prefix);\
|
||||||
|
CFG_GET(huNam,prefix);\
|
||||||
|
CFG_GET(prNam,prefix);\
|
||||||
|
CFG_GET(tmOff,prefix);\
|
||||||
|
CFG_GET(prOff,prefix);
|
||||||
|
|
||||||
|
virtual void readConfig(GwConfigHandler *cfg) override
|
||||||
|
{
|
||||||
|
if (prefix == PRFX1)
|
||||||
|
{
|
||||||
|
busId = 1;
|
||||||
|
addr = 0x76;
|
||||||
|
CFG280(BME28011);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX2)
|
||||||
|
{
|
||||||
|
busId = 1;
|
||||||
|
addr = 0x77;
|
||||||
|
CFG280(BME28012);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX3)
|
||||||
|
{
|
||||||
|
busId = 2;
|
||||||
|
addr = 0x76;
|
||||||
|
CFG280(BME28021);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX4)
|
||||||
|
{
|
||||||
|
busId = 2;
|
||||||
|
addr = 0x77;
|
||||||
|
CFG280(BME28022);
|
||||||
|
}
|
||||||
|
intv *= 1000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void registerBME280(GwApi *api,SensorList &sensors){
|
||||||
|
#if defined(GWBME280) || defined(GWBME28011)
|
||||||
|
{
|
||||||
|
BME280Config *cfg=new BME280Config(api,PRFX1);
|
||||||
|
sensors.add(api,cfg);
|
||||||
|
CHECK_IIC1();
|
||||||
|
#pragma message "GWBME28011 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWBME28012)
|
||||||
|
{
|
||||||
|
BME280Config *cfg=new BME280Config(api,PRFX2);
|
||||||
|
sensors.add(api,cfg);
|
||||||
|
CHECK_IIC1();
|
||||||
|
#pragma message "GWBME28012 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWBME28021)
|
||||||
|
{
|
||||||
|
BME280Config *cfg=new BME280Config(api,PRFX3);
|
||||||
|
sensors.add(api,cfg);
|
||||||
|
CHECK_IIC2();
|
||||||
|
#pragma message "GWBME28021 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWBME28022)
|
||||||
|
{
|
||||||
|
BME280Config *cfg=new BME280Config(api,PRFX4);
|
||||||
|
sensors.add(api,cfg);
|
||||||
|
CHECK_IIC1();
|
||||||
|
#pragma message "GWBME28022 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void registerBME280(GwApi *api,SensorList &sensors){
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#ifndef _GWBME280_H
|
||||||
|
#define _GWBME280_H
|
||||||
|
#include "GwIicSensors.h"
|
||||||
|
void registerBME280(GwApi *api,SensorList &sensors);
|
||||||
|
#endif
|
|
@ -0,0 +1,134 @@
|
||||||
|
#ifndef _GWIICSENSSORS_H
|
||||||
|
#define _GWIICSENSSORS_H
|
||||||
|
#include "GwApi.h"
|
||||||
|
#include "N2kMessages.h"
|
||||||
|
#include "GwXdrTypeMappings.h"
|
||||||
|
#include "GwHardware.h"
|
||||||
|
#ifdef _GWIIC
|
||||||
|
#include <Wire.h>
|
||||||
|
#else
|
||||||
|
class TwoWire;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CFG_GET(name,prefix) \
|
||||||
|
cfg->getValue(name, GwConfigDefinitions::prefix ## name)
|
||||||
|
|
||||||
|
template <class CFG>
|
||||||
|
bool addPressureXdr(GwApi *api, CFG &cfg)
|
||||||
|
{
|
||||||
|
if (! cfg.prAct) return false;
|
||||||
|
if (cfg.prNam.isEmpty()){
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG, "pressure active for %s, no xdr mapping", cfg.prefix.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG, "adding pressure xdr mapping for %s", cfg.prefix.c_str());
|
||||||
|
GwXDRMappingDef xdr;
|
||||||
|
xdr.category = GwXDRCategory::XDRPRESSURE;
|
||||||
|
xdr.direction = GwXDRMappingDef::M_FROM2K;
|
||||||
|
xdr.selector = (int)cfg.prSrc;
|
||||||
|
xdr.instanceId = cfg.iid;
|
||||||
|
xdr.instanceMode = GwXDRMappingDef::IS_SINGLE;
|
||||||
|
xdr.xdrName = cfg.prNam;
|
||||||
|
api->addXdrMapping(xdr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class CFG>
|
||||||
|
bool addTempXdr(GwApi *api, CFG &cfg)
|
||||||
|
{
|
||||||
|
if (!cfg.tmAct) return false;
|
||||||
|
if (cfg.tmNam.isEmpty()){
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG, "temperature active for %s, no xdr mapping", cfg.prefix.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG, "adding temperature xdr mapping for %s", cfg.prefix.c_str());
|
||||||
|
GwXDRMappingDef xdr;
|
||||||
|
xdr.category = GwXDRCategory::XDRTEMP;
|
||||||
|
xdr.direction = GwXDRMappingDef::M_FROM2K;
|
||||||
|
xdr.field = GWXDRFIELD_TEMPERATURE_ACTUALTEMPERATURE;
|
||||||
|
xdr.selector = (int)cfg.tmSrc;
|
||||||
|
xdr.instanceMode = GwXDRMappingDef::IS_SINGLE;
|
||||||
|
xdr.instanceId = cfg.iid;
|
||||||
|
xdr.xdrName = cfg.tmNam;
|
||||||
|
api->addXdrMapping(xdr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class CFG>
|
||||||
|
bool addHumidXdr(GwApi *api, CFG &cfg)
|
||||||
|
{
|
||||||
|
if (! cfg.huAct) return false;
|
||||||
|
if (cfg.huNam.isEmpty()){
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG, "humidity active for %s, no xdr mapping", cfg.prefix.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG, "adding humidity xdr mapping for %s", cfg.prefix.c_str());
|
||||||
|
GwXDRMappingDef xdr;
|
||||||
|
xdr.category = GwXDRCategory::XDRHUMIDITY;
|
||||||
|
xdr.direction = GwXDRMappingDef::M_FROM2K;
|
||||||
|
xdr.field = GWXDRFIELD_HUMIDITY_ACTUALHUMIDITY;
|
||||||
|
xdr.selector = (int)cfg.huSrc;
|
||||||
|
xdr.instanceMode = GwXDRMappingDef::IS_SINGLE;
|
||||||
|
xdr.instanceId = cfg.iid;
|
||||||
|
xdr.xdrName = cfg.huNam;
|
||||||
|
api->addXdrMapping(xdr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class CFG>
|
||||||
|
void sendN2kHumidity(GwApi *api,CFG &cfg,double value, int counterId){
|
||||||
|
tN2kMsg msg;
|
||||||
|
SetN2kHumidity(msg,1,cfg.iid,cfg.huSrc,value);
|
||||||
|
api->sendN2kMessage(msg);
|
||||||
|
api->increment(counterId,cfg.prefix+String("hum"));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class CFG>
|
||||||
|
void sendN2kPressure(GwApi *api,CFG &cfg,double value, int counterId){
|
||||||
|
tN2kMsg msg;
|
||||||
|
SetN2kPressure(msg,1,cfg.iid,cfg.prSrc,value);
|
||||||
|
api->sendN2kMessage(msg);
|
||||||
|
api->increment(counterId,cfg.prefix+String("press"));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class CFG>
|
||||||
|
void sendN2kTemperature(GwApi *api,CFG &cfg,double value, int counterId){
|
||||||
|
tN2kMsg msg;
|
||||||
|
SetN2kTemperature(msg,1,cfg.iid,cfg.tmSrc,value);
|
||||||
|
api->sendN2kMessage(msg);
|
||||||
|
api->increment(counterId,cfg.prefix+String("temp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SensorBase{
|
||||||
|
public:
|
||||||
|
int busId=0;
|
||||||
|
int iid=99; //N2K instanceId
|
||||||
|
int addr=-1;
|
||||||
|
String prefix;
|
||||||
|
long intv=0;
|
||||||
|
bool ok=false;
|
||||||
|
virtual void readConfig(GwConfigHandler *cfg)=0;
|
||||||
|
SensorBase(GwApi *api,const String &prfx):prefix(prfx){
|
||||||
|
}
|
||||||
|
virtual bool isActive(){return false;};
|
||||||
|
virtual bool initDevice(GwApi *api,TwoWire *wire){return false;};
|
||||||
|
virtual bool preinit(GwApi * api){return false;}
|
||||||
|
virtual void measure(GwApi * api,TwoWire *wire, int counterId){};
|
||||||
|
virtual ~SensorBase(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SensorList : public std::vector<SensorBase*>{
|
||||||
|
public:
|
||||||
|
void add(GwApi *api, SensorBase *sensor){
|
||||||
|
sensor->readConfig(api->getConfig());
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG,"configured sensor %s, status %d",sensor->prefix.c_str(),(int)sensor->ok);
|
||||||
|
push_back(sensor);
|
||||||
|
}
|
||||||
|
using std::vector<SensorBase*>::vector;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CHECK_IIC1() checkDef(GWIIC_SCL,GWIIC_SDA)
|
||||||
|
#define CHECK_IIC2() checkDef(GWIIC_SCL2,GWIIC_SDA2)
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,155 @@
|
||||||
|
#include "GwIicTask.h"
|
||||||
|
#include "GwIicSensors.h"
|
||||||
|
#include "GwHardware.h"
|
||||||
|
#include "GwBME280.h"
|
||||||
|
#include "GwQMP6988.h"
|
||||||
|
#include "GwSHT3X.h"
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#ifndef GWIIC_SDA
|
||||||
|
#define GWIIC_SDA -1
|
||||||
|
#endif
|
||||||
|
#ifndef GWIIC_SCL
|
||||||
|
#define GWIIC_SCL -1
|
||||||
|
#endif
|
||||||
|
#ifndef GWIIC_SDA2
|
||||||
|
#define GWIIC_SDA2 -1
|
||||||
|
#endif
|
||||||
|
#ifndef GWIIC_SCL2
|
||||||
|
#define GWIIC_SCL2 -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "GwTimer.h"
|
||||||
|
#include "GwHardware.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void runIicTask(GwApi *api);
|
||||||
|
|
||||||
|
static SensorList sensors;
|
||||||
|
|
||||||
|
void initIicTask(GwApi *api){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
#ifndef _GWIIC
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
bool addTask=false;
|
||||||
|
GwConfigHandler *config=api->getConfig();
|
||||||
|
registerSHT3X(api,sensors);
|
||||||
|
registerQMP6988(api,sensors);
|
||||||
|
registerBME280(api,sensors);
|
||||||
|
for (auto it=sensors.begin();it != sensors.end();it++){
|
||||||
|
if ((*it)->preinit(api)) addTask=true;
|
||||||
|
}
|
||||||
|
if (addTask){
|
||||||
|
api->addUserTask(runIicTask,"iicTask",3000);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifndef _GWIIC
|
||||||
|
void runIicTask(GwApi *api){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
LOG_DEBUG(GwLog::LOG,"no iic defined, iic task stopped");
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void runIicTask(GwApi *api){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
std::map<int,TwoWire *> buses;
|
||||||
|
LOG_DEBUG(GwLog::LOG,"iic task started");
|
||||||
|
for (auto it=sensors.begin();it != sensors.end();it++){
|
||||||
|
int busId=(*it)->busId;
|
||||||
|
auto bus=buses.find(busId);
|
||||||
|
if (bus == buses.end()){
|
||||||
|
switch (busId)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
if (GWIIC_SDA < 0 || GWIIC_SCL < 0)
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "IIC 1 invalid config sda=%d,scl=%d",
|
||||||
|
(int)GWIIC_SDA, (int)GWIIC_SCL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool rt = Wire.begin(GWIIC_SDA, GWIIC_SCL);
|
||||||
|
if (!rt)
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "unable to initialize IIC 1 at sad=%d,scl=%d",
|
||||||
|
(int)GWIIC_SDA, (int)GWIIC_SCL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buses[busId] = &Wire;
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "initialized IIC 1 at sda=%d,scl=%d",
|
||||||
|
(int)GWIIC_SDA, (int)GWIIC_SCL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
if (GWIIC_SDA2 < 0 || GWIIC_SCL2 < 0)
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "IIC 2 invalid config sda=%d,scl=%d",
|
||||||
|
(int)GWIIC_SDA2, (int)GWIIC_SCL2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
bool rt = Wire1.begin(GWIIC_SDA2, GWIIC_SCL2);
|
||||||
|
if (!rt)
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "unable to initialize IIC 2 at sda=%d,scl=%d",
|
||||||
|
(int)GWIIC_SDA2, (int)GWIIC_SCL2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buses[busId] = &Wire1;
|
||||||
|
LOG_DEBUG(GwLog::LOG, "initialized IIC 2 at sda=%d,scl=%d",
|
||||||
|
(int)GWIIC_SDA2, (int)GWIIC_SCL2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "invalid bus id %d at config %s", busId, (*it)->prefix.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GwConfigHandler *config=api->getConfig();
|
||||||
|
bool runLoop=false;
|
||||||
|
GwIntervalRunner timers;
|
||||||
|
int counterId=api->addCounter("iicsensors");
|
||||||
|
for (auto it=sensors.begin();it != sensors.end();it++){
|
||||||
|
SensorBase *cfg=*it;
|
||||||
|
auto bus=buses.find(cfg->busId);
|
||||||
|
if (! cfg->isActive()) continue;
|
||||||
|
if (bus == buses.end()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"No bus initialized for %s",cfg->prefix.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TwoWire *wire=bus->second;
|
||||||
|
bool rt=cfg->initDevice(api,wire);
|
||||||
|
if (rt){
|
||||||
|
runLoop=true;
|
||||||
|
timers.addAction(cfg->intv,[wire,api,cfg,counterId](){
|
||||||
|
cfg->measure(api,wire,counterId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! runLoop){
|
||||||
|
LOG_DEBUG(GwLog::LOG,"nothing to do for IIC task, finish");
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while(true){
|
||||||
|
delay(100);
|
||||||
|
timers.loop();
|
||||||
|
}
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef _GWIICTASK_H
|
||||||
|
#define _GWIICTASK_H
|
||||||
|
#include "GwApi.h"
|
||||||
|
void initIicTask(GwApi *api);
|
||||||
|
DECLARE_INITFUNCTION(initIicTask);
|
||||||
|
#endif
|
|
@ -0,0 +1,117 @@
|
||||||
|
#include "GwQMP6988.h"
|
||||||
|
#ifdef _GWQMP6988
|
||||||
|
#define PRFX1 "QMP698811"
|
||||||
|
#define PRFX2 "QMP698812"
|
||||||
|
#define PRFX3 "QMP698821"
|
||||||
|
#define PRFX4 "QMP698822"
|
||||||
|
class QMP6988Config : public SensorBase{
|
||||||
|
public:
|
||||||
|
String prNam="Pressure";
|
||||||
|
bool prAct=true;
|
||||||
|
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
|
||||||
|
float prOff=0;
|
||||||
|
QMP6988 *device=nullptr;
|
||||||
|
QMP6988Config(GwApi* api,const String &prefix):SensorBase(api,prefix){}
|
||||||
|
virtual bool isActive(){return prAct;};
|
||||||
|
virtual bool initDevice(GwApi *api,TwoWire *wire){
|
||||||
|
if (!isActive()) return false;
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
device=new QMP6988();
|
||||||
|
if (!device->init(addr,wire)){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unable to initialize %s at address %d, intv %ld",prefix.c_str(),addr,intv);
|
||||||
|
delete device;
|
||||||
|
device=nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::LOG,"initialized %s at address %d, intv %ld",prefix.c_str(),addr,intv);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
virtual bool preinit(GwApi * api){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
LOG_DEBUG(GwLog::LOG,"QMP6988 configured");
|
||||||
|
api->addCapability(prefix,"true");
|
||||||
|
addPressureXdr(api,*this);
|
||||||
|
return isActive();
|
||||||
|
}
|
||||||
|
virtual void measure(GwApi * api,TwoWire *wire, int counterId){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
float pressure=device->calcPressure();
|
||||||
|
float computed=pressure+prOff;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"%s measure %2.0fPa, computed %2.0fPa",prefix.c_str(), pressure,computed);
|
||||||
|
sendN2kPressure(api,*this,computed,counterId);
|
||||||
|
}
|
||||||
|
#define CFG6988(prefix)\
|
||||||
|
CFG_GET(prNam,prefix); \
|
||||||
|
CFG_GET(iid,prefix); \
|
||||||
|
CFG_GET(prAct,prefix); \
|
||||||
|
CFG_GET(intv,prefix); \
|
||||||
|
CFG_GET(prOff,prefix);
|
||||||
|
|
||||||
|
virtual void readConfig(GwConfigHandler *cfg){
|
||||||
|
if (prefix == PRFX1){
|
||||||
|
busId=1;
|
||||||
|
addr=86;
|
||||||
|
CFG6988(QMP698811);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX2){
|
||||||
|
busId=1;
|
||||||
|
addr=112;
|
||||||
|
CFG6988(QMP698812);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX3){
|
||||||
|
busId=2;
|
||||||
|
addr=86;
|
||||||
|
CFG6988(QMP698821);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX4){
|
||||||
|
busId=2;
|
||||||
|
addr=112;
|
||||||
|
CFG6988(QMP698822);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
intv*=1000;
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void registerQMP6988(GwApi *api,SensorList &sensors){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
#if defined(GWQMP6988) || defined(GWQMP698811)
|
||||||
|
{
|
||||||
|
QMP6988Config *scfg=new QMP6988Config(api,PRFX1);
|
||||||
|
sensors.add(api,scfg);
|
||||||
|
CHECK_IIC1();
|
||||||
|
#pragma message "GWQMP698811 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWQMP698812)
|
||||||
|
{
|
||||||
|
QMP6988Config *scfg=new QMP6988Config(api,PRFX2);
|
||||||
|
sensors.add(api,scfg);
|
||||||
|
CHECK_IIC1();
|
||||||
|
#pragma message "GWQMP698812 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWQMP698821)
|
||||||
|
{
|
||||||
|
QMP6988Config *scfg=new QMP6988Config(api,PRFX3);
|
||||||
|
sensors.add(api,scfg);
|
||||||
|
CHECK_IIC2();
|
||||||
|
#pragma message "GWQMP698821 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWQMP698822)
|
||||||
|
{
|
||||||
|
QMP6988Config *scfg=new QMP6988Config(api,PRFX4);
|
||||||
|
sensors.add(api,scfg);
|
||||||
|
CHECK_IIC2();
|
||||||
|
#pragma message "GWQMP698822 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
void registerQMP6988(GwApi *api,SensorList &sensors){}
|
||||||
|
#endif
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef _GQQMP6988_H
|
||||||
|
#define _GQQMP6988_H
|
||||||
|
#include "GwIicSensors.h"
|
||||||
|
#ifdef _GWIIC
|
||||||
|
#if defined(GWQMP6988) || defined(GWQMP698811) || defined(GWQMP698812) || defined(GWQMP698821) || defined(GWQMP698822)
|
||||||
|
#define _GWQMP6988
|
||||||
|
#else
|
||||||
|
#undef _GWQMP6988
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#undef _GWQMP6988
|
||||||
|
#undef GWQMP6988
|
||||||
|
#undef GWQMP698811
|
||||||
|
#undef GWQMP698812
|
||||||
|
#undef GWQMP698821
|
||||||
|
#undef GWQMP698822
|
||||||
|
#endif
|
||||||
|
#ifdef _GWQMP6988
|
||||||
|
#include "QMP6988.h"
|
||||||
|
#endif
|
||||||
|
void registerQMP6988(GwApi *api,SensorList &sensors);
|
||||||
|
#endif
|
|
@ -0,0 +1,150 @@
|
||||||
|
#include "GwSHT3X.h"
|
||||||
|
|
||||||
|
#ifdef _GWSHT3X
|
||||||
|
#define PRFX1 "SHT3X11"
|
||||||
|
#define PRFX2 "SHT3X12"
|
||||||
|
#define PRFX3 "SHT3X21"
|
||||||
|
#define PRFX4 "SHT3X22"
|
||||||
|
class SHT3XConfig : public SensorBase{
|
||||||
|
public:
|
||||||
|
String tmNam;
|
||||||
|
String huNam;
|
||||||
|
bool tmAct=false;
|
||||||
|
bool huAct=false;
|
||||||
|
tN2kHumiditySource huSrc;
|
||||||
|
tN2kTempSource tmSrc;
|
||||||
|
SHT3X *device=nullptr;
|
||||||
|
SHT3XConfig(GwApi *api,const String &prefix):
|
||||||
|
SensorBase(api,prefix){}
|
||||||
|
virtual bool isActive(){
|
||||||
|
return tmAct || huAct;
|
||||||
|
}
|
||||||
|
virtual bool initDevice(GwApi * api,TwoWire *wire){
|
||||||
|
if (! isActive()) return false;
|
||||||
|
device=new SHT3X();
|
||||||
|
device->init(addr,wire);
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
LOG_DEBUG(GwLog::LOG,"initialized %s at address %d, intv %ld",prefix.c_str(),(int)addr,intv);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
virtual bool preinit(GwApi * api){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
|
||||||
|
api->addCapability(prefix,"true");
|
||||||
|
addHumidXdr(api,*this);
|
||||||
|
addTempXdr(api,*this);
|
||||||
|
return isActive();
|
||||||
|
}
|
||||||
|
virtual void measure(GwApi * api,TwoWire *wire, int counterId)
|
||||||
|
{
|
||||||
|
if (!device)
|
||||||
|
return;
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
int rt = 0;
|
||||||
|
if ((rt = device->get()) == 0)
|
||||||
|
{
|
||||||
|
double temp = device->cTemp;
|
||||||
|
temp = CToKelvin(temp);
|
||||||
|
double humid = device->humidity;
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "%s measure temp=%2.1f, humid=%2.0f",prefix.c_str(), (float)temp, (float)humid);
|
||||||
|
if (huAct)
|
||||||
|
{
|
||||||
|
sendN2kHumidity(api, *this, humid, counterId);
|
||||||
|
}
|
||||||
|
if (tmAct)
|
||||||
|
{
|
||||||
|
sendN2kTemperature(api, *this, temp, counterId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::DEBUG, "unable to query %s: %d",prefix.c_str(), rt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* we do not dynamically compute the config names
|
||||||
|
* just to get compile time errors if something does not fit
|
||||||
|
* correctly
|
||||||
|
*/
|
||||||
|
#define CFG3X(prefix) \
|
||||||
|
CFG_GET(tmNam,prefix); \
|
||||||
|
CFG_GET(huNam,prefix); \
|
||||||
|
CFG_GET(iid,prefix); \
|
||||||
|
CFG_GET(tmAct,prefix); \
|
||||||
|
CFG_GET(huAct,prefix); \
|
||||||
|
CFG_GET(intv,prefix); \
|
||||||
|
CFG_GET(huSrc,prefix); \
|
||||||
|
CFG_GET(tmSrc,prefix);
|
||||||
|
|
||||||
|
virtual void readConfig(GwConfigHandler *cfg){
|
||||||
|
if (prefix == PRFX1){
|
||||||
|
busId=1;
|
||||||
|
addr=0x44;
|
||||||
|
CFG3X(SHT3X11);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX2){
|
||||||
|
busId=1;
|
||||||
|
addr=0x45;
|
||||||
|
CFG3X(SHT3X12);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX3){
|
||||||
|
busId=2;
|
||||||
|
addr=0x44;
|
||||||
|
CFG3X(SHT3X21);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
if (prefix == PRFX4){
|
||||||
|
busId=2;
|
||||||
|
addr=0x45;
|
||||||
|
CFG3X(SHT3X22);
|
||||||
|
ok=true;
|
||||||
|
}
|
||||||
|
intv*=1000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void registerSHT3X(GwApi *api,SensorList &sensors){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
#if defined(GWSHT3X) || defined (GWSHT3X11)
|
||||||
|
{
|
||||||
|
SHT3XConfig *scfg=new SHT3XConfig(api,PRFX1);
|
||||||
|
sensors.add(api,scfg);
|
||||||
|
CHECK_IIC1();
|
||||||
|
#pragma message "GWSHT3X11 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWSHT3X12)
|
||||||
|
{
|
||||||
|
SHT3XConfig *scfg=new SHT3XConfig(api,PRFX2);
|
||||||
|
sensors.add(api,scfg);
|
||||||
|
CHECK_IIC1();
|
||||||
|
#pragma message "GWSHT3X12 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWSHT3X21)
|
||||||
|
{
|
||||||
|
SHT3XConfig *scfg=new SHT3XConfig(api,PRFX3);
|
||||||
|
sensors.add(api,scfg);
|
||||||
|
CHECK_IIC2();
|
||||||
|
#pragma message "GWSHT3X21 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(GWSHT3X22)
|
||||||
|
{
|
||||||
|
SHT3XConfig *scfg=new SHT3XConfig(api,PRFX4);
|
||||||
|
sensors.add(api,scfg);
|
||||||
|
CHECK_IIC2();
|
||||||
|
#pragma message "GWSHT3X22 defined"
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void registerSHT3X(GwApi *api,SensorList &sensors){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef _GWSHT3X_H
|
||||||
|
#define _GWSHT3X_H
|
||||||
|
#include "GwIicSensors.h"
|
||||||
|
#ifdef _GWIIC
|
||||||
|
#if defined(GWSHT3X) || defined(GWSHT3X11) || defined(GWSHT3X12) || defined(GWSHT3X21) || defined(GWSHT3X22)
|
||||||
|
#define _GWSHT3X
|
||||||
|
#else
|
||||||
|
#undef _GWSHT3X
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#undef _GWSHT3X
|
||||||
|
#undef GWSHT3X
|
||||||
|
#undef GWSHT3X11
|
||||||
|
#undef GWSHT3X12
|
||||||
|
#undef GWSHT3X21
|
||||||
|
#undef GWSHT3X22
|
||||||
|
#endif
|
||||||
|
#ifdef _GWSHT3X
|
||||||
|
#include "SHT3X.h"
|
||||||
|
#endif
|
||||||
|
void registerSHT3X(GwApi *api,SensorList &sensors);
|
||||||
|
#endif
|
|
@ -0,0 +1,394 @@
|
||||||
|
#include "GwQMP6988.h"
|
||||||
|
#ifdef _GWQMP6988
|
||||||
|
#include <math.h>
|
||||||
|
#include "stdint.h"
|
||||||
|
#include "stdio.h"
|
||||||
|
|
||||||
|
// DISABLE LOG
|
||||||
|
#define QMP6988_LOG(format...)
|
||||||
|
#define QMP6988_ERR(format...)
|
||||||
|
|
||||||
|
// ENABLE LOG
|
||||||
|
// #define QMP6988_LOG Serial.printf
|
||||||
|
// #define QMP6988_ERR Serial.printf
|
||||||
|
|
||||||
|
void QMP6988::delayMS(unsigned int ms) {
|
||||||
|
delay(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t QMP6988::writeReg(uint8_t slave, uint8_t reg_add, uint8_t reg_dat) {
|
||||||
|
device_wire->beginTransmission(slave);
|
||||||
|
device_wire->write(reg_add);
|
||||||
|
device_wire->write(reg_dat);
|
||||||
|
device_wire->endTransmission();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t QMP6988::readData(uint16_t slave, uint8_t reg_add, unsigned char* Read,
|
||||||
|
uint8_t num) {
|
||||||
|
device_wire->beginTransmission(slave);
|
||||||
|
device_wire->write(reg_add);
|
||||||
|
device_wire->endTransmission();
|
||||||
|
device_wire->requestFrom(slave, num);
|
||||||
|
for (int i = 0; i < num; i++) {
|
||||||
|
*(Read + i) = device_wire->read();
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t QMP6988::deviceCheck() {
|
||||||
|
uint8_t slave_addr_list[2] = {QMP6988_SLAVE_ADDRESS_L,
|
||||||
|
QMP6988_SLAVE_ADDRESS_H};
|
||||||
|
uint8_t ret = 0;
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
slave_addr = slave_addr_list[i];
|
||||||
|
ret = readData(slave_addr, QMP6988_CHIP_ID_REG, &(qmp6988.chip_id), 1);
|
||||||
|
if (ret == 0) {
|
||||||
|
QMP6988_LOG("%s: read 0xD1 failed\r\n", __func__);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QMP6988_LOG("qmp6988 read chip id = 0x%x\r\n", qmp6988.chip_id);
|
||||||
|
if (qmp6988.chip_id == QMP6988_CHIP_ID) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QMP6988::getCalibrationData() {
|
||||||
|
int status = 0;
|
||||||
|
// BITFIELDS temp_COE;
|
||||||
|
uint8_t a_data_uint8_tr[QMP6988_CALIBRATION_DATA_LENGTH] = {0};
|
||||||
|
int len;
|
||||||
|
|
||||||
|
for (len = 0; len < QMP6988_CALIBRATION_DATA_LENGTH; len += 1) {
|
||||||
|
status = readData(slave_addr, QMP6988_CALIBRATION_DATA_START + len,
|
||||||
|
&a_data_uint8_tr[len], 1);
|
||||||
|
if (status == 0) {
|
||||||
|
QMP6988_LOG("qmp6988 read 0xA0 error!");
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qmp6988.qmp6988_cali.COE_a0 =
|
||||||
|
(QMP6988_S32_t)(((a_data_uint8_tr[18] << SHIFT_LEFT_12_POSITION) |
|
||||||
|
(a_data_uint8_tr[19] << SHIFT_LEFT_4_POSITION) |
|
||||||
|
(a_data_uint8_tr[24] & 0x0f))
|
||||||
|
<< 12);
|
||||||
|
qmp6988.qmp6988_cali.COE_a0 = qmp6988.qmp6988_cali.COE_a0 >> 12;
|
||||||
|
|
||||||
|
qmp6988.qmp6988_cali.COE_a1 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[20]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[21]);
|
||||||
|
qmp6988.qmp6988_cali.COE_a2 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[22]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[23]);
|
||||||
|
|
||||||
|
qmp6988.qmp6988_cali.COE_b00 =
|
||||||
|
(QMP6988_S32_t)(((a_data_uint8_tr[0] << SHIFT_LEFT_12_POSITION) |
|
||||||
|
(a_data_uint8_tr[1] << SHIFT_LEFT_4_POSITION) |
|
||||||
|
((a_data_uint8_tr[24] & 0xf0) >>
|
||||||
|
SHIFT_RIGHT_4_POSITION))
|
||||||
|
<< 12);
|
||||||
|
qmp6988.qmp6988_cali.COE_b00 = qmp6988.qmp6988_cali.COE_b00 >> 12;
|
||||||
|
|
||||||
|
qmp6988.qmp6988_cali.COE_bt1 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[2]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[3]);
|
||||||
|
qmp6988.qmp6988_cali.COE_bt2 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[4]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[5]);
|
||||||
|
qmp6988.qmp6988_cali.COE_bp1 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[6]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[7]);
|
||||||
|
qmp6988.qmp6988_cali.COE_b11 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[8]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[9]);
|
||||||
|
qmp6988.qmp6988_cali.COE_bp2 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[10]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[11]);
|
||||||
|
qmp6988.qmp6988_cali.COE_b12 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[12]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[13]);
|
||||||
|
qmp6988.qmp6988_cali.COE_b21 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[14]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[15]);
|
||||||
|
qmp6988.qmp6988_cali.COE_bp3 =
|
||||||
|
(QMP6988_S16_t)(((a_data_uint8_tr[16]) << SHIFT_LEFT_8_POSITION) |
|
||||||
|
a_data_uint8_tr[17]);
|
||||||
|
|
||||||
|
QMP6988_LOG("<-----------calibration data-------------->\r\n");
|
||||||
|
QMP6988_LOG("COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n",
|
||||||
|
qmp6988.qmp6988_cali.COE_a0, qmp6988.qmp6988_cali.COE_a1,
|
||||||
|
qmp6988.qmp6988_cali.COE_a2, qmp6988.qmp6988_cali.COE_b00);
|
||||||
|
QMP6988_LOG("COE_bt1[%d] COE_bt2[%d] COE_bp1[%d] COE_b11[%d]\r\n",
|
||||||
|
qmp6988.qmp6988_cali.COE_bt1, qmp6988.qmp6988_cali.COE_bt2,
|
||||||
|
qmp6988.qmp6988_cali.COE_bp1, qmp6988.qmp6988_cali.COE_b11);
|
||||||
|
QMP6988_LOG("COE_bp2[%d] COE_b12[%d] COE_b21[%d] COE_bp3[%d]\r\n",
|
||||||
|
qmp6988.qmp6988_cali.COE_bp2, qmp6988.qmp6988_cali.COE_b12,
|
||||||
|
qmp6988.qmp6988_cali.COE_b21, qmp6988.qmp6988_cali.COE_bp3);
|
||||||
|
QMP6988_LOG("<-----------calibration data-------------->\r\n");
|
||||||
|
|
||||||
|
qmp6988.ik.a0 = qmp6988.qmp6988_cali.COE_a0; // 20Q4
|
||||||
|
qmp6988.ik.b00 = qmp6988.qmp6988_cali.COE_b00; // 20Q4
|
||||||
|
|
||||||
|
qmp6988.ik.a1 = 3608L * (QMP6988_S32_t)qmp6988.qmp6988_cali.COE_a1 -
|
||||||
|
1731677965L; // 31Q23
|
||||||
|
qmp6988.ik.a2 = 16889L * (QMP6988_S32_t)qmp6988.qmp6988_cali.COE_a2 -
|
||||||
|
87619360L; // 30Q47
|
||||||
|
|
||||||
|
qmp6988.ik.bt1 = 2982L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bt1 +
|
||||||
|
107370906L; // 28Q15
|
||||||
|
qmp6988.ik.bt2 = 329854L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bt2 +
|
||||||
|
108083093L; // 34Q38
|
||||||
|
qmp6988.ik.bp1 = 19923L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bp1 +
|
||||||
|
1133836764L; // 31Q20
|
||||||
|
qmp6988.ik.b11 = 2406L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_b11 +
|
||||||
|
118215883L; // 28Q34
|
||||||
|
qmp6988.ik.bp2 = 3079L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bp2 -
|
||||||
|
181579595L; // 29Q43
|
||||||
|
qmp6988.ik.b12 = 6846L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_b12 +
|
||||||
|
85590281L; // 29Q53
|
||||||
|
qmp6988.ik.b21 = 13836L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_b21 +
|
||||||
|
79333336L; // 29Q60
|
||||||
|
qmp6988.ik.bp3 = 2915L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bp3 +
|
||||||
|
157155561L; // 28Q65
|
||||||
|
QMP6988_LOG("<----------- int calibration data -------------->\r\n");
|
||||||
|
QMP6988_LOG("a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988.ik.a0,
|
||||||
|
qmp6988.ik.a1, qmp6988.ik.a2, qmp6988.ik.b00);
|
||||||
|
QMP6988_LOG("bt1[%lld] bt2[%lld] bp1[%lld] b11[%lld]\r\n",
|
||||||
|
qmp6988.ik.bt1, qmp6988.ik.bt2, qmp6988.ik.bp1, qmp6988.ik.b11);
|
||||||
|
QMP6988_LOG("bp2[%lld] b12[%lld] b21[%lld] bp3[%lld]\r\n",
|
||||||
|
qmp6988.ik.bp2, qmp6988.ik.b12, qmp6988.ik.b21, qmp6988.ik.bp3);
|
||||||
|
QMP6988_LOG("<----------- int calibration data -------------->\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMP6988_S16_t QMP6988::convTx02e(qmp6988_ik_data_t* ik, QMP6988_S32_t dt) {
|
||||||
|
QMP6988_S16_t ret;
|
||||||
|
QMP6988_S64_t wk1, wk2;
|
||||||
|
|
||||||
|
// wk1: 60Q4 // bit size
|
||||||
|
wk1 = ((QMP6988_S64_t)ik->a1 * (QMP6988_S64_t)dt); // 31Q23+24-1=54 (54Q23)
|
||||||
|
wk2 = ((QMP6988_S64_t)ik->a2 * (QMP6988_S64_t)dt) >>
|
||||||
|
14; // 30Q47+24-1=53 (39Q33)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)dt) >> 10; // 39Q33+24-1=62 (52Q23)
|
||||||
|
wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04)
|
||||||
|
ret = (QMP6988_S16_t)((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMP6988_S32_t QMP6988::getPressure02e(qmp6988_ik_data_t* ik, QMP6988_S32_t dp,
|
||||||
|
QMP6988_S16_t tx) {
|
||||||
|
QMP6988_S32_t ret;
|
||||||
|
QMP6988_S64_t wk1, wk2, wk3;
|
||||||
|
|
||||||
|
// wk1 = 48Q16 // bit size
|
||||||
|
wk1 =
|
||||||
|
((QMP6988_S64_t)ik->bt1 * (QMP6988_S64_t)tx); // 28Q15+16-1=43 (43Q15)
|
||||||
|
wk2 = ((QMP6988_S64_t)ik->bp1 * (QMP6988_S64_t)dp) >>
|
||||||
|
5; // 31Q20+24-1=54 (49Q15)
|
||||||
|
wk1 += wk2; // 43,49->50Q15
|
||||||
|
wk2 = ((QMP6988_S64_t)ik->bt2 * (QMP6988_S64_t)tx) >>
|
||||||
|
1; // 34Q38+16-1=49 (48Q37)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)tx) >> 8; // 48Q37+16-1=63 (55Q29)
|
||||||
|
wk3 = wk2; // 55Q29
|
||||||
|
wk2 = ((QMP6988_S64_t)ik->b11 * (QMP6988_S64_t)tx) >>
|
||||||
|
4; // 28Q34+16-1=43 (39Q30)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 1; // 39Q30+24-1=62 (61Q29)
|
||||||
|
wk3 += wk2; // 55,61->62Q29
|
||||||
|
wk2 = ((QMP6988_S64_t)ik->bp2 * (QMP6988_S64_t)dp) >>
|
||||||
|
13; // 29Q43+24-1=52 (39Q30)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 1; // 39Q30+24-1=62 (61Q29)
|
||||||
|
wk3 += wk2; // 62,61->63Q29
|
||||||
|
wk1 += wk3 >> 14; // Q29 >> 14 -> Q15
|
||||||
|
wk2 =
|
||||||
|
((QMP6988_S64_t)ik->b12 * (QMP6988_S64_t)tx); // 29Q53+16-1=45 (45Q53)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)tx) >> 22; // 45Q53+16-1=61 (39Q31)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 1; // 39Q31+24-1=62 (61Q30)
|
||||||
|
wk3 = wk2; // 61Q30
|
||||||
|
wk2 = ((QMP6988_S64_t)ik->b21 * (QMP6988_S64_t)tx) >>
|
||||||
|
6; // 29Q60+16-1=45 (39Q54)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 23; // 39Q54+24-1=62 (39Q31)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 1; // 39Q31+24-1=62 (61Q20)
|
||||||
|
wk3 += wk2; // 61,61->62Q30
|
||||||
|
wk2 = ((QMP6988_S64_t)ik->bp3 * (QMP6988_S64_t)dp) >>
|
||||||
|
12; // 28Q65+24-1=51 (39Q53)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 23; // 39Q53+24-1=62 (39Q30)
|
||||||
|
wk2 = (wk2 * (QMP6988_S64_t)dp); // 39Q30+24-1=62 (62Q30)
|
||||||
|
wk3 += wk2; // 62,62->63Q30
|
||||||
|
wk1 += wk3 >> 15; // Q30 >> 15 = Q15
|
||||||
|
wk1 /= 32767L;
|
||||||
|
wk1 >>= 11; // Q15 >> 7 = Q4
|
||||||
|
wk1 += ik->b00; // Q4 + 20Q4
|
||||||
|
// wk1 >>= 4; // 28Q4 -> 24Q0
|
||||||
|
ret = (QMP6988_S32_t)wk1;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QMP6988::softwareReset() {
|
||||||
|
uint8_t ret = 0;
|
||||||
|
|
||||||
|
ret = writeReg(slave_addr, QMP6988_RESET_REG, 0xe6);
|
||||||
|
if (ret == 0) {
|
||||||
|
QMP6988_LOG("softwareReset fail!!! \r\n");
|
||||||
|
}
|
||||||
|
delayMS(20);
|
||||||
|
ret = writeReg(slave_addr, QMP6988_RESET_REG, 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QMP6988::setpPowermode(int power_mode) {
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
QMP6988_LOG("qmp_set_powermode %d \r\n", power_mode);
|
||||||
|
|
||||||
|
qmp6988.power_mode = power_mode;
|
||||||
|
readData(slave_addr, QMP6988_CTRLMEAS_REG, &data, 1);
|
||||||
|
data = data & 0xfc;
|
||||||
|
if (power_mode == QMP6988_SLEEP_MODE) {
|
||||||
|
data |= 0x00;
|
||||||
|
} else if (power_mode == QMP6988_FORCED_MODE) {
|
||||||
|
data |= 0x01;
|
||||||
|
} else if (power_mode == QMP6988_NORMAL_MODE) {
|
||||||
|
data |= 0x03;
|
||||||
|
}
|
||||||
|
writeReg(slave_addr, QMP6988_CTRLMEAS_REG, data);
|
||||||
|
|
||||||
|
QMP6988_LOG("qmp_set_powermode 0xf4=0x%x \r\n", data);
|
||||||
|
|
||||||
|
delayMS(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QMP6988::setFilter(unsigned char filter) {
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
data = (filter & 0x03);
|
||||||
|
writeReg(slave_addr, QMP6988_CONFIG_REG, data);
|
||||||
|
|
||||||
|
delayMS(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QMP6988::setOversamplingP(unsigned char oversampling_p) {
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
readData(slave_addr, QMP6988_CTRLMEAS_REG, &data, 1);
|
||||||
|
data &= 0xe3;
|
||||||
|
data |= (oversampling_p << 2);
|
||||||
|
writeReg(slave_addr, QMP6988_CTRLMEAS_REG, data);
|
||||||
|
delayMS(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QMP6988::setOversamplingT(unsigned char oversampling_t) {
|
||||||
|
uint8_t data;
|
||||||
|
|
||||||
|
readData(slave_addr, QMP6988_CTRLMEAS_REG, &data, 1);
|
||||||
|
data &= 0x1f;
|
||||||
|
data |= (oversampling_t << 5);
|
||||||
|
writeReg(slave_addr, QMP6988_CTRLMEAS_REG, data);
|
||||||
|
delayMS(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
float QMP6988::calcAltitude(float pressure, float temp) {
|
||||||
|
float altitude;
|
||||||
|
|
||||||
|
altitude =
|
||||||
|
(pow((101325 / pressure), 1 / 5.257) - 1) * (temp + 273.15) / 0.0065;
|
||||||
|
QMP6988_LOG("altitude = %f\r\n", altitude);
|
||||||
|
return altitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
float QMP6988::calcPressure() {
|
||||||
|
uint8_t err = 0;
|
||||||
|
QMP6988_U32_t P_read, T_read;
|
||||||
|
QMP6988_S32_t P_raw, T_raw;
|
||||||
|
uint8_t a_data_uint8_tr[6] = {0};
|
||||||
|
QMP6988_S32_t T_int, P_int;
|
||||||
|
|
||||||
|
// press
|
||||||
|
err = readData(slave_addr, QMP6988_PRESSURE_MSB_REG, a_data_uint8_tr, 6);
|
||||||
|
if (err == 0) {
|
||||||
|
QMP6988_LOG("qmp6988 read press raw error! \r\n");
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
P_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0]))
|
||||||
|
<< SHIFT_LEFT_16_POSITION) |
|
||||||
|
(((QMP6988_U16_t)(a_data_uint8_tr[1]))
|
||||||
|
<< SHIFT_LEFT_8_POSITION) |
|
||||||
|
(a_data_uint8_tr[2]));
|
||||||
|
P_raw = (QMP6988_S32_t)(P_read - SUBTRACTOR);
|
||||||
|
|
||||||
|
T_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3]))
|
||||||
|
<< SHIFT_LEFT_16_POSITION) |
|
||||||
|
(((QMP6988_U16_t)(a_data_uint8_tr[4]))
|
||||||
|
<< SHIFT_LEFT_8_POSITION) |
|
||||||
|
(a_data_uint8_tr[5]));
|
||||||
|
T_raw = (QMP6988_S32_t)(T_read - SUBTRACTOR);
|
||||||
|
|
||||||
|
T_int = convTx02e(&(qmp6988.ik), T_raw);
|
||||||
|
P_int = getPressure02e(&(qmp6988.ik), P_raw, T_int);
|
||||||
|
qmp6988.temperature = (float)T_int / 256.0f;
|
||||||
|
qmp6988.pressure = (float)P_int / 16.0f;
|
||||||
|
|
||||||
|
return qmp6988.pressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
float QMP6988::calcTemperature() {
|
||||||
|
uint8_t err = 0;
|
||||||
|
QMP6988_U32_t P_read, T_read;
|
||||||
|
QMP6988_S32_t P_raw, T_raw;
|
||||||
|
uint8_t a_data_uint8_tr[6] = {0};
|
||||||
|
QMP6988_S32_t T_int, P_int;
|
||||||
|
|
||||||
|
// press
|
||||||
|
err = readData(slave_addr, QMP6988_PRESSURE_MSB_REG, a_data_uint8_tr, 6);
|
||||||
|
if (err == 0) {
|
||||||
|
QMP6988_LOG("qmp6988 read press raw error! \r\n");
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
P_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0]))
|
||||||
|
<< SHIFT_LEFT_16_POSITION) |
|
||||||
|
(((QMP6988_U16_t)(a_data_uint8_tr[1]))
|
||||||
|
<< SHIFT_LEFT_8_POSITION) |
|
||||||
|
(a_data_uint8_tr[2]));
|
||||||
|
P_raw = (QMP6988_S32_t)(P_read - SUBTRACTOR);
|
||||||
|
|
||||||
|
// temp
|
||||||
|
err = readData(slave_addr, QMP6988_TEMPERATURE_MSB_REG, a_data_uint8_tr, 3);
|
||||||
|
if (err == 0) {
|
||||||
|
QMP6988_LOG("qmp6988 read temp raw error! \n");
|
||||||
|
}
|
||||||
|
T_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3]))
|
||||||
|
<< SHIFT_LEFT_16_POSITION) |
|
||||||
|
(((QMP6988_U16_t)(a_data_uint8_tr[4]))
|
||||||
|
<< SHIFT_LEFT_8_POSITION) |
|
||||||
|
(a_data_uint8_tr[5]));
|
||||||
|
T_raw = (QMP6988_S32_t)(T_read - SUBTRACTOR);
|
||||||
|
|
||||||
|
T_int = convTx02e(&(qmp6988.ik), T_raw);
|
||||||
|
P_int = getPressure02e(&(qmp6988.ik), P_raw, T_int);
|
||||||
|
qmp6988.temperature = (float)T_int / 256.0f;
|
||||||
|
qmp6988.pressure = (float)P_int / 16.0f;
|
||||||
|
|
||||||
|
return qmp6988.temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t QMP6988::init(uint8_t slave_addr_in, TwoWire* wire_in) {
|
||||||
|
device_wire = wire_in;
|
||||||
|
uint8_t ret;
|
||||||
|
slave_addr = slave_addr_in;
|
||||||
|
ret = deviceCheck();
|
||||||
|
if (ret == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
softwareReset();
|
||||||
|
getCalibrationData();
|
||||||
|
setpPowermode(QMP6988_NORMAL_MODE);
|
||||||
|
setFilter(QMP6988_FILTERCOEFF_4);
|
||||||
|
setOversamplingP(QMP6988_OVERSAMPLING_8X);
|
||||||
|
setOversamplingT(QMP6988_OVERSAMPLING_1X);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,152 @@
|
||||||
|
#ifndef __QMP6988_H
|
||||||
|
#define __QMP6988_H
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
|
||||||
|
#define QMP6988_SLAVE_ADDRESS_L (0x70)
|
||||||
|
#define QMP6988_SLAVE_ADDRESS_H (0x56)
|
||||||
|
|
||||||
|
#define QMP6988_U16_t unsigned short
|
||||||
|
#define QMP6988_S16_t short
|
||||||
|
#define QMP6988_U32_t unsigned int
|
||||||
|
#define QMP6988_S32_t int
|
||||||
|
#define QMP6988_U64_t unsigned long long
|
||||||
|
#define QMP6988_S64_t long long
|
||||||
|
|
||||||
|
#define QMP6988_CHIP_ID 0x5C
|
||||||
|
|
||||||
|
#define QMP6988_CHIP_ID_REG 0xD1
|
||||||
|
#define QMP6988_RESET_REG 0xE0 /* Device reset register */
|
||||||
|
#define QMP6988_DEVICE_STAT_REG 0xF3 /* Device state register */
|
||||||
|
#define QMP6988_CTRLMEAS_REG 0xF4 /* Measurement Condition Control Register */
|
||||||
|
/* data */
|
||||||
|
#define QMP6988_PRESSURE_MSB_REG 0xF7 /* Pressure MSB Register */
|
||||||
|
#define QMP6988_TEMPERATURE_MSB_REG 0xFA /* Temperature MSB Reg */
|
||||||
|
|
||||||
|
/* compensation calculation */
|
||||||
|
#define QMP6988_CALIBRATION_DATA_START \
|
||||||
|
0xA0 /* QMP6988 compensation coefficients */
|
||||||
|
#define QMP6988_CALIBRATION_DATA_LENGTH 25
|
||||||
|
|
||||||
|
#define SHIFT_RIGHT_4_POSITION 4
|
||||||
|
#define SHIFT_LEFT_2_POSITION 2
|
||||||
|
#define SHIFT_LEFT_4_POSITION 4
|
||||||
|
#define SHIFT_LEFT_5_POSITION 5
|
||||||
|
#define SHIFT_LEFT_8_POSITION 8
|
||||||
|
#define SHIFT_LEFT_12_POSITION 12
|
||||||
|
#define SHIFT_LEFT_16_POSITION 16
|
||||||
|
|
||||||
|
/* power mode */
|
||||||
|
#define QMP6988_SLEEP_MODE 0x00
|
||||||
|
#define QMP6988_FORCED_MODE 0x01
|
||||||
|
#define QMP6988_NORMAL_MODE 0x03
|
||||||
|
|
||||||
|
#define QMP6988_CTRLMEAS_REG_MODE__POS 0
|
||||||
|
#define QMP6988_CTRLMEAS_REG_MODE__MSK 0x03
|
||||||
|
#define QMP6988_CTRLMEAS_REG_MODE__LEN 2
|
||||||
|
|
||||||
|
/* oversampling */
|
||||||
|
#define QMP6988_OVERSAMPLING_SKIPPED 0x00
|
||||||
|
#define QMP6988_OVERSAMPLING_1X 0x01
|
||||||
|
#define QMP6988_OVERSAMPLING_2X 0x02
|
||||||
|
#define QMP6988_OVERSAMPLING_4X 0x03
|
||||||
|
#define QMP6988_OVERSAMPLING_8X 0x04
|
||||||
|
#define QMP6988_OVERSAMPLING_16X 0x05
|
||||||
|
#define QMP6988_OVERSAMPLING_32X 0x06
|
||||||
|
#define QMP6988_OVERSAMPLING_64X 0x07
|
||||||
|
|
||||||
|
#define QMP6988_CTRLMEAS_REG_OSRST__POS 5
|
||||||
|
#define QMP6988_CTRLMEAS_REG_OSRST__MSK 0xE0
|
||||||
|
#define QMP6988_CTRLMEAS_REG_OSRST__LEN 3
|
||||||
|
|
||||||
|
#define QMP6988_CTRLMEAS_REG_OSRSP__POS 2
|
||||||
|
#define QMP6988_CTRLMEAS_REG_OSRSP__MSK 0x1C
|
||||||
|
#define QMP6988_CTRLMEAS_REG_OSRSP__LEN 3
|
||||||
|
|
||||||
|
/* filter */
|
||||||
|
#define QMP6988_FILTERCOEFF_OFF 0x00
|
||||||
|
#define QMP6988_FILTERCOEFF_2 0x01
|
||||||
|
#define QMP6988_FILTERCOEFF_4 0x02
|
||||||
|
#define QMP6988_FILTERCOEFF_8 0x03
|
||||||
|
#define QMP6988_FILTERCOEFF_16 0x04
|
||||||
|
#define QMP6988_FILTERCOEFF_32 0x05
|
||||||
|
|
||||||
|
#define QMP6988_CONFIG_REG 0xF1 /*IIR filter co-efficient setting Register*/
|
||||||
|
#define QMP6988_CONFIG_REG_FILTER__POS 0
|
||||||
|
#define QMP6988_CONFIG_REG_FILTER__MSK 0x07
|
||||||
|
#define QMP6988_CONFIG_REG_FILTER__LEN 3
|
||||||
|
|
||||||
|
#define SUBTRACTOR 8388608
|
||||||
|
|
||||||
|
typedef struct _qmp6988_cali_data {
|
||||||
|
QMP6988_S32_t COE_a0;
|
||||||
|
QMP6988_S16_t COE_a1;
|
||||||
|
QMP6988_S16_t COE_a2;
|
||||||
|
QMP6988_S32_t COE_b00;
|
||||||
|
QMP6988_S16_t COE_bt1;
|
||||||
|
QMP6988_S16_t COE_bt2;
|
||||||
|
QMP6988_S16_t COE_bp1;
|
||||||
|
QMP6988_S16_t COE_b11;
|
||||||
|
QMP6988_S16_t COE_bp2;
|
||||||
|
QMP6988_S16_t COE_b12;
|
||||||
|
QMP6988_S16_t COE_b21;
|
||||||
|
QMP6988_S16_t COE_bp3;
|
||||||
|
} qmp6988_cali_data_t;
|
||||||
|
|
||||||
|
typedef struct _qmp6988_fk_data {
|
||||||
|
float a0, b00;
|
||||||
|
float a1, a2, bt1, bt2, bp1, b11, bp2, b12, b21, bp3;
|
||||||
|
} qmp6988_fk_data_t;
|
||||||
|
|
||||||
|
typedef struct _qmp6988_ik_data {
|
||||||
|
QMP6988_S32_t a0, b00;
|
||||||
|
QMP6988_S32_t a1, a2;
|
||||||
|
QMP6988_S64_t bt1, bt2, bp1, b11, bp2, b12, b21, bp3;
|
||||||
|
} qmp6988_ik_data_t;
|
||||||
|
|
||||||
|
typedef struct _qmp6988_data {
|
||||||
|
uint8_t slave;
|
||||||
|
uint8_t chip_id;
|
||||||
|
uint8_t power_mode;
|
||||||
|
float temperature;
|
||||||
|
float pressure;
|
||||||
|
float altitude;
|
||||||
|
qmp6988_cali_data_t qmp6988_cali;
|
||||||
|
qmp6988_ik_data_t ik;
|
||||||
|
} qmp6988_data_t;
|
||||||
|
|
||||||
|
class QMP6988 {
|
||||||
|
private:
|
||||||
|
qmp6988_data_t qmp6988;
|
||||||
|
uint8_t slave_addr;
|
||||||
|
TwoWire* device_wire;
|
||||||
|
void delayMS(unsigned int ms);
|
||||||
|
|
||||||
|
// read calibration data from otp
|
||||||
|
int getCalibrationData();
|
||||||
|
QMP6988_S32_t getPressure02e(qmp6988_ik_data_t* ik, QMP6988_S32_t dp,
|
||||||
|
QMP6988_S16_t tx);
|
||||||
|
QMP6988_S16_t convTx02e(qmp6988_ik_data_t* ik, QMP6988_S32_t dt);
|
||||||
|
|
||||||
|
void softwareReset();
|
||||||
|
|
||||||
|
public:
|
||||||
|
uint8_t init(uint8_t slave_addr = 0x56, TwoWire* wire_in = &Wire);
|
||||||
|
uint8_t deviceCheck();
|
||||||
|
|
||||||
|
float calcAltitude(float pressure, float temp);
|
||||||
|
float calcPressure();
|
||||||
|
float calcTemperature();
|
||||||
|
|
||||||
|
void setpPowermode(int power_mode);
|
||||||
|
void setFilter(unsigned char filter);
|
||||||
|
void setOversamplingP(unsigned char oversampling_p);
|
||||||
|
void setOversamplingT(unsigned char oversampling_t);
|
||||||
|
|
||||||
|
uint8_t writeReg(uint8_t slave, uint8_t reg_add, uint8_t reg_dat);
|
||||||
|
uint8_t readData(uint16_t slave, uint8_t reg_add, unsigned char* Read,
|
||||||
|
uint8_t num);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,47 @@
|
||||||
|
#include "GwSHT3X.h"
|
||||||
|
#ifdef _GWSHT3X
|
||||||
|
|
||||||
|
bool SHT3X::init(uint8_t slave_addr_in, TwoWire* wire_in)
|
||||||
|
{
|
||||||
|
_wire = wire_in;
|
||||||
|
_address=slave_addr_in;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte SHT3X::get()
|
||||||
|
{
|
||||||
|
unsigned int data[6];
|
||||||
|
|
||||||
|
// Start I2C Transmission
|
||||||
|
_wire->beginTransmission(_address);
|
||||||
|
// Send measurement command
|
||||||
|
_wire->write(0x2C);
|
||||||
|
_wire->write(0x06);
|
||||||
|
// Stop I2C transmission
|
||||||
|
if (_wire->endTransmission()!=0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
delay(200);
|
||||||
|
|
||||||
|
// Request 6 bytes of data
|
||||||
|
_wire->requestFrom(_address, (uint8_t) 6);
|
||||||
|
|
||||||
|
// Read 6 bytes of data
|
||||||
|
// cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc
|
||||||
|
for (int i=0;i<6;i++) {
|
||||||
|
data[i]=_wire->read();
|
||||||
|
};
|
||||||
|
|
||||||
|
delay(50);
|
||||||
|
|
||||||
|
if (_wire->available()!=0)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
// Convert the data
|
||||||
|
cTemp = ((((data[0] * 256.0) + data[1]) * 175) / 65535.0) - 45;
|
||||||
|
fTemp = (cTemp * 1.8) + 32;
|
||||||
|
humidity = ((((data[3] * 256.0) + data[4]) * 100) / 65535.0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,26 @@
|
||||||
|
#ifndef __SHT3X_H
|
||||||
|
#define __HT3X_H
|
||||||
|
//taken from https://github.com/m5stack/M5Unit-ENV/tree/0.0.8/src
|
||||||
|
|
||||||
|
#if ARDUINO >= 100
|
||||||
|
#include "Arduino.h"
|
||||||
|
#else
|
||||||
|
#include "WProgram.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Wire.h"
|
||||||
|
|
||||||
|
class SHT3X{
|
||||||
|
public:
|
||||||
|
bool init(uint8_t slave_addr_in=0x44, TwoWire* wire_in = &Wire);
|
||||||
|
byte get(void);
|
||||||
|
float cTemp=0;
|
||||||
|
float fTemp=0;
|
||||||
|
float humidity=0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t _address;
|
||||||
|
TwoWire* _wire;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,534 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"name": "SHT3X",
|
||||||
|
"replace": [
|
||||||
|
{
|
||||||
|
"b": "1",
|
||||||
|
"i": "11",
|
||||||
|
"n": "99"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "1",
|
||||||
|
"i": "12",
|
||||||
|
"n": "98"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "2",
|
||||||
|
"i": "21",
|
||||||
|
"n": "109"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "2",
|
||||||
|
"i": "22",
|
||||||
|
"n": "108"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "SHT3X$itmAct",
|
||||||
|
"label": "SHT3X$i Temp",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"description": "Enable the $i. I2C SHT3x temp sensor (bus $b)",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"SHT3X$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SHT3X$itmSrc",
|
||||||
|
"label": "SHT3X$i Temp Type",
|
||||||
|
"type": "list",
|
||||||
|
"default": "2",
|
||||||
|
"description": "the NMEA2000 source type for the temperature",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"l": "SeaTemperature",
|
||||||
|
"v": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "OutsideTemperature",
|
||||||
|
"v": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "InsideTemperature",
|
||||||
|
"v": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "EngineRoomTemperature",
|
||||||
|
"v": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "MainCabinTemperature",
|
||||||
|
"v": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "LiveWellTemperature",
|
||||||
|
"v": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "BaitWellTemperature",
|
||||||
|
"v": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "RefridgerationTemperature",
|
||||||
|
"v": "7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "HeatingSystemTemperature",
|
||||||
|
"v": "8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "DewPointTemperature",
|
||||||
|
"v": "9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "ApparentWindChillTemperature",
|
||||||
|
"v": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "TheoreticalWindChillTemperature",
|
||||||
|
"v": "11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "HeatIndexTemperature",
|
||||||
|
"v": "12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "FreezerTemperature",
|
||||||
|
"v": "13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "ExhaustGasTemperature",
|
||||||
|
"v": "14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "ShaftSealTemperature",
|
||||||
|
"v": "15"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"SHT3X$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SHT3X$ihuAct",
|
||||||
|
"label": "SHT3X$i Humidity",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"description": "Enable the $i. I2C SHT3x humidity sensor (bus $b)",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"SHT3X$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SHT3X$ihuSrc",
|
||||||
|
"label": "SHT3X$i Humid Type",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"l": "OutsideHumidity",
|
||||||
|
"v": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "Undef",
|
||||||
|
"v": "0xff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"SHT3X": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SHT3X$iiid",
|
||||||
|
"label": "SHT3X$i N2K iid",
|
||||||
|
"type": "number",
|
||||||
|
"default": "$n",
|
||||||
|
"description": "the N2K instance id for the $i. SHT3X Temperature and Humidity ",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"min": 0,
|
||||||
|
"max": 253,
|
||||||
|
"check": "checkMinMax",
|
||||||
|
"capabilities": {
|
||||||
|
"SHT3X$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SHT3X$iintv",
|
||||||
|
"label": "SHT3X$i Interval",
|
||||||
|
"type": "number",
|
||||||
|
"default": 2,
|
||||||
|
"description": "Interval(s) to query SHT3X Temperature and Humidity (1...300)",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"min": 1,
|
||||||
|
"max": 300,
|
||||||
|
"check": "checkMinMax",
|
||||||
|
"capabilities": {
|
||||||
|
"SHT3X$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SHT3X$itmNam",
|
||||||
|
"label": "SHT3X$i Temp XDR",
|
||||||
|
"type": "String",
|
||||||
|
"default": "Temp$i",
|
||||||
|
"description": "set the XDR transducer name for the $i. SHT3X Temperature, leave empty to disable NMEA0183 XDR ",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"SHT3X$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SHT3X$ihuNam",
|
||||||
|
"label": "SHT3X$i Humid XDR",
|
||||||
|
"type": "String",
|
||||||
|
"default": "Humidity$i",
|
||||||
|
"description": "set the XDR transducer name for the $i. SHT3X Humidity, leave empty to disable NMEA0183 XDR",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"SHT3X$i": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"name": "QMP6988",
|
||||||
|
"replace": [
|
||||||
|
{
|
||||||
|
"b": "1",
|
||||||
|
"i": "11",
|
||||||
|
"n": "97"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "1",
|
||||||
|
"i": "12",
|
||||||
|
"n": "96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "2",
|
||||||
|
"i": "21",
|
||||||
|
"n": "107"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "2",
|
||||||
|
"i": "22",
|
||||||
|
"n": "106"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "QMP6988$iprAct",
|
||||||
|
"label": "QMP6988-$i pressure",
|
||||||
|
"description": "activate the $i. QMP6988 pressure measurement (bus $b)",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"QMP6988$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "QMP6988$iiid",
|
||||||
|
"label": "QMP6988-$i N2K iid",
|
||||||
|
"type": "number",
|
||||||
|
"default": "$n",
|
||||||
|
"description": "the N2K instance id for the $i. QMP6988 pressure",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"min": 0,
|
||||||
|
"max": 253,
|
||||||
|
"check": "checkMinMax",
|
||||||
|
"capabilities": {
|
||||||
|
"QMP6988$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "QMP6988$iintv",
|
||||||
|
"label": "QMP6988-$i Interval",
|
||||||
|
"type": "number",
|
||||||
|
"default": 2,
|
||||||
|
"description": "Interval(s) to query the $i. QMP6988 Pressure (1...300)",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"min": 1,
|
||||||
|
"max": 300,
|
||||||
|
"check": "checkMinMax",
|
||||||
|
"capabilities": {
|
||||||
|
"QMP6988$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "QMP6988$iprNam",
|
||||||
|
"label": "QMP6988-$i Pressure XDR",
|
||||||
|
"type": "String",
|
||||||
|
"default": "Pressure$i",
|
||||||
|
"description": "set the XDR transducer name for the $i. QMP6988 Pressure, leave empty to disable NMEA0183 XDR",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"QMP6988$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "QMP6988$iprOff",
|
||||||
|
"label": "QMP6988-$i Pressure Offset",
|
||||||
|
"type": "number",
|
||||||
|
"description": "offset (in pa) to be added to the $i. QMP6988 pressure measurements",
|
||||||
|
"default": "0",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"QMP6988$i": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"name": "BME280",
|
||||||
|
"replace": [
|
||||||
|
{
|
||||||
|
"b": "1",
|
||||||
|
"i": "11",
|
||||||
|
"n": "95"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "1",
|
||||||
|
"i": "12",
|
||||||
|
"n": "94"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "2",
|
||||||
|
"i": "21",
|
||||||
|
"n": "105"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b": "2",
|
||||||
|
"i": "22",
|
||||||
|
"n": "104"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "BME280$itmAct",
|
||||||
|
"label": "BME280-$i Temp",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"description": "Enable the $i. I2C BME280 temp sensor (bus $b)",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$itmSrc",
|
||||||
|
"label": "BME280-$i Temp Type",
|
||||||
|
"type": "list",
|
||||||
|
"default": "2",
|
||||||
|
"description": "the NMEA2000 source type for the temperature",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"l": "SeaTemperature",
|
||||||
|
"v": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "OutsideTemperature",
|
||||||
|
"v": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "InsideTemperature",
|
||||||
|
"v": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "EngineRoomTemperature",
|
||||||
|
"v": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "MainCabinTemperature",
|
||||||
|
"v": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "LiveWellTemperature",
|
||||||
|
"v": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "BaitWellTemperature",
|
||||||
|
"v": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "RefridgerationTemperature",
|
||||||
|
"v": "7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "HeatingSystemTemperature",
|
||||||
|
"v": "8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "DewPointTemperature",
|
||||||
|
"v": "9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "ApparentWindChillTemperature",
|
||||||
|
"v": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "TheoreticalWindChillTemperature",
|
||||||
|
"v": "11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "HeatIndexTemperature",
|
||||||
|
"v": "12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "FreezerTemperature",
|
||||||
|
"v": "13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "ExhaustGasTemperature",
|
||||||
|
"v": "14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "ShaftSealTemperature",
|
||||||
|
"v": "15"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$itmOff",
|
||||||
|
"label": "BME280-$i Temperature Offset",
|
||||||
|
"type": "number",
|
||||||
|
"description": "offset (in °) to be added to the BME280 temperature measurements",
|
||||||
|
"default": "0",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$ihuAct",
|
||||||
|
"label": "BME280-$i Humidity",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"description": "Enable the $i. I2C BME280 humidity sensor",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$ihuSrc",
|
||||||
|
"label": "BME280-$i Humid Type",
|
||||||
|
"type": "list",
|
||||||
|
"description": "the NMEA2000 source type for the humidity",
|
||||||
|
"default": "0",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"l": "InsideHumidity",
|
||||||
|
"v": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "OutsideHumidity",
|
||||||
|
"v": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"l": "Undef",
|
||||||
|
"v": "0xff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$iprAct",
|
||||||
|
"label": "BME280-$i Pressure",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"description": "Enable the $i. I2C BME280 pressure sensor (bus $b)",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$iprOff",
|
||||||
|
"label": "BME280 Pressure Offset",
|
||||||
|
"type": "number",
|
||||||
|
"description": "offset (in pa) to be added to the BME280 pressure measurements",
|
||||||
|
"default": "0",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$iiid",
|
||||||
|
"label": "BME280-$i N2K iid",
|
||||||
|
"type": "number",
|
||||||
|
"default": "$n",
|
||||||
|
"description": "the N2K instance id for the BME280 Temperature and Humidity ",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"min": 0,
|
||||||
|
"max": 253,
|
||||||
|
"check": "checkMinMax",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$iintv",
|
||||||
|
"label": "BME280-$i Interval",
|
||||||
|
"type": "number",
|
||||||
|
"default": 2,
|
||||||
|
"description": "Interval(s) to query BME280 Temperature and Humidity (1...300)",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"min": 1,
|
||||||
|
"max": 300,
|
||||||
|
"check": "checkMinMax",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$itmNam",
|
||||||
|
"label": "BME280-$i Temp XDR",
|
||||||
|
"type": "String",
|
||||||
|
"default": "BTemp$i",
|
||||||
|
"description": "set the XDR transducer name for the BME280 Temperature, leave empty to disable NMEA0183 XDR ",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$ihuNam",
|
||||||
|
"label": "BME280-$i Humid XDR",
|
||||||
|
"type": "String",
|
||||||
|
"default": "BHumidity$i",
|
||||||
|
"description": "set the XDR transducer name for the BME280 Humidity, leave empty to disable NMEA0183 XDR",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BME280$iprNam",
|
||||||
|
"label": "BME280-$i Pressure XDR",
|
||||||
|
"type": "String",
|
||||||
|
"default": "BPressure$i",
|
||||||
|
"description": "set the XDR transducer name for the BME280 Pressure, leave empty to disable NMEA0183 XDR",
|
||||||
|
"category": "iicsensors$b",
|
||||||
|
"capabilities": {
|
||||||
|
"BME280$i": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,37 @@
|
||||||
|
[platformio]
|
||||||
|
#basically for testing purposes
|
||||||
|
[env:m5stack-atom-env3]
|
||||||
|
extends = sensors
|
||||||
|
board = m5stack-atom
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
${sensors.lib_deps}
|
||||||
|
build_flags=
|
||||||
|
-D M5_ENV3
|
||||||
|
-D M5_CAN_KIT
|
||||||
|
${env.build_flags}
|
||||||
|
|
||||||
|
[env:m5stack-atom-bme280]
|
||||||
|
extends = sensors
|
||||||
|
board = m5stack-atom
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
${sensors.lib_deps}
|
||||||
|
build_flags=
|
||||||
|
-D GWBME280=-1
|
||||||
|
-D M5_GROOVEIIC
|
||||||
|
-D M5_CAN_KIT
|
||||||
|
${env.build_flags}
|
||||||
|
|
||||||
|
[env:m5stack-atom-bme2802]
|
||||||
|
extends = sensors
|
||||||
|
board = m5stack-atom
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
${sensors.lib_deps}
|
||||||
|
build_flags=
|
||||||
|
-D GWBME280
|
||||||
|
-D GWBME2802
|
||||||
|
-D M5_GROOVEIIC
|
||||||
|
-D M5_CAN_KIT
|
||||||
|
${env.build_flags}
|
|
@ -1,61 +0,0 @@
|
||||||
#include "GwLeds.h"
|
|
||||||
#include "GwHardware.h"
|
|
||||||
#include "GwApi.h"
|
|
||||||
#include "FastLED.h"
|
|
||||||
|
|
||||||
static GwLedMode mode=LED_OFF;
|
|
||||||
void setLedMode(GwLedMode newMode){
|
|
||||||
//we consider the mode to an atomic item...
|
|
||||||
mode=newMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static CRGB::HTMLColorCode colorFromMode(GwLedMode cmode){
|
|
||||||
switch(cmode){
|
|
||||||
case LED_BLUE:
|
|
||||||
return CRGB::Blue;
|
|
||||||
case LED_GREEN:
|
|
||||||
return CRGB::Green;
|
|
||||||
case LED_RED:
|
|
||||||
return CRGB::Red;
|
|
||||||
case LED_WHITE:
|
|
||||||
return CRGB::White;
|
|
||||||
default:
|
|
||||||
return CRGB::Black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void handleLeds(void *param){
|
|
||||||
GwApi *api=(GwApi*)param;
|
|
||||||
GwLog *logger=api->getLogger();
|
|
||||||
#ifndef GWLED_FASTLED
|
|
||||||
LOG_DEBUG(GwLog::LOG,"currently only fastled handling");
|
|
||||||
delay(50);
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
return;
|
|
||||||
#else
|
|
||||||
CRGB leds[1];
|
|
||||||
#ifdef GWLED_SCHEMA
|
|
||||||
FastLED.addLeds<GWLED_TYPE,GWLED_PIN,GWLED_SCHEMA>(leds,1);
|
|
||||||
#else
|
|
||||||
FastLED.addLeds<GWLED_TYPE,GWLED_PIN>(leds,1);
|
|
||||||
#endif
|
|
||||||
#ifdef GWLED_BRIGHTNESS
|
|
||||||
uint8_t brightness=GWLED_BRIGHTNESS;
|
|
||||||
#else
|
|
||||||
uint8_t brightness=128; //50%
|
|
||||||
#endif
|
|
||||||
GwLedMode currentMode=mode;
|
|
||||||
leds[0]=colorFromMode(currentMode);
|
|
||||||
FastLED.setBrightness(brightness);
|
|
||||||
FastLED.show();
|
|
||||||
while(true){
|
|
||||||
delay(50);
|
|
||||||
GwLedMode newMode=mode;
|
|
||||||
if (newMode != currentMode){
|
|
||||||
leds[0]=colorFromMode(newMode);
|
|
||||||
FastLED.show();
|
|
||||||
currentMode=newMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vTaskDelete(NULL);
|
|
||||||
#endif
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
#ifndef _GWLEDS_H
|
|
||||||
#define _GWLEDS_H
|
|
||||||
//task function
|
|
||||||
void handleLeds(void *param);
|
|
||||||
typedef enum {
|
|
||||||
LED_OFF,
|
|
||||||
LED_GREEN,
|
|
||||||
LED_BLUE,
|
|
||||||
LED_RED,
|
|
||||||
LED_WHITE
|
|
||||||
} GwLedMode;
|
|
||||||
void setLedMode(GwLedMode mode);
|
|
||||||
#endif
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
#include "GwLedTask.h"
|
||||||
|
#include "GwHardware.h"
|
||||||
|
#include "GwApi.h"
|
||||||
|
#include "FastLED.h"
|
||||||
|
typedef enum {
|
||||||
|
LED_OFF,
|
||||||
|
LED_GREEN,
|
||||||
|
LED_BLUE,
|
||||||
|
LED_RED,
|
||||||
|
LED_WHITE
|
||||||
|
} GwLedMode;
|
||||||
|
|
||||||
|
static CRGB::HTMLColorCode colorFromMode(GwLedMode cmode){
|
||||||
|
switch(cmode){
|
||||||
|
case LED_BLUE:
|
||||||
|
return CRGB::Blue;
|
||||||
|
case LED_GREEN:
|
||||||
|
return CRGB::Green;
|
||||||
|
case LED_RED:
|
||||||
|
return CRGB::Red;
|
||||||
|
case LED_WHITE:
|
||||||
|
return CRGB::White;
|
||||||
|
default:
|
||||||
|
return CRGB::Black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void handleLeds(GwApi *api){
|
||||||
|
GwLog *logger=api->getLogger();
|
||||||
|
#ifndef GWLED_FASTLED
|
||||||
|
LOG_DEBUG(GwLog::LOG,"currently only fastled handling");
|
||||||
|
delay(50);
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
CRGB leds[1];
|
||||||
|
#ifdef GWLED_SCHEMA
|
||||||
|
FastLED.addLeds<GWLED_TYPE,GWLED_PIN,(EOrder)GWLED_SCHEMA>(leds,1);
|
||||||
|
#else
|
||||||
|
FastLED.addLeds<GWLED_TYPE,GWLED_PIN>(leds,1);
|
||||||
|
#endif
|
||||||
|
uint8_t brightness=api->getConfig()->getInt(GwConfigDefinitions::ledBrightness,128);
|
||||||
|
GwLedMode currentMode=LED_GREEN;
|
||||||
|
leds[0]=colorFromMode(currentMode);
|
||||||
|
FastLED.setBrightness(brightness);
|
||||||
|
FastLED.show();
|
||||||
|
LOG_DEBUG(GwLog::LOG,"led task started with mode %d",(int)currentMode);
|
||||||
|
int apiResult=0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
delay(50);
|
||||||
|
GwLedMode newMode = currentMode;
|
||||||
|
IButtonTask buttonState = api->taskInterfaces()->get<IButtonTask>(apiResult);
|
||||||
|
if (apiResult >= 0)
|
||||||
|
{
|
||||||
|
switch (buttonState.state)
|
||||||
|
{
|
||||||
|
case IButtonTask::PRESSED_5:
|
||||||
|
newMode = LED_BLUE;
|
||||||
|
break;
|
||||||
|
case IButtonTask::PRESSED_10:
|
||||||
|
newMode = LED_RED;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
newMode = LED_GREEN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newMode = LED_WHITE;
|
||||||
|
}
|
||||||
|
if (newMode != currentMode)
|
||||||
|
{
|
||||||
|
leds[0] = colorFromMode(newMode);
|
||||||
|
FastLED.show();
|
||||||
|
currentMode = newMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef _GWLEDS_H
|
||||||
|
#define _GWLEDS_H
|
||||||
|
#include "GwApi.h"
|
||||||
|
//task function
|
||||||
|
void handleLeds(GwApi *param);
|
||||||
|
|
||||||
|
DECLARE_USERTASK(handleLeds);
|
||||||
|
#endif
|
|
@ -1,22 +1,41 @@
|
||||||
#include "GwLog.h"
|
#include "GwLog.h"
|
||||||
|
#include "GwHardware.h"
|
||||||
|
|
||||||
class DefaultLogWriter: public GwLogWriter{
|
|
||||||
public:
|
|
||||||
virtual ~DefaultLogWriter(){};
|
|
||||||
virtual void write(const char *data){
|
|
||||||
Serial.print(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
GwLog::GwLog(int level, GwLogWriter *writer){
|
GwLog::GwLog(int level, GwLogWriter *writer){
|
||||||
logLevel=level;
|
logLevel=level;
|
||||||
if (writer == NULL) writer=new DefaultLogWriter();
|
|
||||||
this->writer=writer;
|
this->writer=writer;
|
||||||
|
if (!writer){
|
||||||
|
iniBuffer=new char[INIBUFFERSIZE];
|
||||||
|
iniBuffer[0]=0;
|
||||||
|
}
|
||||||
locker = xSemaphoreCreateMutex();
|
locker = xSemaphoreCreateMutex();
|
||||||
}
|
}
|
||||||
GwLog::~GwLog(){
|
GwLog::~GwLog(){
|
||||||
vSemaphoreDelete(locker);
|
vSemaphoreDelete(locker);
|
||||||
}
|
}
|
||||||
|
void GwLog::writeOut(const char *data)
|
||||||
|
{
|
||||||
|
if (!writer)
|
||||||
|
{
|
||||||
|
if (iniBuffer && iniBufferFill < (INIBUFFERSIZE - 1))
|
||||||
|
{
|
||||||
|
size_t remain = INIBUFFERSIZE - iniBufferFill-1;
|
||||||
|
size_t len = strlen(data);
|
||||||
|
if (len < remain)
|
||||||
|
remain = len;
|
||||||
|
if (remain){
|
||||||
|
memcpy(iniBuffer + iniBufferFill, data, remain);
|
||||||
|
iniBufferFill += remain;
|
||||||
|
iniBuffer[iniBufferFill] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer->write(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
void GwLog::logString(const char *fmt,...){
|
void GwLog::logString(const char *fmt,...){
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args,fmt);
|
va_start(args,fmt);
|
||||||
|
@ -24,42 +43,43 @@ void GwLog::logString(const char *fmt,...){
|
||||||
recordCounter++;
|
recordCounter++;
|
||||||
vsnprintf(buffer,bufferSize-1,fmt,args);
|
vsnprintf(buffer,bufferSize-1,fmt,args);
|
||||||
buffer[bufferSize-1]=0;
|
buffer[bufferSize-1]=0;
|
||||||
if (! writer) {
|
writeOut(prefix.c_str());
|
||||||
xSemaphoreGive(locker);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writer->write(prefix.c_str());
|
|
||||||
char buf[20];
|
char buf[20];
|
||||||
snprintf(buf,20,"%lu:",millis());
|
snprintf(buf,20,"%lu:",millis());
|
||||||
writer->write(buf);
|
writeOut(buf);
|
||||||
writer->write(buffer);
|
writeOut(buffer);
|
||||||
writer->write("\n");
|
writeOut("\n");
|
||||||
xSemaphoreGive(locker);
|
xSemaphoreGive(locker);
|
||||||
}
|
}
|
||||||
void GwLog::logDebug(int level,const char *fmt,...){
|
void GwLog::logDebug(int level,const char *fmt,...){
|
||||||
if (level > logLevel) return;
|
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args,fmt);
|
va_start(args,fmt);
|
||||||
|
logDebug(level,fmt,args);
|
||||||
|
}
|
||||||
|
void GwLog::logDebug(int level,const char *fmt,va_list args){
|
||||||
|
if (level > logLevel) return;
|
||||||
xSemaphoreTake(locker, portMAX_DELAY);
|
xSemaphoreTake(locker, portMAX_DELAY);
|
||||||
recordCounter++;
|
recordCounter++;
|
||||||
vsnprintf(buffer,bufferSize-1,fmt,args);
|
vsnprintf(buffer,bufferSize-1,fmt,args);
|
||||||
buffer[bufferSize-1]=0;
|
buffer[bufferSize-1]=0;
|
||||||
if (! writer) {
|
writeOut(prefix.c_str());
|
||||||
xSemaphoreGive(locker);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writer->write(prefix.c_str());
|
|
||||||
char buf[20];
|
char buf[20];
|
||||||
snprintf(buf,20,"%lu:",millis());
|
snprintf(buf,20,"%lu:",millis());
|
||||||
writer->write(buf);
|
writeOut(buf);
|
||||||
writer->write(buffer);
|
writeOut(buffer);
|
||||||
writer->write("\n");
|
writeOut("\n");
|
||||||
xSemaphoreGive(locker);
|
xSemaphoreGive(locker);
|
||||||
}
|
}
|
||||||
void GwLog::setWriter(GwLogWriter *writer){
|
void GwLog::setWriter(GwLogWriter *writer){
|
||||||
xSemaphoreTake(locker, portMAX_DELAY);
|
xSemaphoreTake(locker, portMAX_DELAY);
|
||||||
if (this->writer) delete this->writer;
|
if (this->writer) delete this->writer;
|
||||||
this->writer=writer;
|
this->writer=writer;
|
||||||
|
if (iniBuffer && iniBufferFill){
|
||||||
|
writer->write(iniBuffer);
|
||||||
|
iniBufferFill=0;
|
||||||
|
delete[] iniBuffer;
|
||||||
|
iniBuffer=nullptr;
|
||||||
|
}
|
||||||
xSemaphoreGive(locker);
|
xSemaphoreGive(locker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,10 @@ class GwLog{
|
||||||
GwLogWriter *writer;
|
GwLogWriter *writer;
|
||||||
SemaphoreHandle_t locker;
|
SemaphoreHandle_t locker;
|
||||||
long long recordCounter=0;
|
long long recordCounter=0;
|
||||||
|
const size_t INIBUFFERSIZE=1024;
|
||||||
|
char *iniBuffer=nullptr;
|
||||||
|
size_t iniBufferFill=0;
|
||||||
|
void writeOut(const char *data);
|
||||||
public:
|
public:
|
||||||
static const int LOG=1;
|
static const int LOG=1;
|
||||||
static const int ERROR=0;
|
static const int ERROR=0;
|
||||||
|
@ -27,6 +31,7 @@ class GwLog{
|
||||||
void setWriter(GwLogWriter *writer);
|
void setWriter(GwLogWriter *writer);
|
||||||
void logString(const char *fmt,...);
|
void logString(const char *fmt,...);
|
||||||
void logDebug(int level, const char *fmt,...);
|
void logDebug(int level, const char *fmt,...);
|
||||||
|
void logDebug(int level, const char *fmt,va_list ap);
|
||||||
int isActive(int level){return level <= logLevel;};
|
int isActive(int level){return level <= logLevel;};
|
||||||
void flush();
|
void flush();
|
||||||
void setLevel(int level){this->logLevel=level;}
|
void setLevel(int level){this->logLevel=level;}
|
||||||
|
|
|
@ -160,7 +160,7 @@ class MyAisDecoder : public AIS::AisDecoder
|
||||||
_uToPort + _uToStarboard, _uToStarboard, _uToBow, eta_days,
|
_uToPort + _uToStarboard, _uToStarboard, _uToBow, eta_days,
|
||||||
(_uEtaHour * 3600) + (_uEtaMinute * 60), _uDraught / 10.0, Dest,
|
(_uEtaHour * 3600) + (_uEtaMinute * 60), _uDraught / 10.0, Dest,
|
||||||
(tN2kAISVersion) _ais_version, (tN2kGNSStype) _uFixType,
|
(tN2kAISVersion) _ais_version, (tN2kGNSStype) _uFixType,
|
||||||
(tN2kAISDTE) _dte, (tN2kAISTranceiverInfo) _ais_version);
|
(tN2kAISDTE) _dte, (tN2kAISTransceiverInformation) _ais_version);
|
||||||
|
|
||||||
send(N2kMsg);
|
send(N2kMsg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,291 +0,0 @@
|
||||||
/**
|
|
||||||
* @section License
|
|
||||||
*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2017, Thomas Barth, barth-dev.de
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person
|
|
||||||
* obtaining a copy of this software and associated documentation
|
|
||||||
* files (the "Software"), to deal in the Software without
|
|
||||||
* restriction, including without limitation the rights to use, copy,
|
|
||||||
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
* of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be
|
|
||||||
* included in all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
||||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __DRIVERS_CAN_REGDEF_H_
|
|
||||||
#define __DRIVERS_CAN_REGDEF_H_
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
CAN_SPEED_100KBPS=100, /**< \brief CAN Node runs at 100kBit/s. */
|
|
||||||
CAN_SPEED_125KBPS=125, /**< \brief CAN Node runs at 125kBit/s. */
|
|
||||||
CAN_SPEED_250KBPS=250, /**< \brief CAN Node runs at 250kBit/s. */
|
|
||||||
CAN_SPEED_500KBPS=500, /**< \brief CAN Node runs at 500kBit/s. */
|
|
||||||
CAN_SPEED_800KBPS=800, /**< \brief CAN Node runs at 800kBit/s. */
|
|
||||||
CAN_SPEED_1000KBPS=1000 /**< \brief CAN Node runs at 1000kBit/s. */
|
|
||||||
}CAN_speed_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief CAN frame type (standard/extended)
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
CAN_frame_std=0, /**< Standard frame, using 11 bit identifer. */
|
|
||||||
CAN_frame_ext=1 /**< Extended frame, using 29 bit identifer. */
|
|
||||||
}CAN_frame_format_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief CAN RTR
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
CAN_no_RTR=0, /**< No RTR frame. */
|
|
||||||
CAN_RTR=1 /**< RTR frame. */
|
|
||||||
}CAN_RTR_t;
|
|
||||||
|
|
||||||
/** \brief Frame information record type */
|
|
||||||
typedef union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
uint8_t DLC:4; /**< \brief [3:0] DLC, Data length container */
|
|
||||||
unsigned int unknown_2:2; /**< \brief \internal unknown */
|
|
||||||
CAN_RTR_t RTR:1; /**< \brief [6:6] RTR, Remote Transmission Request */
|
|
||||||
CAN_frame_format_t FF:1; /**< \brief [7:7] Frame Format, see# CAN_frame_format_t*/
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} CAN_FIR_t;
|
|
||||||
|
|
||||||
/** \brief Start address of CAN registers */
|
|
||||||
#define MODULE_CAN ((volatile CAN_Module_t *)0x3ff6b000)
|
|
||||||
|
|
||||||
/** \brief Get standard message ID */
|
|
||||||
#define _CAN_GET_STD_ID (((uint32_t)MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[0] << 3) | \
|
|
||||||
(MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[1] >> 5))
|
|
||||||
|
|
||||||
/** \brief Get extended message ID */
|
|
||||||
#define _CAN_GET_EXT_ID (((uint32_t)MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[0] << 21) | \
|
|
||||||
(MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[1] << 13) | \
|
|
||||||
(MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[2] << 5) | \
|
|
||||||
(MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[3] >> 3 ))
|
|
||||||
|
|
||||||
/** \brief Set standard message ID */
|
|
||||||
#define _CAN_SET_STD_ID(x) MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[0] = ((x) >> 3); \
|
|
||||||
MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.STD.ID[1] = ((x) << 5);
|
|
||||||
|
|
||||||
/** \brief Set extended message ID */
|
|
||||||
#define _CAN_SET_EXT_ID(x) MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[0] = ((x) >> 21); \
|
|
||||||
MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[1] = ((x) >> 13); \
|
|
||||||
MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[2] = ((x) >> 5); \
|
|
||||||
MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[3] = ((x) << 3); \
|
|
||||||
|
|
||||||
/** \brief Interrupt status register */
|
|
||||||
typedef enum {
|
|
||||||
__CAN_IRQ_RX= BIT(0), /**< \brief RX Interrupt */
|
|
||||||
__CAN_IRQ_TX= BIT(1), /**< \brief TX Interrupt */
|
|
||||||
__CAN_IRQ_ERR= BIT(2), /**< \brief Error Interrupt */
|
|
||||||
__CAN_IRQ_DATA_OVERRUN= BIT(3), /**< \brief Date Overrun Interrupt */
|
|
||||||
__CAN_IRQ_WAKEUP= BIT(4), /**< \brief Wakeup Interrupt */
|
|
||||||
__CAN_IRQ_ERR_PASSIVE= BIT(5), /**< \brief Passive Error Interrupt */
|
|
||||||
__CAN_IRQ_ARB_LOST= BIT(6), /**< \brief Arbitration lost interrupt */
|
|
||||||
__CAN_IRQ_BUS_ERR= BIT(7), /**< \brief Bus error Interrupt */
|
|
||||||
}__CAN_IRQ_t;
|
|
||||||
|
|
||||||
|
|
||||||
/** \brief OCMODE options. */
|
|
||||||
typedef enum {
|
|
||||||
__CAN_OC_BOM= 0b00, /**< \brief bi-phase output mode */
|
|
||||||
__CAN_OC_TOM= 0b01, /**< \brief test output mode */
|
|
||||||
__CAN_OC_NOM= 0b10, /**< \brief normal output mode */
|
|
||||||
__CAN_OC_COM= 0b11, /**< \brief clock output mode */
|
|
||||||
}__CAN_OCMODE_t;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CAN controller (SJA1000).
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int RM:1; /**< \brief MOD.0 Reset Mode */
|
|
||||||
unsigned int LOM:1; /**< \brief MOD.1 Listen Only Mode */
|
|
||||||
unsigned int STM:1; /**< \brief MOD.2 Self Test Mode */
|
|
||||||
unsigned int AFM:1; /**< \brief MOD.3 Acceptance Filter Mode */
|
|
||||||
unsigned int SM:1; /**< \brief MOD.4 Sleep Mode */
|
|
||||||
unsigned int reserved_27:27; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} MOD;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int TR:1; /**< \brief CMR.0 Transmission Request */
|
|
||||||
unsigned int AT:1; /**< \brief CMR.1 Abort Transmission */
|
|
||||||
unsigned int RRB:1; /**< \brief CMR.2 Release Receive Buffer */
|
|
||||||
unsigned int CDO:1; /**< \brief CMR.3 Clear Data Overrun */
|
|
||||||
unsigned int GTS:1; /**< \brief CMR.4 Go To Sleep */
|
|
||||||
unsigned int reserved_27:27; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} CMR;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int RBS:1; /**< \brief SR.0 Receive Buffer Status */
|
|
||||||
unsigned int DOS:1; /**< \brief SR.1 Data Overrun Status */
|
|
||||||
unsigned int TBS:1; /**< \brief SR.2 Transmit Buffer Status */
|
|
||||||
unsigned int TCS:1; /**< \brief SR.3 Transmission Complete Status */
|
|
||||||
unsigned int RS:1; /**< \brief SR.4 Receive Status */
|
|
||||||
unsigned int TS:1; /**< \brief SR.5 Transmit Status */
|
|
||||||
unsigned int ES:1; /**< \brief SR.6 Error Status */
|
|
||||||
unsigned int BS:1; /**< \brief SR.7 Bus Status */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} SR;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int RI:1; /**< \brief IR.0 Receive Interrupt */
|
|
||||||
unsigned int TI:1; /**< \brief IR.1 Transmit Interrupt */
|
|
||||||
unsigned int EI:1; /**< \brief IR.2 Error Interrupt */
|
|
||||||
unsigned int DOI:1; /**< \brief IR.3 Data Overrun Interrupt */
|
|
||||||
unsigned int WUI:1; /**< \brief IR.4 Wake-Up Interrupt */
|
|
||||||
unsigned int EPI:1; /**< \brief IR.5 Error Passive Interrupt */
|
|
||||||
unsigned int ALI:1; /**< \brief IR.6 Arbitration Lost Interrupt */
|
|
||||||
unsigned int BEI:1; /**< \brief IR.7 Bus Error Interrupt */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} IR;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int RIE:1; /**< \brief IER.0 Receive Interrupt Enable */
|
|
||||||
unsigned int TIE:1; /**< \brief IER.1 Transmit Interrupt Enable */
|
|
||||||
unsigned int EIE:1; /**< \brief IER.2 Error Interrupt Enable */
|
|
||||||
unsigned int DOIE:1; /**< \brief IER.3 Data Overrun Interrupt Enable */
|
|
||||||
unsigned int WUIE:1; /**< \brief IER.4 Wake-Up Interrupt Enable */
|
|
||||||
unsigned int EPIE:1; /**< \brief IER.5 Error Passive Interrupt Enable */
|
|
||||||
unsigned int ALIE:1; /**< \brief IER.6 Arbitration Lost Interrupt Enable */
|
|
||||||
unsigned int BEIE:1; /**< \brief IER.7 Bus Error Interrupt Enable */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} IER;
|
|
||||||
uint32_t RESERVED0;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int BRP:6; /**< \brief BTR0[5:0] Baud Rate Prescaler */
|
|
||||||
unsigned int SJW:2; /**< \brief BTR0[7:6] Synchronization Jump Width*/
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} BTR0;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int TSEG1:4; /**< \brief BTR1[3:0] Timing Segment 1 */
|
|
||||||
unsigned int TSEG2:3; /**< \brief BTR1[6:4] Timing Segment 2*/
|
|
||||||
unsigned int SAM:1; /**< \brief BTR1.7 Sampling*/
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} BTR1;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int OCMODE:2; /**< \brief OCR[1:0] Output Control Mode, see # */
|
|
||||||
unsigned int OCPOL0:1; /**< \brief OCR.2 Output Control Polarity 0 */
|
|
||||||
unsigned int OCTN0:1; /**< \brief OCR.3 Output Control Transistor N0 */
|
|
||||||
unsigned int OCTP0:1; /**< \brief OCR.4 Output Control Transistor P0 */
|
|
||||||
unsigned int OCPOL1:1; /**< \brief OCR.5 Output Control Polarity 1 */
|
|
||||||
unsigned int OCTN1:1; /**< \brief OCR.6 Output Control Transistor N1 */
|
|
||||||
unsigned int OCTP1:1; /**< \brief OCR.7 Output Control Transistor P1 */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} OCR;
|
|
||||||
uint32_t RESERVED1[2];
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int ALC:8; /**< \brief ALC[7:0] Arbitration Lost Capture */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} ALC;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int ECC:8; /**< \brief ECC[7:0] Error Code Capture */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} ECC;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int EWLR:8; /**< \brief EWLR[7:0] Error Warning Limit */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} EWLR;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int RXERR:8; /**< \brief RXERR[7:0] Receive Error Counter */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} RXERR;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int TXERR:8; /**< \brief TXERR[7:0] Transmit Error Counter */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} TXERR;
|
|
||||||
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
uint32_t CODE[4]; /**< \brief Acceptance Message ID */
|
|
||||||
uint32_t MASK[4]; /**< \brief Acceptance Mask */
|
|
||||||
uint32_t RESERVED2[5];
|
|
||||||
} ACC; /**< \brief Acceptance filtering */
|
|
||||||
struct {
|
|
||||||
CAN_FIR_t FIR; /**< \brief Frame information record */
|
|
||||||
union{
|
|
||||||
struct {
|
|
||||||
uint32_t ID[2]; /**< \brief Standard frame message-ID*/
|
|
||||||
uint32_t data[8]; /**< \brief Standard frame payload */
|
|
||||||
uint32_t reserved[2];
|
|
||||||
} STD; /**< \brief Standard frame format */
|
|
||||||
struct {
|
|
||||||
uint32_t ID[4]; /**< \brief Extended frame message-ID*/
|
|
||||||
uint32_t data[8]; /**< \brief Extended frame payload */
|
|
||||||
} EXT; /**< \brief Extended frame format */
|
|
||||||
}TX_RX; /**< \brief RX/TX interface */
|
|
||||||
}FCTRL; /**< \brief Function control regs */
|
|
||||||
} MBX_CTRL; /**< \brief Mailbox control */
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int RMC:8; /**< \brief RMC[7:0] RX Message Counter */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved Enable */
|
|
||||||
} B;
|
|
||||||
} RMC;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int RBSA:8; /**< \brief RBSA[7:0] RX Buffer Start Address */
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved Enable */
|
|
||||||
} B;
|
|
||||||
} RBSA;
|
|
||||||
union{uint32_t U; /**< \brief Unsigned access */
|
|
||||||
struct {
|
|
||||||
unsigned int COD:3; /**< \brief CDR[2:0] CLKOUT frequency selector based of fOSC*/
|
|
||||||
unsigned int COFF:1; /**< \brief CDR.3 CLKOUT off*/
|
|
||||||
unsigned int reserved_1:1; /**< \brief \internal Reserved */
|
|
||||||
unsigned int RXINTEN:1; /**< \brief CDR.5 This bit allows the TX1 output to be used as a dedicated receive interrupt output*/
|
|
||||||
unsigned int CBP:1; /**< \brief CDR.6 allows to bypass the CAN input comparator and is only possible in reset mode.*/
|
|
||||||
unsigned int CAN_M:1; /**< \brief CDR.7 If CDR.7 is at logic 0 the CAN controller operates in BasicCAN mode. If set to logic 1 the CAN controller operates in PeliCAN mode. Write access is only possible in reset mode*/
|
|
||||||
unsigned int reserved_24:24; /**< \brief \internal Reserved */
|
|
||||||
} B;
|
|
||||||
} CDR;
|
|
||||||
uint32_t IRAM[2];
|
|
||||||
}CAN_Module_t;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* __DRIVERS_CAN_REGDEF_H_ */
|
|
|
@ -1,385 +0,0 @@
|
||||||
/*
|
|
||||||
NMEA2000_esp32.cpp
|
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Timo Lappalainen, Kave Oy, www.kave.fi
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to use,
|
|
||||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
||||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
||||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
||||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
Inherited NMEA2000 object for ESP32 modules. See also NMEA2000 library.
|
|
||||||
|
|
||||||
Thanks to Thomas Barth, barth-dev.de, who has written ESP32 CAN code. To avoid extra
|
|
||||||
libraries, I implemented his code directly to the NMEA2000_esp32 to avoid extra
|
|
||||||
can.h library, which may cause even naming problem.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "driver/periph_ctrl.h"
|
|
||||||
|
|
||||||
#include "soc/dport_reg.h"
|
|
||||||
#include "NMEA2000_esp32.h"
|
|
||||||
|
|
||||||
//time to reinit CAN bus if the queue is full for that time
|
|
||||||
#define SEND_CANCEL_TIME 2000
|
|
||||||
//reinit CAN bis if nothing send/received within this time
|
|
||||||
#define RECEIVE_REINIT_TIME 60000
|
|
||||||
|
|
||||||
bool tNMEA2000_esp32::CanInUse=false;
|
|
||||||
tNMEA2000_esp32 *pNMEA2000_esp32=0;
|
|
||||||
|
|
||||||
void ESP32Can1Interrupt(void *);
|
|
||||||
|
|
||||||
#define ECDEBUG(fmt,args...) if(debugStream){debugStream->printf(fmt, ## args);}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
tNMEA2000_esp32::tNMEA2000_esp32(gpio_num_t _TxPin,
|
|
||||||
gpio_num_t _RxPin,
|
|
||||||
Print *dbg) :
|
|
||||||
tNMEA2000(), IsOpen(false),
|
|
||||||
speed(CAN_SPEED_250KBPS), TxPin(_TxPin), RxPin(_RxPin),
|
|
||||||
RxQueue(NULL), TxQueue(NULL) {
|
|
||||||
debugStream=dbg;
|
|
||||||
}
|
|
||||||
//*****************************************************************************
|
|
||||||
bool tNMEA2000_esp32::CANSendFrame(unsigned long id, unsigned char len, const unsigned char *buf, bool /*wait_sent*/) {
|
|
||||||
tCANFrame frame;
|
|
||||||
unsigned long now=millis();
|
|
||||||
if ( uxQueueSpacesAvailable(TxQueue)==0 ) {
|
|
||||||
if (lastSend && (lastSend + SEND_CANCEL_TIME) < now){
|
|
||||||
ECDEBUG("CanSendFrame Aborting and emptying queue\n");
|
|
||||||
while (xQueueReceive(TxQueue,&frame,0)){}
|
|
||||||
errReinit++;
|
|
||||||
CAN_init(false);
|
|
||||||
if ( uxQueueSpacesAvailable(TxQueue)==0 ) return false;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
ECDEBUG("CanSendFrame queue full\n");
|
|
||||||
return false; // can not send to queue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastSend=now;
|
|
||||||
frame.id=id;
|
|
||||||
frame.len=len>8?8:len;
|
|
||||||
memcpy(frame.buf,buf,len);
|
|
||||||
CheckBusOff();
|
|
||||||
ECDEBUG("CanSendFrame IntrCnt %d\n",cntIntr);
|
|
||||||
ECDEBUG("CanSendFrame Error TX/RX %d/%d\n",MODULE_CAN->TXERR.U,MODULE_CAN->RXERR.U);
|
|
||||||
ECDEBUG("CanSendFrame Error Overrun %d\n",errOverrun);
|
|
||||||
ECDEBUG("CanSendFrame Error Arbitration %d\n",errArb);
|
|
||||||
ECDEBUG("CanSendFrame Error Bus %d\n",errBus);
|
|
||||||
ECDEBUG("CanSendFrame Error Recovery %d\n",errRecovery);
|
|
||||||
ECDEBUG("CanSendFrame ErrorCount %d\n",errCountTxInternal);
|
|
||||||
ECDEBUG("CanSendFrame ErrorCancel %d\n",errCancelTransmit);
|
|
||||||
ECDEBUG("CanSendFrame ErrorReinit %d\n",errReinit);
|
|
||||||
ECDEBUG("CanSendFrame busOff=%d, errPassive=%d\n",MODULE_CAN->SR.B.BS,MODULE_CAN->SR.B.ES)
|
|
||||||
xQueueSendToBack(TxQueue,&frame,0); // Add frame to queue
|
|
||||||
if ( MODULE_CAN->SR.B.TBS==0 ) {
|
|
||||||
ECDEBUG("CanSendFrame: wait for ISR to send %d\n",frame.id);
|
|
||||||
return true; // Currently sending, ISR takes care of sending
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( MODULE_CAN->SR.B.TBS==1 ) { // Check again and restart send, if is not going on
|
|
||||||
xQueueReceive(TxQueue,&frame,0);
|
|
||||||
ECDEBUG("CanSendFrame: send direct %d\n",frame.id);
|
|
||||||
CAN_send_frame(frame);
|
|
||||||
//return MODULE_CAN->TXERR.U < 127;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
void tNMEA2000_esp32::InitCANFrameBuffers() {
|
|
||||||
if (MaxCANReceiveFrames<10 ) MaxCANReceiveFrames=50; // ESP32 has plenty of RAM
|
|
||||||
if (MaxCANSendFrames<10 ) MaxCANSendFrames=40;
|
|
||||||
uint16_t CANGlobalBufSize=MaxCANSendFrames-4;
|
|
||||||
MaxCANSendFrames=4; // we do not need much libary internal buffer since driver has them.
|
|
||||||
RxQueue=xQueueCreate(MaxCANReceiveFrames,sizeof(tCANFrame));
|
|
||||||
TxQueue=xQueueCreate(CANGlobalBufSize,sizeof(tCANFrame));
|
|
||||||
|
|
||||||
tNMEA2000::InitCANFrameBuffers(); // call main initialization
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
bool tNMEA2000_esp32::CANOpen() {
|
|
||||||
if (IsOpen) return true;
|
|
||||||
|
|
||||||
if (CanInUse) return false; // currently prevent accidental second instance. Maybe possible in future.
|
|
||||||
|
|
||||||
pNMEA2000_esp32=this;
|
|
||||||
IsOpen=true;
|
|
||||||
CAN_init();
|
|
||||||
|
|
||||||
CanInUse=IsOpen;
|
|
||||||
|
|
||||||
return IsOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
bool tNMEA2000_esp32::CANGetFrame(unsigned long &id, unsigned char &len, unsigned char *buf) {
|
|
||||||
bool HasFrame=false;
|
|
||||||
tCANFrame frame;
|
|
||||||
CheckBusOff();
|
|
||||||
unsigned long now=millis();
|
|
||||||
//receive next CAN frame from queue
|
|
||||||
if ( xQueueReceive(RxQueue,&frame, 0)==pdTRUE ) {
|
|
||||||
HasFrame=true;
|
|
||||||
id=frame.id;
|
|
||||||
len=frame.len;
|
|
||||||
memcpy(buf,frame.buf,frame.len);
|
|
||||||
lastReceive=now;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if (lastReceive != 0 && (lastReceive + RECEIVE_REINIT_TIME) < now && (lastSend + RECEIVE_REINIT_TIME) < now){
|
|
||||||
ECDEBUG("Noting received within %d ms, reinit",RECEIVE_REINIT_TIME);
|
|
||||||
CAN_init(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return HasFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
void tNMEA2000_esp32::CAN_init(bool installIsr) {
|
|
||||||
|
|
||||||
//Time quantum
|
|
||||||
double __tq;
|
|
||||||
|
|
||||||
|
|
||||||
// A soft reset of the ESP32 leaves it's CAN controller in an undefined state so a reset is needed.
|
|
||||||
// Reset CAN controller to same state as it would be in after a power down reset.
|
|
||||||
periph_module_reset(PERIPH_CAN_MODULE);
|
|
||||||
|
|
||||||
|
|
||||||
//enable module
|
|
||||||
DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_CAN_CLK_EN);
|
|
||||||
DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_CAN_RST);
|
|
||||||
|
|
||||||
//configure RX pin
|
|
||||||
gpio_set_direction(RxPin,GPIO_MODE_INPUT);
|
|
||||||
gpio_matrix_in(RxPin,CAN_RX_IDX,0);
|
|
||||||
gpio_pad_select_gpio(RxPin);
|
|
||||||
|
|
||||||
//set to PELICAN mode
|
|
||||||
MODULE_CAN->CDR.B.CAN_M=0x1;
|
|
||||||
|
|
||||||
//synchronization jump width is the same for all baud rates
|
|
||||||
MODULE_CAN->BTR0.B.SJW =0x1;
|
|
||||||
|
|
||||||
//TSEG2 is the same for all baud rates
|
|
||||||
MODULE_CAN->BTR1.B.TSEG2 =0x1;
|
|
||||||
|
|
||||||
//select time quantum and set TSEG1
|
|
||||||
switch (speed) {
|
|
||||||
case CAN_SPEED_1000KBPS:
|
|
||||||
MODULE_CAN->BTR1.B.TSEG1 =0x4;
|
|
||||||
__tq = 0.125;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CAN_SPEED_800KBPS:
|
|
||||||
MODULE_CAN->BTR1.B.TSEG1 =0x6;
|
|
||||||
__tq = 0.125;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
MODULE_CAN->BTR1.B.TSEG1 =0xc;
|
|
||||||
__tq = ((float)1000/speed) / 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
//set baud rate prescaler
|
|
||||||
MODULE_CAN->BTR0.B.BRP=(uint8_t)round((((APB_CLK_FREQ * __tq) / 2) - 1)/1000000)-1;
|
|
||||||
|
|
||||||
/* Set sampling
|
|
||||||
* 1 -> triple; the bus is sampled three times; recommended for low/medium speed buses (class A and B) where filtering spikes on the bus line is beneficial
|
|
||||||
* 0 -> single; the bus is sampled once; recommended for high speed buses (SAE class C)*/
|
|
||||||
MODULE_CAN->BTR1.B.SAM =0x1;
|
|
||||||
|
|
||||||
//enable all interrupts
|
|
||||||
MODULE_CAN->IER.U = 0xef; // bit 0x10 contains Baud Rate Prescaler Divider (BRP_DIV) bit
|
|
||||||
|
|
||||||
//no acceptance filtering, as we want to fetch all messages
|
|
||||||
MODULE_CAN->MBX_CTRL.ACC.CODE[0] = 0;
|
|
||||||
MODULE_CAN->MBX_CTRL.ACC.CODE[1] = 0;
|
|
||||||
MODULE_CAN->MBX_CTRL.ACC.CODE[2] = 0;
|
|
||||||
MODULE_CAN->MBX_CTRL.ACC.CODE[3] = 0;
|
|
||||||
MODULE_CAN->MBX_CTRL.ACC.MASK[0] = 0xff;
|
|
||||||
MODULE_CAN->MBX_CTRL.ACC.MASK[1] = 0xff;
|
|
||||||
MODULE_CAN->MBX_CTRL.ACC.MASK[2] = 0xff;
|
|
||||||
MODULE_CAN->MBX_CTRL.ACC.MASK[3] = 0xff;
|
|
||||||
|
|
||||||
//set to normal mode
|
|
||||||
MODULE_CAN->OCR.B.OCMODE=__CAN_OC_NOM;
|
|
||||||
|
|
||||||
//clear error counters
|
|
||||||
MODULE_CAN->TXERR.U = 0;
|
|
||||||
MODULE_CAN->RXERR.U = 0;
|
|
||||||
(void)MODULE_CAN->ECC;
|
|
||||||
|
|
||||||
//clear interrupt flags
|
|
||||||
(void)MODULE_CAN->IR.U;
|
|
||||||
|
|
||||||
//install CAN ISR
|
|
||||||
if (installIsr) esp_intr_alloc(ETS_CAN_INTR_SOURCE,0,ESP32Can1Interrupt,NULL,NULL);
|
|
||||||
|
|
||||||
//configure TX pin
|
|
||||||
// We do late configure, since some initialization above caused CAN Tx flash
|
|
||||||
// shortly causing one error frame on startup. By setting CAN pin here
|
|
||||||
// it works right.
|
|
||||||
gpio_set_direction(TxPin,GPIO_MODE_OUTPUT);
|
|
||||||
gpio_matrix_out(TxPin,CAN_TX_IDX,0,0);
|
|
||||||
gpio_pad_select_gpio(TxPin);
|
|
||||||
|
|
||||||
//Showtime. Release Reset Mode.
|
|
||||||
MODULE_CAN->MOD.B.RM = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
void tNMEA2000_esp32::CAN_read_frame() {
|
|
||||||
tCANFrame frame;
|
|
||||||
CAN_FIR_t FIR;
|
|
||||||
|
|
||||||
//get FIR
|
|
||||||
FIR.U=MODULE_CAN->MBX_CTRL.FCTRL.FIR.U;
|
|
||||||
frame.len=FIR.B.DLC>8?8:FIR.B.DLC;
|
|
||||||
|
|
||||||
// Handle only extended frames
|
|
||||||
if (FIR.B.FF==CAN_frame_ext) { //extended frame
|
|
||||||
//Get Message ID
|
|
||||||
frame.id = _CAN_GET_EXT_ID;
|
|
||||||
|
|
||||||
//deep copy data bytes
|
|
||||||
for( size_t i=0; i<frame.len; i++ ) {
|
|
||||||
frame.buf[i]=MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
//send frame to input queue
|
|
||||||
xQueueSendToBackFromISR(RxQueue,&frame,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Let the hardware know the frame has been read.
|
|
||||||
MODULE_CAN->CMR.B.RRB=1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
void tNMEA2000_esp32::CAN_send_frame(tCANFrame &frame) {
|
|
||||||
CAN_FIR_t FIR;
|
|
||||||
|
|
||||||
FIR.U=0;
|
|
||||||
FIR.B.DLC=frame.len>8?8:frame.len;
|
|
||||||
FIR.B.FF=CAN_frame_ext;
|
|
||||||
|
|
||||||
//copy frame information record
|
|
||||||
MODULE_CAN->MBX_CTRL.FCTRL.FIR.U=FIR.U;
|
|
||||||
|
|
||||||
//Write message ID
|
|
||||||
_CAN_SET_EXT_ID(frame.id);
|
|
||||||
|
|
||||||
// Copy the frame data to the hardware
|
|
||||||
for ( size_t i=0; i<frame.len; i++) {
|
|
||||||
MODULE_CAN->MBX_CTRL.FCTRL.TX_RX.EXT.data[i]=frame.buf[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transmit frame
|
|
||||||
MODULE_CAN->CMR.B.TR=1;
|
|
||||||
}
|
|
||||||
#define CAN_MAX_TX_RETRY 12
|
|
||||||
#define RECOVERY_RETRY_MS 1000
|
|
||||||
void tNMEA2000_esp32::CAN_bus_off_recovery(){
|
|
||||||
unsigned long now=millis();
|
|
||||||
if (recoveryStarted && (recoveryStarted + RECOVERY_RETRY_MS) > now ) return;
|
|
||||||
ECDEBUG("CAN_bus_off_recovery started\n");
|
|
||||||
recoveryStarted=now;
|
|
||||||
errRecovery++;
|
|
||||||
MODULE_CAN->CMR.B.AT=1; // abort transmission
|
|
||||||
(void)MODULE_CAN->SR.U;
|
|
||||||
MODULE_CAN->TXERR.U = 127;
|
|
||||||
MODULE_CAN->RXERR.U = 0;
|
|
||||||
MODULE_CAN->MOD.B.RM = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tNMEA2000_esp32::CheckBusOff(){
|
|
||||||
//should we really recover here?
|
|
||||||
if (MODULE_CAN->SR.B.BS){
|
|
||||||
ECDEBUG("Bus off detected, trying recovery\n");
|
|
||||||
CAN_bus_off_recovery();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
void tNMEA2000_esp32::InterruptHandler() {
|
|
||||||
//Interrupt flag buffer
|
|
||||||
cntIntr++;
|
|
||||||
uint32_t interrupt;
|
|
||||||
|
|
||||||
// Read interrupt status and clear flags
|
|
||||||
interrupt = (MODULE_CAN->IR.U & 0xff);
|
|
||||||
|
|
||||||
// Handle TX complete interrupt
|
|
||||||
//see http://uglyduck.vajn.icu/PDF/wireless/Espressif/ESP32/Eco_and_Workarounds_for_Bugs_in_ESP32.pdf, 3.13.4
|
|
||||||
if ((interrupt & __CAN_IRQ_TX) != 0 || MODULE_CAN->SR.B.TBS) {
|
|
||||||
tCANFrame frame;
|
|
||||||
if ( (xQueueReceiveFromISR(TxQueue,&frame,NULL)==pdTRUE) ) {
|
|
||||||
CAN_send_frame(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle RX frame available interrupt
|
|
||||||
if ((interrupt & __CAN_IRQ_RX) != 0) {
|
|
||||||
CAN_read_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle error interrupts.
|
|
||||||
if ((interrupt & (__CAN_IRQ_ERR //0x4
|
|
||||||
|
|
||||||
| __CAN_IRQ_WAKEUP //0x10
|
|
||||||
| __CAN_IRQ_ERR_PASSIVE //0x20
|
|
||||||
|
|
||||||
|
|
||||||
)) != 0) {
|
|
||||||
/*handler*/
|
|
||||||
|
|
||||||
}
|
|
||||||
//https://www.esp32.com/viewtopic.php?t=5010
|
|
||||||
// Handle error interrupts.
|
|
||||||
if (interrupt & __CAN_IRQ_DATA_OVERRUN ) { //0x08
|
|
||||||
errOverrun++;
|
|
||||||
MODULE_CAN->CMR.B.CDO=1;
|
|
||||||
(void)MODULE_CAN->SR.U; // read SR after write to CMR to settle register changes
|
|
||||||
}
|
|
||||||
if (interrupt & __CAN_IRQ_ARB_LOST ) { //0x40
|
|
||||||
errArb++;
|
|
||||||
(void)MODULE_CAN->ALC.U; // must be read to re-enable interrupt
|
|
||||||
errCountTxInternal++;
|
|
||||||
}
|
|
||||||
if (interrupt & __CAN_IRQ_BUS_ERR ) { //0x80
|
|
||||||
errBus++;
|
|
||||||
(void)MODULE_CAN->ECC.U; // must be read to re-enable interrupt
|
|
||||||
errCountTxInternal+=2;
|
|
||||||
}
|
|
||||||
if (MODULE_CAN->TXERR.U == 0){
|
|
||||||
recoveryStarted=0;
|
|
||||||
}
|
|
||||||
if (errCountTxInternal >= 2 *CAN_MAX_TX_RETRY){
|
|
||||||
MODULE_CAN->CMR.B.AT=1; // abort transmission
|
|
||||||
(void)MODULE_CAN->SR.U;
|
|
||||||
errCountTxInternal=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//*****************************************************************************
|
|
||||||
void ESP32Can1Interrupt(void *) {
|
|
||||||
pNMEA2000_esp32->InterruptHandler();
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
/*
|
|
||||||
NMEA2000_esp32.h
|
|
||||||
|
|
||||||
Copyright (c) 2015-2020 Timo Lappalainen, Kave Oy, www.kave.fi
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to use,
|
|
||||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
||||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
||||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
|
||||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
Inherited NMEA2000 object for ESP32 modules. See also NMEA2000 library.
|
|
||||||
|
|
||||||
Thanks to Thomas Barth, barth-dev.de, who has written ESP32 CAN code. To avoid extra
|
|
||||||
libraries, I implemented his code directly to the NMEA2000_esp32 to avoid extra
|
|
||||||
can.h library, which may cause even naming problem.
|
|
||||||
|
|
||||||
The library sets as default CAN Tx pin to GPIO 16 and CAN Rx pint to GPIO 4. If you
|
|
||||||
want to use other pins (I have not tested can any pins be used), add defines e.g.
|
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_34
|
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_35
|
|
||||||
before including NMEA2000_esp32.h or NMEA2000_CAN.h
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _NMEA2000_ESP32_H_
|
|
||||||
#define _NMEA2000_ESP32_H_
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/queue.h"
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "NMEA2000.h"
|
|
||||||
#include "N2kMsg.h"
|
|
||||||
#include "ESP32_CAN_def.h"
|
|
||||||
|
|
||||||
#ifndef ESP32_CAN_TX_PIN
|
|
||||||
#define ESP32_CAN_TX_PIN GPIO_NUM_16
|
|
||||||
#endif
|
|
||||||
#ifndef ESP32_CAN_RX_PIN
|
|
||||||
#define ESP32_CAN_RX_PIN GPIO_NUM_4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class tNMEA2000_esp32 : public tNMEA2000
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
bool IsOpen;
|
|
||||||
static bool CanInUse;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
struct tCANFrame {
|
|
||||||
uint32_t id; // can identifier
|
|
||||||
uint8_t len; // length of data
|
|
||||||
uint8_t buf[8];
|
|
||||||
};
|
|
||||||
|
|
||||||
protected:
|
|
||||||
CAN_speed_t speed;
|
|
||||||
gpio_num_t TxPin;
|
|
||||||
gpio_num_t RxPin;
|
|
||||||
QueueHandle_t RxQueue;
|
|
||||||
QueueHandle_t TxQueue;
|
|
||||||
Print *debugStream;
|
|
||||||
int errOverrun=0;
|
|
||||||
int errArb=0;
|
|
||||||
int errBus=0;
|
|
||||||
int errRecovery=0;
|
|
||||||
int errCountTxInternal=0;
|
|
||||||
int errCancelTransmit=0;
|
|
||||||
int errReinit=0;
|
|
||||||
unsigned long recoveryStarted=0;
|
|
||||||
unsigned long lastSend=0;
|
|
||||||
unsigned long lastReceive=0;
|
|
||||||
int cntIntr=0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void CAN_read_frame(); // Read frame to queue within interrupt
|
|
||||||
void CAN_send_frame(tCANFrame &frame); // Send frame
|
|
||||||
void CAN_init(bool installIsr=true);
|
|
||||||
void CAN_bus_off_recovery(); //recover from bus off
|
|
||||||
void CheckBusOff();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool CANSendFrame(unsigned long id, unsigned char len, const unsigned char *buf, bool wait_sent=true);
|
|
||||||
bool CANOpen();
|
|
||||||
bool CANGetFrame(unsigned long &id, unsigned char &len, unsigned char *buf);
|
|
||||||
virtual void InitCANFrameBuffers();
|
|
||||||
|
|
||||||
public:
|
|
||||||
tNMEA2000_esp32(gpio_num_t _TxPin=ESP32_CAN_TX_PIN,
|
|
||||||
gpio_num_t _RxPin=ESP32_CAN_RX_PIN,
|
|
||||||
Print *debugStream=NULL);
|
|
||||||
|
|
||||||
void InterruptHandler();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,3 +0,0 @@
|
||||||
forked from https://github.com/ttlappalainen/NMEA2000_esp32
|
|
||||||
with some error handling additions
|
|
||||||
based on https://www.esp32.com/viewtopic.php?t=5010
|
|
|
@ -738,7 +738,7 @@ private:
|
||||||
char _Destination[21];
|
char _Destination[21];
|
||||||
tN2kAISVersion _AISversion;
|
tN2kAISVersion _AISversion;
|
||||||
tN2kGNSStype _GNSStype;
|
tN2kGNSStype _GNSStype;
|
||||||
tN2kAISTranceiverInfo _AISinfo;
|
tN2kAISTransceiverInformation _AISinfo;
|
||||||
tN2kAISDTE _DTE;
|
tN2kAISDTE _DTE;
|
||||||
|
|
||||||
tNMEA0183AISMsg NMEA0183AISMsg;
|
tNMEA0183AISMsg NMEA0183AISMsg;
|
||||||
|
@ -848,15 +848,16 @@ private:
|
||||||
tN2kAISUnit _Unit;
|
tN2kAISUnit _Unit;
|
||||||
bool _Display, _DSC, _Band, _Msg22, _State;
|
bool _Display, _DSC, _Band, _Msg22, _State;
|
||||||
tN2kAISMode _Mode;
|
tN2kAISMode _Mode;
|
||||||
|
tN2kAISTransceiverInformation _AISTranceiverInformation;
|
||||||
|
|
||||||
if (ParseN2kPGN129039(N2kMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
|
if (ParseN2kPGN129039(N2kMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
|
||||||
_Seconds, _COG, _SOG, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State))
|
_Seconds, _COG, _SOG, _AISTranceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State))
|
||||||
{
|
{
|
||||||
|
|
||||||
tNMEA0183AISMsg NMEA0183AISMsg;
|
tNMEA0183AISMsg NMEA0183AISMsg;
|
||||||
|
|
||||||
if (SetAISClassBMessage18(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
|
if (SetAISClassBMessage18(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
|
||||||
_Seconds, _COG, _SOG, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State))
|
_Seconds, _COG, _SOG, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State))
|
||||||
{
|
{
|
||||||
|
|
||||||
SendMessage(NMEA0183AISMsg);
|
SendMessage(NMEA0183AISMsg);
|
||||||
|
|
|
@ -347,26 +347,29 @@ bool AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam,
|
||||||
uint16_t _PosRefStbd = 0;
|
uint16_t _PosRefStbd = 0;
|
||||||
uint16_t _PosRefPort = 0;
|
uint16_t _PosRefPort = 0;
|
||||||
|
|
||||||
if ( PosRefBow >= 0.0 && PosRefBow <= 511.0 ) {
|
if (PosRefBow < 0) PosRefBow=0; //could be N2kIsNA
|
||||||
_PosRefBow = ceil(PosRefBow);
|
if ( PosRefBow <= 511.0 ) {
|
||||||
|
_PosRefBow = round(PosRefBow);
|
||||||
} else {
|
} else {
|
||||||
_PosRefBow = 511;
|
_PosRefBow = 511;
|
||||||
}
|
}
|
||||||
|
if (PosRefStbd < 0 ) PosRefStbd=0; //could be N2kIsNA
|
||||||
if ( PosRefStbd >= 0.0 && PosRefStbd <= 63.0 ) {
|
if (PosRefStbd <= 63.0 ) {
|
||||||
_PosRefStbd = ceil(PosRefStbd);
|
_PosRefStbd = round(PosRefStbd);
|
||||||
} else {
|
} else {
|
||||||
_PosRefStbd = 63;
|
_PosRefStbd = 63;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !N2kIsNA(Length) ) {
|
if ( !N2kIsNA(Length) ) {
|
||||||
_PosRefStern = ceil( Length ) - _PosRefBow;
|
if (Length >= PosRefBow){
|
||||||
if ( _PosRefStern < 0 ) _PosRefStern = 0;
|
_PosRefStern=round(Length - PosRefBow);
|
||||||
|
}
|
||||||
if ( _PosRefStern > 511 ) _PosRefStern = 511;
|
if ( _PosRefStern > 511 ) _PosRefStern = 511;
|
||||||
}
|
}
|
||||||
if ( !N2kIsNA(Beam) ) {
|
if ( !N2kIsNA(Beam) ) {
|
||||||
_PosRefPort = ceil( Beam ) - _PosRefStbd;
|
if (Beam >= PosRefStbd){
|
||||||
if ( _PosRefPort < 0 ) _PosRefPort = 0;
|
_PosRefPort = round( Beam - PosRefStbd);
|
||||||
|
}
|
||||||
if ( _PosRefPort > 63 ) _PosRefPort = 63;
|
if ( _PosRefPort > 63 ) _PosRefPort = 63;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
#include "Nmea2kTwai.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/twai.h"
|
||||||
|
|
||||||
|
#define LOGID(id) ((id >> 8) & 0x1ffff)
|
||||||
|
|
||||||
|
static const int TIMEOUT_OFFLINE=256; //# of timeouts to consider offline
|
||||||
|
|
||||||
|
Nmea2kTwai::Nmea2kTwai(gpio_num_t _TxPin, gpio_num_t _RxPin, unsigned long recP, unsigned long logP):
|
||||||
|
tNMEA2000(),RxPin(_RxPin),TxPin(_TxPin)
|
||||||
|
{
|
||||||
|
if (RxPin < 0 || TxPin < 0){
|
||||||
|
disabled=true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
timers.addAction(logP,[this](){logStatus();});
|
||||||
|
timers.addAction(recP,[this](){checkRecovery();});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Nmea2kTwai::CANSendFrame(unsigned long id, unsigned char len, const unsigned char *buf, bool wait_sent)
|
||||||
|
{
|
||||||
|
if (disabled) return true;
|
||||||
|
twai_message_t message;
|
||||||
|
memset(&message,0,sizeof(message));
|
||||||
|
message.identifier = id;
|
||||||
|
message.extd = 1;
|
||||||
|
message.data_length_code = len;
|
||||||
|
memcpy(message.data,buf,len);
|
||||||
|
esp_err_t rt=twai_transmit(&message,0);
|
||||||
|
if (rt != ESP_OK){
|
||||||
|
if (rt == ESP_ERR_TIMEOUT){
|
||||||
|
if (txTimeouts < TIMEOUT_OFFLINE) txTimeouts++;
|
||||||
|
}
|
||||||
|
logDebug(LOG_MSG,"twai transmit for %ld failed: %x",LOGID(id),(int)rt);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
txTimeouts=0;
|
||||||
|
logDebug(LOG_MSG,"twai transmit id %ld, len %d",LOGID(id),(int)len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool Nmea2kTwai::CANOpen()
|
||||||
|
{
|
||||||
|
if (disabled){
|
||||||
|
logDebug(LOG_INFO,"CAN disabled");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
esp_err_t rt=twai_start();
|
||||||
|
if (rt != ESP_OK){
|
||||||
|
logDebug(LOG_ERR,"CANOpen failed: %x",(int)rt);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
logDebug(LOG_INFO,"CANOpen ok");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool Nmea2kTwai::CANGetFrame(unsigned long &id, unsigned char &len, unsigned char *buf)
|
||||||
|
{
|
||||||
|
if (disabled) return false;
|
||||||
|
twai_message_t message;
|
||||||
|
esp_err_t rt=twai_receive(&message,0);
|
||||||
|
if (rt != ESP_OK){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (! message.extd){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
id=message.identifier;
|
||||||
|
len=message.data_length_code;
|
||||||
|
if (len > 8){
|
||||||
|
logDebug(LOG_DEBUG,"twai: received invalid message %lld, len %d",LOGID(id),len);
|
||||||
|
len=8;
|
||||||
|
}
|
||||||
|
logDebug(LOG_MSG,"twai rcv id=%ld,len=%d, ext=%d",LOGID(message.identifier),message.data_length_code,message.extd);
|
||||||
|
if (! message.rtr){
|
||||||
|
memcpy(buf,message.data,message.data_length_code);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void Nmea2kTwai::initDriver(){
|
||||||
|
if (disabled) return;
|
||||||
|
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(TxPin,RxPin, TWAI_MODE_NORMAL);
|
||||||
|
g_config.tx_queue_len=20;
|
||||||
|
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS();
|
||||||
|
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
|
||||||
|
esp_err_t rt=twai_driver_install(&g_config, &t_config, &f_config);
|
||||||
|
if (rt == ESP_OK) {
|
||||||
|
logDebug(LOG_INFO,"twai driver initialzed, rx=%d,tx=%d",(int)RxPin,(int)TxPin);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
logDebug(LOG_ERR,"twai driver init failed: %x",(int)rt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This will be called on Open() before any other initialization. Inherit this, if buffers can be set for the driver
|
||||||
|
// and you want to change size of library send frame buffer size. See e.g. NMEA2000_teensy.cpp.
|
||||||
|
void Nmea2kTwai::InitCANFrameBuffers()
|
||||||
|
{
|
||||||
|
if (disabled){
|
||||||
|
logDebug(LOG_INFO,"twai init - disabled");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
initDriver();
|
||||||
|
}
|
||||||
|
tNMEA2000::InitCANFrameBuffers();
|
||||||
|
|
||||||
|
}
|
||||||
|
Nmea2kTwai::Status Nmea2kTwai::getStatus(){
|
||||||
|
twai_status_info_t state;
|
||||||
|
Status rt;
|
||||||
|
if (disabled){
|
||||||
|
rt.state=ST_DISABLED;
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
if (twai_get_status_info(&state) != ESP_OK){
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
switch(state.state){
|
||||||
|
case TWAI_STATE_STOPPED:
|
||||||
|
rt.state=ST_STOPPED;
|
||||||
|
break;
|
||||||
|
case TWAI_STATE_RUNNING:
|
||||||
|
rt.state=ST_RUNNING;
|
||||||
|
break;
|
||||||
|
case TWAI_STATE_BUS_OFF:
|
||||||
|
rt.state=ST_BUS_OFF;
|
||||||
|
break;
|
||||||
|
case TWAI_STATE_RECOVERING:
|
||||||
|
rt.state=ST_RECOVERING;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rt.rx_errors=state.rx_error_counter;
|
||||||
|
rt.tx_errors=state.tx_error_counter;
|
||||||
|
rt.tx_failed=state.tx_failed_count;
|
||||||
|
rt.rx_missed=state.rx_missed_count;
|
||||||
|
rt.rx_overrun=state.rx_overrun_count;
|
||||||
|
rt.tx_timeouts=txTimeouts;
|
||||||
|
if (rt.tx_timeouts >= TIMEOUT_OFFLINE && rt.state == ST_RUNNING){
|
||||||
|
rt.state=ST_OFFLINE;
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
bool Nmea2kTwai::checkRecovery(){
|
||||||
|
if (disabled) return false;
|
||||||
|
Status canState=getStatus();
|
||||||
|
bool strt=false;
|
||||||
|
if (canState.state != Nmea2kTwai::ST_RUNNING)
|
||||||
|
{
|
||||||
|
if (canState.state == Nmea2kTwai::ST_BUS_OFF)
|
||||||
|
{
|
||||||
|
strt = true;
|
||||||
|
bool rt = startRecovery();
|
||||||
|
logDebug(LOG_INFO, "twai BUS_OFF: start can recovery - result %d", (int)rt);
|
||||||
|
}
|
||||||
|
if (canState.state == Nmea2kTwai::ST_STOPPED)
|
||||||
|
{
|
||||||
|
bool rt = CANOpen();
|
||||||
|
logDebug(LOG_INFO, "twai STOPPED: restart can driver - result %d", (int)rt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nmea2kTwai::loop(){
|
||||||
|
if (disabled) return;
|
||||||
|
timers.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Nmea2kTwai::Status Nmea2kTwai::logStatus(){
|
||||||
|
Status canState=getStatus();
|
||||||
|
logDebug(LOG_INFO, "twai state %s, rxerr %d, txerr %d, txfail %d, txtimeout %d, rxmiss %d, rxoverrun %d",
|
||||||
|
stateStr(canState.state),
|
||||||
|
canState.rx_errors,
|
||||||
|
canState.tx_errors,
|
||||||
|
canState.tx_failed,
|
||||||
|
canState.tx_timeouts,
|
||||||
|
canState.rx_missed,
|
||||||
|
canState.rx_overrun);
|
||||||
|
return canState;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Nmea2kTwai::startRecovery(){
|
||||||
|
if (disabled) return false;
|
||||||
|
lastRecoveryStart=millis();
|
||||||
|
esp_err_t rt=twai_driver_uninstall();
|
||||||
|
if (rt != ESP_OK){
|
||||||
|
logDebug(LOG_ERR,"twai: deinit for recovery failed with %x",(int)rt);
|
||||||
|
}
|
||||||
|
initDriver();
|
||||||
|
bool frt=CANOpen();
|
||||||
|
return frt;
|
||||||
|
}
|
||||||
|
const char * Nmea2kTwai::stateStr(const Nmea2kTwai::STATE &st){
|
||||||
|
switch (st)
|
||||||
|
{
|
||||||
|
case ST_BUS_OFF: return "BUS_OFF";
|
||||||
|
case ST_RECOVERING: return "RECOVERING";
|
||||||
|
case ST_RUNNING: return "RUNNING";
|
||||||
|
case ST_STOPPED: return "STOPPED";
|
||||||
|
case ST_OFFLINE: return "OFFLINE";
|
||||||
|
case ST_DISABLED: return "DISABLED";
|
||||||
|
}
|
||||||
|
return "ERROR";
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
#ifndef _NMEA2KTWAI_H
|
||||||
|
#define _NMEA2KTWAI_H
|
||||||
|
#include "NMEA2000.h"
|
||||||
|
#include "GwTimer.h"
|
||||||
|
|
||||||
|
class Nmea2kTwai : public tNMEA2000{
|
||||||
|
public:
|
||||||
|
Nmea2kTwai(gpio_num_t _TxPin, gpio_num_t _RxPin, unsigned long recP=0, unsigned long logPeriod=0);
|
||||||
|
typedef enum{
|
||||||
|
ST_STOPPED,
|
||||||
|
ST_RUNNING,
|
||||||
|
ST_BUS_OFF,
|
||||||
|
ST_RECOVERING,
|
||||||
|
ST_OFFLINE,
|
||||||
|
ST_DISABLED,
|
||||||
|
ST_ERROR
|
||||||
|
} STATE;
|
||||||
|
typedef struct{
|
||||||
|
//see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/twai.html#_CPPv418twai_status_info_t
|
||||||
|
uint32_t rx_errors=0;
|
||||||
|
uint32_t tx_errors=0;
|
||||||
|
uint32_t tx_failed=0;
|
||||||
|
uint32_t rx_missed=0;
|
||||||
|
uint32_t rx_overrun=0;
|
||||||
|
uint32_t tx_timeouts=0;
|
||||||
|
STATE state=ST_ERROR;
|
||||||
|
} Status;
|
||||||
|
Status getStatus();
|
||||||
|
unsigned long getLastRecoveryStart(){return lastRecoveryStart;}
|
||||||
|
void loop();
|
||||||
|
static const char * stateStr(const STATE &st);
|
||||||
|
virtual bool CANOpen();
|
||||||
|
virtual ~Nmea2kTwai(){};
|
||||||
|
static const int LOG_ERR=0;
|
||||||
|
static const int LOG_INFO=1;
|
||||||
|
static const int LOG_DEBUG=2;
|
||||||
|
static const int LOG_MSG=3;
|
||||||
|
protected:
|
||||||
|
// Virtual functions for different interfaces. Currently there are own classes
|
||||||
|
// for Arduino due internal CAN (NMEA2000_due), external MCP2515 SPI CAN bus controller (NMEA2000_mcp),
|
||||||
|
// Teensy FlexCAN (NMEA2000_Teensy), NMEA2000_avr for AVR, NMEA2000_mbed for MBED and NMEA2000_socketCAN for e.g. RPi.
|
||||||
|
virtual bool CANSendFrame(unsigned long id, unsigned char len, const unsigned char *buf, bool wait_sent=true);
|
||||||
|
virtual bool CANGetFrame(unsigned long &id, unsigned char &len, unsigned char *buf);
|
||||||
|
// This will be called on Open() before any other initialization. Inherit this, if buffers can be set for the driver
|
||||||
|
// and you want to change size of library send frame buffer size. See e.g. NMEA2000_teensy.cpp.
|
||||||
|
virtual void InitCANFrameBuffers();
|
||||||
|
virtual void logDebug(int level,const char *fmt,...){}
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initDriver();
|
||||||
|
bool startRecovery();
|
||||||
|
bool checkRecovery();
|
||||||
|
Status logStatus();
|
||||||
|
gpio_num_t TxPin;
|
||||||
|
gpio_num_t RxPin;
|
||||||
|
uint32_t txTimeouts=0;
|
||||||
|
GwIntervalRunner timers;
|
||||||
|
bool disabled=false;
|
||||||
|
unsigned long lastRecoveryStart=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -40,12 +40,11 @@ class GwSerialStream: public Stream{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
GwSerial::GwSerial(GwLog *logger, int num, int id,bool allowRead)
|
GwSerial::GwSerial(GwLog *logger, Stream *s, int id,bool allowRead):serial(s)
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::DEBUG,"creating GwSerial %p port %d for %d",this,(int)num,id);
|
LOG_DEBUG(GwLog::DEBUG,"creating GwSerial %p id %d",this,id);
|
||||||
this->id=id;
|
this->id=id;
|
||||||
this->logger = logger;
|
this->logger = logger;
|
||||||
this->num = num;
|
|
||||||
String bufName="Ser(";
|
String bufName="Ser(";
|
||||||
bufName+=String(id);
|
bufName+=String(id);
|
||||||
bufName+=")";
|
bufName+=")";
|
||||||
|
@ -54,21 +53,15 @@ GwSerial::GwSerial(GwLog *logger, int num, int id,bool allowRead)
|
||||||
if (allowRead){
|
if (allowRead){
|
||||||
this->readBuffer=new GwBuffer(logger, GwBuffer::RX_BUFFER_SIZE,bufName+"rd");
|
this->readBuffer=new GwBuffer(logger, GwBuffer::RX_BUFFER_SIZE,bufName+"rd");
|
||||||
}
|
}
|
||||||
this->serial=new HardwareSerial(num);
|
buffer->reset("init");
|
||||||
|
initialized=true;
|
||||||
}
|
}
|
||||||
GwSerial::~GwSerial()
|
GwSerial::~GwSerial()
|
||||||
{
|
{
|
||||||
delete buffer;
|
delete buffer;
|
||||||
if (readBuffer) delete readBuffer;
|
if (readBuffer) delete readBuffer;
|
||||||
delete serial;
|
|
||||||
}
|
|
||||||
int GwSerial::setup(int baud, int rxpin, int txpin)
|
|
||||||
{
|
|
||||||
serial->begin(baud,SERIAL_8N1,rxpin,txpin);
|
|
||||||
buffer->reset(F("init"));
|
|
||||||
initialized = true;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GwSerial::isInitialized() { return initialized; }
|
bool GwSerial::isInitialized() { return initialized; }
|
||||||
size_t GwSerial::enqueue(const uint8_t *data, size_t len, bool partial)
|
size_t GwSerial::enqueue(const uint8_t *data, size_t len, bool partial)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,19 +10,17 @@ class GwSerial : public GwChannelInterface{
|
||||||
GwBuffer *buffer;
|
GwBuffer *buffer;
|
||||||
GwBuffer *readBuffer=NULL;
|
GwBuffer *readBuffer=NULL;
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
int num;
|
|
||||||
bool initialized=false;
|
bool initialized=false;
|
||||||
bool allowRead=true;
|
bool allowRead=true;
|
||||||
GwBuffer::WriteStatus write();
|
GwBuffer::WriteStatus write();
|
||||||
int id=-1;
|
int id=-1;
|
||||||
int overflows=0;
|
int overflows=0;
|
||||||
size_t enqueue(const uint8_t *data, size_t len,bool partial=false);
|
size_t enqueue(const uint8_t *data, size_t len,bool partial=false);
|
||||||
HardwareSerial *serial;
|
Stream *serial;
|
||||||
public:
|
public:
|
||||||
static const int bufferSize=200;
|
static const int bufferSize=200;
|
||||||
GwSerial(GwLog *logger,int num,int id,bool allowRead=true);
|
GwSerial(GwLog *logger,Stream *stream,int id,bool allowRead=true);
|
||||||
~GwSerial();
|
~GwSerial();
|
||||||
int setup(int baud,int rxpin,int txpin);
|
|
||||||
bool isInitialized();
|
bool isInitialized();
|
||||||
virtual size_t sendToClients(const char *buf,int sourceId,bool partial=false);
|
virtual size_t sendToClients(const char *buf,int sourceId,bool partial=false);
|
||||||
virtual void loop(bool handleRead=true,bool handleWrite=true);
|
virtual void loop(bool handleRead=true,bool handleWrite=true);
|
||||||
|
|
|
@ -59,7 +59,7 @@ int GwSocketServer::available()
|
||||||
int client_sock;
|
int client_sock;
|
||||||
struct sockaddr_in _client;
|
struct sockaddr_in _client;
|
||||||
int cs = sizeof(struct sockaddr_in);
|
int cs = sizeof(struct sockaddr_in);
|
||||||
client_sock = lwip_accept_r(listener, (struct sockaddr *)&_client, (socklen_t *)&cs);
|
client_sock = accept(listener, (struct sockaddr *)&_client, (socklen_t *)&cs);
|
||||||
if (client_sock >= 0)
|
if (client_sock >= 0)
|
||||||
{
|
{
|
||||||
int val = 1;
|
int val = 1;
|
||||||
|
|
|
@ -80,7 +80,7 @@ void GwTcpClient::startConnection()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK );
|
fcntl( sockfd, F_SETFL, fcntl( sockfd, F_GETFL, 0 ) | O_NONBLOCK );
|
||||||
int res = lwip_connect_r(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
|
int res = connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
|
||||||
if (res < 0 ) {
|
if (res < 0 ) {
|
||||||
if (errno != EINPROGRESS){
|
if (errno != EINPROGRESS){
|
||||||
error=String("connect error ")+String(strerror(errno));
|
error=String("connect error ")+String(strerror(errno));
|
||||||
|
@ -258,7 +258,7 @@ void GwTcpClient::resolveHost(String host)
|
||||||
if (xTaskCreate([](void *p)
|
if (xTaskCreate([](void *p)
|
||||||
{
|
{
|
||||||
ResolveArgs *args = (ResolveArgs *)p;
|
ResolveArgs *args = (ResolveArgs *)p;
|
||||||
struct ip4_addr addr;
|
esp_ip4_addr_t addr;
|
||||||
addr.addr = 0;
|
addr.addr = 0;
|
||||||
esp_err_t err = mdns_query_a(args->host.c_str(), args->timeout, &addr);
|
esp_err_t err = mdns_query_a(args->host.c_str(), args->timeout, &addr);
|
||||||
if (err)
|
if (err)
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
#ifndef _GWTIMER_H
|
||||||
|
#define _GWTIMER_H
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <Arduino.h>
|
||||||
|
class GwIntervalRunner{
|
||||||
|
public:
|
||||||
|
using RunFunction=std::function<void(void)>;
|
||||||
|
private:
|
||||||
|
class Run{
|
||||||
|
public:
|
||||||
|
unsigned long interval=0;
|
||||||
|
RunFunction runner;
|
||||||
|
unsigned long last=0;
|
||||||
|
Run(RunFunction r,unsigned long iv,unsigned long l=0):
|
||||||
|
runner(r),interval(iv),last(l){}
|
||||||
|
bool shouldRun(unsigned long now){
|
||||||
|
if ((last+interval) > now) return false;
|
||||||
|
last=now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool runIf(unsigned long now){
|
||||||
|
if (shouldRun(now)) {
|
||||||
|
runner();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::vector<Run> runners;
|
||||||
|
unsigned long startTime=0;
|
||||||
|
public:
|
||||||
|
GwIntervalRunner(unsigned long now=millis()){
|
||||||
|
startTime=now;
|
||||||
|
}
|
||||||
|
void addAction(unsigned long interval,RunFunction run,unsigned long start=0){
|
||||||
|
if (start=0) start=startTime;
|
||||||
|
runners.push_back(Run(run,interval,start));
|
||||||
|
}
|
||||||
|
bool loop(unsigned long now=millis()){
|
||||||
|
bool rt=false;
|
||||||
|
for (auto it=runners.begin();it!=runners.end();it++){
|
||||||
|
if (it->runIf(now)) rt=true;
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -1,8 +1,18 @@
|
||||||
|
#define DECLARE_USERTASK(task) GwUserTaskDef __##task##__(task,#task);
|
||||||
|
#define DECLARE_USERTASK_PARAM(task,...) GwUserTaskDef __##task##__(task,#task,__VA_ARGS__);
|
||||||
|
#define DECLARE_INITFUNCTION(task) GwInitTask __Init##task##__(task,#task);
|
||||||
|
#define DECLARE_CAPABILITY(name,value) GwUserCapability __CAP##name##__(#name,#value);
|
||||||
|
#define DECLARE_STRING_CAPABILITY(name,value) GwUserCapability __CAP##name##__(#name,value);
|
||||||
|
#define DECLARE_TASKIF(type) \
|
||||||
|
DECLARE_TASKIF_IMPL(type) \
|
||||||
|
GwIreg __register##type(__FILE__,#type)
|
||||||
|
|
||||||
#include "GwUserCode.h"
|
#include "GwUserCode.h"
|
||||||
#include "GwSynchronized.h"
|
#include "GwSynchronized.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include "GwCounter.h"
|
||||||
//user task handling
|
//user task handling
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +21,50 @@ std::vector<GwUserTask> userTasks;
|
||||||
std::vector<GwUserTask> initTasks;
|
std::vector<GwUserTask> initTasks;
|
||||||
GwUserCode::Capabilities userCapabilities;
|
GwUserCode::Capabilities userCapabilities;
|
||||||
|
|
||||||
|
template <typename V>
|
||||||
|
bool taskExists(V &list, const String &name){
|
||||||
|
for (auto it=list.begin();it!=list.end();it++){
|
||||||
|
if (it->name == name) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
class RegEntry{
|
||||||
|
public:
|
||||||
|
String file;
|
||||||
|
String task;
|
||||||
|
RegEntry(const String &t, const String &f):file(f),task(t){}
|
||||||
|
RegEntry(){}
|
||||||
|
};
|
||||||
|
using RegMap=std::map<String,RegEntry>;
|
||||||
|
static RegMap ®istrations(){
|
||||||
|
static RegMap *regMap=new RegMap();
|
||||||
|
return *regMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerInterface(const String &task,const String &file, const String &name){
|
||||||
|
auto it=registrations().find(name);
|
||||||
|
if (it != registrations().end()){
|
||||||
|
if (it->second.file != file){
|
||||||
|
ESP_LOGE("Assert","type %s redefined in %s original in %s",name,file,it->second.file);
|
||||||
|
std::abort();
|
||||||
|
};
|
||||||
|
if (it->second.task != task){
|
||||||
|
ESP_LOGE("Assert","type %s registered for multiple tasks %s and %s",name,task,it->second.task);
|
||||||
|
std::abort();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
registrations()[name]=RegEntry(task,file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GwIreg{
|
||||||
|
public:
|
||||||
|
GwIreg(const String &file, const String &name){
|
||||||
|
registerInterface("",file,name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GwUserTaskDef{
|
class GwUserTaskDef{
|
||||||
|
@ -38,25 +92,125 @@ class GwUserCapability{
|
||||||
userCapabilities[name]=value;
|
userCapabilities[name]=value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#define DECLARE_USERTASK(task) GwUserTaskDef __##task##__(task,#task);
|
#define _NOGWHARDWAREUT
|
||||||
#define DECLARE_USERTASK_PARAM(task,...) GwUserTaskDef __##task##__(task,#task,__VA_ARGS__);
|
|
||||||
#define DECLARE_INITFUNCTION(task) GwInitTask __Init##task##__(task,#task);
|
|
||||||
#define DECLARE_CAPABILITY(name,value) GwUserCapability __CAP##name##__(#name,#value);
|
|
||||||
#define DECLARE_STRING_CAPABILITY(name,value) GwUserCapability __CAP##name##__(#name,value);
|
|
||||||
#include "GwApi.h"
|
|
||||||
#include "GwUserTasks.h"
|
#include "GwUserTasks.h"
|
||||||
class TaskApi : public GwApi
|
#undef _NOGWHARDWAREUT
|
||||||
|
|
||||||
|
class TaskDataEntry{
|
||||||
|
public:
|
||||||
|
GwApi::TaskInterfaces::Ptr ptr;
|
||||||
|
int updates=0;
|
||||||
|
TaskDataEntry(GwApi::TaskInterfaces::Ptr p):ptr(p){}
|
||||||
|
TaskDataEntry(){}
|
||||||
|
};
|
||||||
|
class TaskInterfacesStorage{
|
||||||
|
GwLog *logger;
|
||||||
|
SemaphoreHandle_t lock;
|
||||||
|
std::map<String,TaskDataEntry> values;
|
||||||
|
public:
|
||||||
|
TaskInterfacesStorage(GwLog* l):
|
||||||
|
logger(l){
|
||||||
|
lock=xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
|
bool set(const String &file, const String &name, const String &task,GwApi::TaskInterfaces::Ptr v){
|
||||||
|
GWSYNCHRONIZED(&lock);
|
||||||
|
auto it=registrations().find(name);
|
||||||
|
if (it == registrations().end()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"TaskInterfaces: invalid set %s not known",name.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (it->second.file != file){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"TaskInterfaces: invalid set %s wrong file, expected %s , got %s",name.c_str(),it->second.file.c_str(),file.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (it->second.task != task){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"TaskInterfaces: invalid set %s wrong task, expected %s , got %s",name.c_str(),it->second.task.c_str(),task.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto vit=values.find(name);
|
||||||
|
if (vit != values.end()){
|
||||||
|
vit->second.updates++;
|
||||||
|
if (vit->second.updates < 0){
|
||||||
|
vit->second.updates=0;
|
||||||
|
}
|
||||||
|
vit->second.ptr=v;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
values[name]=TaskDataEntry(v);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
GwApi::TaskInterfaces::Ptr get(const String &name, int &result){
|
||||||
|
GWSYNCHRONIZED(&lock);
|
||||||
|
auto it = values.find(name);
|
||||||
|
if (it == values.end())
|
||||||
|
{
|
||||||
|
result = -1;
|
||||||
|
return GwApi::TaskInterfaces::Ptr();
|
||||||
|
}
|
||||||
|
result = it->second.updates;
|
||||||
|
return it->second.ptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
class TaskInterfacesImpl : public GwApi::TaskInterfaces{
|
||||||
|
String task;
|
||||||
|
TaskInterfacesStorage *storage;
|
||||||
|
GwLog *logger;
|
||||||
|
bool isInit=false;
|
||||||
|
public:
|
||||||
|
TaskInterfacesImpl(const String &n,TaskInterfacesStorage *s, GwLog *l,bool i):
|
||||||
|
task(n),storage(s),isInit(i),logger(l){}
|
||||||
|
virtual bool iset(const String &file, const String &name, Ptr v){
|
||||||
|
return storage->set(file,name,task,v);
|
||||||
|
}
|
||||||
|
virtual Ptr iget(const String &name, int &result){
|
||||||
|
return storage->get(name,result);
|
||||||
|
}
|
||||||
|
virtual bool iclaim(const String &name, const String &task){
|
||||||
|
if (! isInit) return false;
|
||||||
|
auto it=registrations().find(name);
|
||||||
|
if (it == registrations().end()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unable to claim interface %s for task %s, not registered",name.c_str(),task.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!it->second.task.isEmpty()){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unable to claim interface %s for task %s, already claimed by %s",name.c_str(),task.c_str(),it->second.task.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
it->second.task=task;
|
||||||
|
LOG_DEBUG(GwLog::LOG,"claimed interface %s for task %s",name.c_str(),task.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class TaskApi : public GwApiInternal
|
||||||
{
|
{
|
||||||
GwApi *api;
|
GwApiInternal *api=nullptr;
|
||||||
int sourceId;
|
int sourceId;
|
||||||
SemaphoreHandle_t *mainLock;
|
SemaphoreHandle_t *mainLock;
|
||||||
|
SemaphoreHandle_t localLock;
|
||||||
|
std::map<int,GwCounter<String>> counter;
|
||||||
|
String name;
|
||||||
|
bool counterUsed=false;
|
||||||
|
int counterIdx=0;
|
||||||
|
TaskInterfacesImpl *interfaces;
|
||||||
|
bool isInit=false;
|
||||||
public:
|
public:
|
||||||
TaskApi(GwApi *api, int sourceId, SemaphoreHandle_t *mainLock)
|
TaskApi(GwApiInternal *api,
|
||||||
|
int sourceId,
|
||||||
|
SemaphoreHandle_t *mainLock,
|
||||||
|
const String &name,
|
||||||
|
TaskInterfacesStorage *s,
|
||||||
|
bool init=false)
|
||||||
{
|
{
|
||||||
this->sourceId = sourceId;
|
this->sourceId = sourceId;
|
||||||
this->api = api;
|
this->api = api;
|
||||||
this->mainLock=mainLock;
|
this->mainLock=mainLock;
|
||||||
|
this->name=name;
|
||||||
|
localLock=xSemaphoreCreateMutex();
|
||||||
|
interfaces=new TaskInterfacesImpl(name,s,api->getLogger(),init);
|
||||||
|
isInit=init;
|
||||||
}
|
}
|
||||||
virtual GwRequestQueue *getQueue()
|
virtual GwRequestQueue *getQueue()
|
||||||
{
|
{
|
||||||
|
@ -104,13 +258,91 @@ public:
|
||||||
GWSYNCHRONIZED(mainLock);
|
GWSYNCHRONIZED(mainLock);
|
||||||
api->getStatus(status);
|
api->getStatus(status);
|
||||||
}
|
}
|
||||||
virtual ~TaskApi(){};
|
virtual ~TaskApi(){
|
||||||
|
delete interfaces;
|
||||||
|
vSemaphoreDelete(localLock);
|
||||||
|
};
|
||||||
|
virtual void fillStatus(GwJsonDocument &status){
|
||||||
|
GWSYNCHRONIZED(&localLock);
|
||||||
|
if (! counterUsed) return;
|
||||||
|
for (auto it=counter.begin();it != counter.end();it++){
|
||||||
|
it->second.toJson(status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
virtual int getJsonSize(){
|
||||||
|
GWSYNCHRONIZED(&localLock);
|
||||||
|
if (! counterUsed) return 0;
|
||||||
|
int rt=0;
|
||||||
|
for (auto it=counter.begin();it != counter.end();it++){
|
||||||
|
rt+=it->second.getJsonSize();
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
};
|
||||||
|
virtual void increment(int idx,const String &name,bool failed=false){
|
||||||
|
GWSYNCHRONIZED(&localLock);
|
||||||
|
counterUsed=true;
|
||||||
|
auto it=counter.find(idx);
|
||||||
|
if (it == counter.end()) return;
|
||||||
|
if (failed) it->second.addFail(name);
|
||||||
|
else (it->second.add(name));
|
||||||
|
};
|
||||||
|
virtual void reset(int idx){
|
||||||
|
GWSYNCHRONIZED(&localLock);
|
||||||
|
counterUsed=true;
|
||||||
|
auto it=counter.find(idx);
|
||||||
|
if (it == counter.end()) return;
|
||||||
|
it->second.reset();
|
||||||
|
};
|
||||||
|
virtual void remove(int idx){
|
||||||
|
GWSYNCHRONIZED(&localLock);
|
||||||
|
counter.erase(idx);
|
||||||
|
}
|
||||||
|
virtual int addCounter(const String &name){
|
||||||
|
GWSYNCHRONIZED(&localLock);
|
||||||
|
counterUsed=true;
|
||||||
|
counterIdx++;
|
||||||
|
//avoid the need for an empty counter constructor
|
||||||
|
auto it=counter.find(counterIdx);
|
||||||
|
if (it == counter.end()){
|
||||||
|
counter.insert(std::make_pair(counterIdx,GwCounter<String>("count"+name)));
|
||||||
|
}
|
||||||
|
else it->second=GwCounter<String>("count"+name);
|
||||||
|
return counterIdx;
|
||||||
|
}
|
||||||
|
virtual TaskInterfaces * taskInterfaces(){
|
||||||
|
return interfaces;
|
||||||
|
}
|
||||||
|
virtual bool addXdrMapping(const GwXDRMappingDef &def){
|
||||||
|
return api->addXdrMapping(def);
|
||||||
|
}
|
||||||
|
virtual void addCapability(const String &name, const String &value){
|
||||||
|
if (! isInit) return;
|
||||||
|
userCapabilities[name]=value;
|
||||||
|
}
|
||||||
|
virtual bool addUserTask(GwUserTaskFunction task,const String tname, int stackSize=2000){
|
||||||
|
if (! isInit){
|
||||||
|
api->getLogger()->logDebug(GwLog::ERROR,"trying to add a user task %s outside init",tname.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (taskExists(userTasks,name)){
|
||||||
|
api->getLogger()->logDebug(GwLog::ERROR,"trying to add a user task %s that already exists",tname.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
userTasks.push_back(GwUserTask(tname,task,stackSize));
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG,"adding user task %s",tname.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GwUserCode::GwUserCode(GwApi *api,SemaphoreHandle_t *mainLock){
|
GwUserCode::GwUserCode(GwApiInternal *api,SemaphoreHandle_t *mainLock){
|
||||||
this->logger=api->getLogger();
|
this->logger=api->getLogger();
|
||||||
this->api=api;
|
this->api=api;
|
||||||
this->mainLock=mainLock;
|
this->mainLock=mainLock;
|
||||||
|
this->taskData=new TaskInterfacesStorage(this->logger);
|
||||||
|
}
|
||||||
|
GwUserCode::~GwUserCode(){
|
||||||
|
delete taskData;
|
||||||
}
|
}
|
||||||
void userTaskStart(void *p){
|
void userTaskStart(void *p){
|
||||||
GwUserTask *task=(GwUserTask*)p;
|
GwUserTask *task=(GwUserTask*)p;
|
||||||
|
@ -123,8 +355,8 @@ void userTaskStart(void *p){
|
||||||
delete task->api;
|
delete task->api;
|
||||||
task->api=NULL;
|
task->api=NULL;
|
||||||
}
|
}
|
||||||
void GwUserCode::startAddOnTask(GwApi *api,GwUserTask *task,int sourceId,String name){
|
void GwUserCode::startAddOnTask(GwApiInternal *api,GwUserTask *task,int sourceId,String name){
|
||||||
task->api=new TaskApi(api,sourceId,mainLock);
|
task->api=new TaskApi(api,sourceId,mainLock,name,taskData);
|
||||||
xTaskCreate(userTaskStart,name.c_str(),task->stackSize,task,3,NULL);
|
xTaskCreate(userTaskStart,name.c_str(),task->stackSize,task,3,NULL);
|
||||||
}
|
}
|
||||||
void GwUserCode::startUserTasks(int baseId){
|
void GwUserCode::startUserTasks(int baseId){
|
||||||
|
@ -139,7 +371,7 @@ void GwUserCode::startInitTasks(int baseId){
|
||||||
LOG_DEBUG(GwLog::DEBUG,"starting %d user init tasks",initTasks.size());
|
LOG_DEBUG(GwLog::DEBUG,"starting %d user init tasks",initTasks.size());
|
||||||
for (auto it=initTasks.begin();it != initTasks.end();it++){
|
for (auto it=initTasks.begin();it != initTasks.end();it++){
|
||||||
LOG_DEBUG(GwLog::LOG,"starting user init task %s with id %d",it->name.c_str(),baseId);
|
LOG_DEBUG(GwLog::LOG,"starting user init task %s with id %d",it->name.c_str(),baseId);
|
||||||
it->api=new TaskApi(api,baseId,mainLock);
|
it->api=new TaskApi(api,baseId,mainLock,it->name,taskData,true);
|
||||||
userTaskStart(&(*it));
|
userTaskStart(&(*it));
|
||||||
baseId++;
|
baseId++;
|
||||||
}
|
}
|
||||||
|
@ -153,3 +385,20 @@ void GwUserCode::startAddonTask(String name, TaskFunction_t task, int id){
|
||||||
GwUserCode::Capabilities * GwUserCode::getCapabilities(){
|
GwUserCode::Capabilities * GwUserCode::getCapabilities(){
|
||||||
return &userCapabilities;
|
return &userCapabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GwUserCode::fillStatus(GwJsonDocument &status){
|
||||||
|
for (auto it=userTasks.begin();it != userTasks.end();it++){
|
||||||
|
if (it->api){
|
||||||
|
it->api->fillStatus(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int GwUserCode::getJsonSize(){
|
||||||
|
int rt=0;
|
||||||
|
for (auto it=userTasks.begin();it != userTasks.end();it++){
|
||||||
|
if (it->api){
|
||||||
|
rt+=it->api->getJsonSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
|
@ -2,16 +2,23 @@
|
||||||
#define _GWUSERCODE_H
|
#define _GWUSERCODE_H
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include "GwApi.h"
|
||||||
|
#include "GwJsonDocument.h"
|
||||||
class GwLog;
|
class GwLog;
|
||||||
class GwApi;
|
|
||||||
typedef void (*GwUserTaskFunction)(GwApi *);
|
class GwApiInternal : public GwApi{
|
||||||
|
public:
|
||||||
|
~GwApiInternal(){}
|
||||||
|
virtual void fillStatus(GwJsonDocument &status){};
|
||||||
|
virtual int getJsonSize(){return 0;};
|
||||||
|
};
|
||||||
class GwUserTask{
|
class GwUserTask{
|
||||||
public:
|
public:
|
||||||
String name;
|
String name;
|
||||||
TaskFunction_t task=NULL;
|
TaskFunction_t task=NULL;
|
||||||
GwUserTaskFunction usertask=NULL;
|
GwUserTaskFunction usertask=NULL;
|
||||||
bool isUserTask=false;
|
bool isUserTask=false;
|
||||||
GwApi *api=NULL;
|
GwApiInternal *api=NULL;
|
||||||
int stackSize=2000;
|
int stackSize=2000;
|
||||||
GwUserTask(String name,TaskFunction_t task,int stackSize=2000){
|
GwUserTask(String name,TaskFunction_t task,int stackSize=2000){
|
||||||
this->name=name;
|
this->name=name;
|
||||||
|
@ -25,17 +32,23 @@ class GwUserTask{
|
||||||
this->stackSize=stackSize;
|
this->stackSize=stackSize;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TaskInterfacesStorage;
|
||||||
class GwUserCode{
|
class GwUserCode{
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
GwApi *api;
|
GwApiInternal *api;
|
||||||
SemaphoreHandle_t *mainLock;
|
SemaphoreHandle_t *mainLock;
|
||||||
void startAddOnTask(GwApi *api,GwUserTask *task,int sourceId,String name);
|
TaskInterfacesStorage *taskData;
|
||||||
|
void startAddOnTask(GwApiInternal *api,GwUserTask *task,int sourceId,String name);
|
||||||
public:
|
public:
|
||||||
|
~GwUserCode();
|
||||||
typedef std::map<String,String> Capabilities;
|
typedef std::map<String,String> Capabilities;
|
||||||
GwUserCode(GwApi *api, SemaphoreHandle_t *mainLock);
|
GwUserCode(GwApiInternal *api, SemaphoreHandle_t *mainLock);
|
||||||
void startUserTasks(int baseId);
|
void startUserTasks(int baseId);
|
||||||
void startInitTasks(int baseId);
|
void startInitTasks(int baseId);
|
||||||
void startAddonTask(String name,TaskFunction_t task, int id);
|
void startAddonTask(String name,TaskFunction_t task, int id);
|
||||||
Capabilities *getCapabilities();
|
Capabilities *getCapabilities();
|
||||||
|
void fillStatus(GwJsonDocument &status);
|
||||||
|
int getJsonSize();
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
|
@ -1,4 +1,5 @@
|
||||||
#include "GwXDRMappings.h"
|
#include "GwXDRMappings.h"
|
||||||
|
#include "GWConfig.h"
|
||||||
#include "N2kMessages.h"
|
#include "N2kMessages.h"
|
||||||
|
|
||||||
double PtoBar(double v)
|
double PtoBar(double v)
|
||||||
|
@ -55,20 +56,19 @@ GwXDRType *types[] = {
|
||||||
new GwXDRType(GwXDRType::GENERIC, "G", ""),
|
new GwXDRType(GwXDRType::GENERIC, "G", ""),
|
||||||
new GwXDRType(GwXDRType::DISPLACEMENT, "A", "P"),
|
new GwXDRType(GwXDRType::DISPLACEMENT, "A", "P"),
|
||||||
new GwXDRType(GwXDRType::DISPLACEMENTD, "A", "D",DegToRad,RadToDeg,"rd"),
|
new GwXDRType(GwXDRType::DISPLACEMENTD, "A", "D",DegToRad,RadToDeg,"rd"),
|
||||||
new GwXDRType(GwXDRType::RPM,"T","R"),
|
new GwXDRType(GwXDRType::RPM,"T","R")
|
||||||
//important to have 2x NULL!
|
};
|
||||||
NULL,
|
template<typename T, int size>
|
||||||
NULL};
|
int GetArrLength(T(&)[size]){return size;}
|
||||||
|
|
||||||
static GwXDRType *findType(GwXDRType::TypeCode type, int *start = NULL)
|
static GwXDRType *findType(GwXDRType::TypeCode type, int *start = NULL)
|
||||||
{
|
{
|
||||||
|
int len=GetArrLength(types);
|
||||||
int from = 0;
|
int from = 0;
|
||||||
if (start != NULL)
|
if (start != NULL)
|
||||||
from = *start;
|
from = *start;
|
||||||
if (types[from] == NULL)
|
if (from < 0 || from >= len) return NULL;
|
||||||
return NULL;
|
|
||||||
int i = from;
|
int i = from;
|
||||||
for (; types[i] != NULL; i++)
|
for (; i< len; i++)
|
||||||
{
|
{
|
||||||
if (types[i]->code == type)
|
if (types[i]->code == type)
|
||||||
{
|
{
|
||||||
|
@ -97,7 +97,7 @@ static GwXDRType::TypeCode findTypeMapping(GwXDRCategory category, int field)
|
||||||
return GwXDRType::UNKNOWN;
|
return GwXDRType::UNKNOWN;
|
||||||
}
|
}
|
||||||
//category,direction,selector,field,instanceMode,instance,name
|
//category,direction,selector,field,instanceMode,instance,name
|
||||||
String GwXDRMappingDef::toString()
|
String GwXDRMappingDef::toString() const
|
||||||
{
|
{
|
||||||
String rt = "";
|
String rt = "";
|
||||||
rt += String((int)category);
|
rt += String((int)category);
|
||||||
|
@ -234,6 +234,82 @@ GwXDRMappings::GwXDRMappings(GwLog *logger, GwConfigHandler *config)
|
||||||
this->logger = logger;
|
this->logger = logger;
|
||||||
this->config = config;
|
this->config = config;
|
||||||
}
|
}
|
||||||
|
bool GwXDRMappings::addFixedMapping(const GwXDRMappingDef &mapping){
|
||||||
|
GwXDRMappingDef *nm=new GwXDRMappingDef(mapping);
|
||||||
|
bool res=addMapping(nm);
|
||||||
|
if (! res){
|
||||||
|
LOG_DEBUG(GwLog::ERROR,"unable to add fixed mapping %s",mapping.toString().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool GwXDRMappings::addMapping(GwXDRMappingDef *def)
|
||||||
|
{
|
||||||
|
if (def)
|
||||||
|
{
|
||||||
|
int typeIndex = 0;
|
||||||
|
LOG_DEBUG(GwLog::LOG, "add xdr mapping %s",
|
||||||
|
def->toString().c_str());
|
||||||
|
// n2k: find first matching type mapping
|
||||||
|
GwXDRType::TypeCode code = findTypeMapping(def->category, def->field);
|
||||||
|
if (code == GwXDRType::UNKNOWN)
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "no type mapping for %s", def->toString().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GwXDRType *type = findType(code, &typeIndex);
|
||||||
|
if (!type)
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::ERROR, "no type definition for %s", def->toString().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long n2kkey = def->n2kKey();
|
||||||
|
auto it = n2kMap.find(n2kkey);
|
||||||
|
GwXDRMapping *mapping = new GwXDRMapping(def, type);
|
||||||
|
if (it == n2kMap.end())
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::LOG, "insert mapping with key %ld", n2kkey);
|
||||||
|
GwXDRMapping::MappingList mappings;
|
||||||
|
mappings.push_back(mapping);
|
||||||
|
n2kMap[n2kkey] = mappings;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::LOG, "append mapping with key %ld", n2kkey);
|
||||||
|
it->second.push_back(mapping);
|
||||||
|
}
|
||||||
|
// for nmea0183 there could be multiple entries
|
||||||
|
// as potentially there are different units that we can handle
|
||||||
|
// so after we inserted the definition we do additional type lookups
|
||||||
|
while (type != NULL)
|
||||||
|
{
|
||||||
|
String n183key = GwXDRMappingDef::n183key(def->xdrName,
|
||||||
|
type->xdrtype, type->xdrunit);
|
||||||
|
auto it = n183Map.find(n183key);
|
||||||
|
if (it == n183Map.end())
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::LOG, "insert mapping with n183key %s", n183key.c_str());
|
||||||
|
GwXDRMapping::MappingList mappings;
|
||||||
|
mappings.push_back(mapping);
|
||||||
|
n183Map[n183key] = mappings;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_DEBUG(GwLog::LOG, "append mapping with n183key %s", n183key.c_str());
|
||||||
|
it->second.push_back(mapping);
|
||||||
|
}
|
||||||
|
type = findType(code, &typeIndex);
|
||||||
|
if (!type)
|
||||||
|
break;
|
||||||
|
mapping = new GwXDRMapping(def, type);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#define MAX_MAPPINGS 100
|
#define MAX_MAPPINGS 100
|
||||||
void GwXDRMappings::begin()
|
void GwXDRMappings::begin()
|
||||||
|
@ -259,61 +335,10 @@ void GwXDRMappings::begin()
|
||||||
GwXDRMappingDef *def = GwXDRMappingDef::fromString(cfg->asCString());
|
GwXDRMappingDef *def = GwXDRMappingDef::fromString(cfg->asCString());
|
||||||
if (def)
|
if (def)
|
||||||
{
|
{
|
||||||
int typeIndex = 0;
|
bool res=addMapping(def);
|
||||||
LOG_DEBUG(GwLog::DEBUG, "read xdr mapping %s from %s",
|
if (! res){
|
||||||
def->toString().c_str(),namebuf);
|
LOG_DEBUG(GwLog::ERROR,"unable to add mapping from %s",cfg);
|
||||||
//n2k: find first matching type mapping
|
delete cfg;
|
||||||
GwXDRType::TypeCode code = findTypeMapping(def->category, def->field);
|
|
||||||
if (code == GwXDRType::UNKNOWN)
|
|
||||||
{
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "no type mapping for %s", def->toString().c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
GwXDRType *type = findType(code, &typeIndex);
|
|
||||||
if (!type)
|
|
||||||
{
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "no type definition for %s", def->toString().c_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
long n2kkey = def->n2kKey();
|
|
||||||
auto it = n2kMap.find(n2kkey);
|
|
||||||
GwXDRMapping *mapping = new GwXDRMapping(def, type);
|
|
||||||
if (it == n2kMap.end())
|
|
||||||
{
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "insert mapping with key %ld", n2kkey);
|
|
||||||
GwXDRMapping::MappingList mappings;
|
|
||||||
mappings.push_back(mapping);
|
|
||||||
n2kMap[n2kkey] = mappings;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "append mapping with key %ld", n2kkey);
|
|
||||||
it->second.push_back(mapping);
|
|
||||||
}
|
|
||||||
//for nmea0183 there could be multiple entries
|
|
||||||
//as potentially there are different units that we can handle
|
|
||||||
//so after we inserted the definition we do additional type lookups
|
|
||||||
while (type != NULL)
|
|
||||||
{
|
|
||||||
String n183key = GwXDRMappingDef::n183key(def->xdrName,
|
|
||||||
type->xdrtype, type->xdrunit);
|
|
||||||
auto it = n183Map.find(n183key);
|
|
||||||
if (it == n183Map.end())
|
|
||||||
{
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "insert mapping with n183key %s", n183key.c_str());
|
|
||||||
GwXDRMapping::MappingList mappings;
|
|
||||||
mappings.push_back(mapping);
|
|
||||||
n183Map[n183key] = mappings;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_DEBUG(GwLog::DEBUG, "append mapping with n183key %s", n183key.c_str());
|
|
||||||
it->second.push_back(mapping);
|
|
||||||
}
|
|
||||||
type = findType(code, &typeIndex);
|
|
||||||
if (!type)
|
|
||||||
break;
|
|
||||||
mapping=new GwXDRMapping(def,type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#ifndef _GWXDRMAPPINGS_H
|
#ifndef _GWXDRMAPPINGS_H
|
||||||
#define _GWXDRMAPPINGS_H
|
#define _GWXDRMAPPINGS_H
|
||||||
#include "GwLog.h"
|
#include "GwLog.h"
|
||||||
#include "GWConfig.h"
|
|
||||||
#include "GwBoatData.h"
|
#include "GwBoatData.h"
|
||||||
#include <WString.h>
|
#include <WString.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -115,7 +114,7 @@ class GwXDRMappingDef{
|
||||||
category=XDRTEMP;
|
category=XDRTEMP;
|
||||||
}
|
}
|
||||||
//category,direction,selector,field,instanceMode,instance,name
|
//category,direction,selector,field,instanceMode,instance,name
|
||||||
String toString();
|
String toString() const;
|
||||||
static GwXDRMappingDef *fromString(String s);
|
static GwXDRMappingDef *fromString(String s);
|
||||||
//we allow 100 entities of code,selector and field nid
|
//we allow 100 entities of code,selector and field nid
|
||||||
static unsigned long n2kKey(GwXDRCategory category, int selector, int field)
|
static unsigned long n2kKey(GwXDRCategory category, int selector, int field)
|
||||||
|
@ -200,6 +199,7 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
|
||||||
|
|
||||||
//the class GwXDRMappings is not intended to be deleted
|
//the class GwXDRMappings is not intended to be deleted
|
||||||
//the deletion will leave memory leaks!
|
//the deletion will leave memory leaks!
|
||||||
|
class GwConfigHandler;
|
||||||
class GwXDRMappings{
|
class GwXDRMappings{
|
||||||
static const int MAX_UNKNOWN=200;
|
static const int MAX_UNKNOWN=200;
|
||||||
static const int ESIZE=13;
|
static const int ESIZE=13;
|
||||||
|
@ -212,8 +212,10 @@ class GwXDRMappings{
|
||||||
char *unknowAsString=NULL;
|
char *unknowAsString=NULL;
|
||||||
GwXDRFoundMapping selectMapping(GwXDRMapping::MappingList *list,int instance,const char * key);
|
GwXDRFoundMapping selectMapping(GwXDRMapping::MappingList *list,int instance,const char * key);
|
||||||
bool addUnknown(GwXDRCategory category,int selector,int field=0,int instance=-1);
|
bool addUnknown(GwXDRCategory category,int selector,int field=0,int instance=-1);
|
||||||
|
bool addMapping(GwXDRMappingDef *mapping);
|
||||||
public:
|
public:
|
||||||
GwXDRMappings(GwLog *logger,GwConfigHandler *config);
|
GwXDRMappings(GwLog *logger,GwConfigHandler *config);
|
||||||
|
bool addFixedMapping(const GwXDRMappingDef &mapping);
|
||||||
void begin();
|
void begin();
|
||||||
//get the mappings
|
//get the mappings
|
||||||
//the returned mapping will exactly contain one mapping def
|
//the returned mapping will exactly contain one mapping def
|
||||||
|
|
|
@ -17,14 +17,25 @@ extra_configs=
|
||||||
lib/*task*/platformio.ini
|
lib/*task*/platformio.ini
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
platform = espressif32 @ 3.4.0
|
platform = espressif32 @ 6.3.2
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
;platform_packages=
|
||||||
|
; framework-arduinoespressif32 @ 3.20011.230801
|
||||||
|
; framework-espidf @ 3.50101.0
|
||||||
lib_deps =
|
lib_deps =
|
||||||
ttlappalainen/NMEA2000-library @ 4.17.2
|
ttlappalainen/NMEA2000-library @ 4.18.9
|
||||||
ttlappalainen/NMEA0183 @ 1.7.1
|
ttlappalainen/NMEA0183 @ 1.9.1
|
||||||
ArduinoJson @ 6.18.5
|
ArduinoJson @ 6.18.5
|
||||||
ottowinter/ESPAsyncWebServer-esphome@2.0.1
|
ottowinter/ESPAsyncWebServer-esphome@2.0.1
|
||||||
fastled/FastLED @ 3.4.0
|
fastled/FastLED @ 3.6.0
|
||||||
|
FS
|
||||||
|
Preferences
|
||||||
|
ESPmDNS
|
||||||
|
WiFi
|
||||||
|
Update
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
board_build.embed_files =
|
board_build.embed_files =
|
||||||
lib/generated/index.html.gz
|
lib/generated/index.html.gz
|
||||||
lib/generated/index.js.gz
|
lib/generated/index.js.gz
|
||||||
|
@ -36,11 +47,21 @@ board_build.partitions = partitions_custom.csv
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
pre:extra_script.py
|
pre:extra_script.py
|
||||||
post:post.py
|
post:post.py
|
||||||
lib_ldf_mode = chain+
|
lib_ldf_mode = off
|
||||||
|
#lib_ldf_mode = chain+
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
build_flags =
|
build_flags =
|
||||||
-D PIO_ENV_BUILD=$PIOENV
|
-D PIO_ENV_BUILD=$PIOENV
|
||||||
|
|
||||||
|
[sensors]
|
||||||
|
; collect the libraries for sensors here
|
||||||
|
lib_deps =
|
||||||
|
Wire
|
||||||
|
SPI
|
||||||
|
adafruit/Adafruit BME280 Library @ 2.2.2
|
||||||
|
adafruit/Adafruit BusIO @ 1.14.5
|
||||||
|
adafruit/Adafruit Unified Sensor @ 1.1.13
|
||||||
|
|
||||||
[env:m5stack-atom]
|
[env:m5stack-atom]
|
||||||
board = m5stack-atom
|
board = m5stack-atom
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
|
@ -50,6 +71,37 @@ build_flags =
|
||||||
upload_port = /dev/esp32
|
upload_port = /dev/esp32
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
|
|
||||||
|
[env:m5stack-atom-generic]
|
||||||
|
extends = sensors
|
||||||
|
board = m5stack-atom
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
${sensors.lib_deps}
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
upload_port = /dev/esp32
|
||||||
|
upload_protocol = esptool
|
||||||
|
|
||||||
|
[env:m5stack-atoms3]
|
||||||
|
board = m5stack-atoms3
|
||||||
|
lib_deps = ${env.lib_deps}
|
||||||
|
build_flags =
|
||||||
|
-D BOARD_M5ATOMS3
|
||||||
|
${env.build_flags}
|
||||||
|
upload_port = /dev/esp32s3
|
||||||
|
upload_protocol = esptool
|
||||||
|
|
||||||
|
[env:m5stack-atoms3-generic]
|
||||||
|
extends = sensors
|
||||||
|
board = m5stack-atoms3
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
${sensors.lib_deps}
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
upload_port = /dev/esp32s3
|
||||||
|
upload_protocol = esptool
|
||||||
|
|
||||||
[env:m5stack-atom-canunit]
|
[env:m5stack-atom-canunit]
|
||||||
board = m5stack-atom
|
board = m5stack-atom
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
|
@ -59,6 +111,16 @@ build_flags =
|
||||||
upload_port = /dev/esp32
|
upload_port = /dev/esp32
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
|
|
||||||
|
[env:m5stack-atoms3-canunit]
|
||||||
|
board = m5stack-atoms3
|
||||||
|
lib_deps = ${env.lib_deps}
|
||||||
|
build_flags =
|
||||||
|
-D BOARD_M5ATOMS3_CANUNIT
|
||||||
|
${env.build_flags}
|
||||||
|
upload_port = /dev/esp32s3
|
||||||
|
upload_protocol = esptool
|
||||||
|
|
||||||
|
|
||||||
[env:m5stack-atom-rs232-canunit]
|
[env:m5stack-atom-rs232-canunit]
|
||||||
board = m5stack-atom
|
board = m5stack-atom
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
|
@ -87,6 +149,18 @@ build_flags =
|
||||||
upload_port = /dev/esp32
|
upload_port = /dev/esp32
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
|
|
||||||
|
[env:m5stickc-atom-generic]
|
||||||
|
extends = sensors
|
||||||
|
board = m5stick-c
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
${sensors.lib_deps}
|
||||||
|
build_flags =
|
||||||
|
-D BOARD_M5STICK -D HAS_RTC -D HAS_M5LCD
|
||||||
|
${env.build_flags}
|
||||||
|
upload_port = /dev/esp32
|
||||||
|
upload_protocol = esptool
|
||||||
|
|
||||||
[env:nodemcu-homberger]
|
[env:nodemcu-homberger]
|
||||||
board = nodemcu-32s
|
board = nodemcu-32s
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
|
@ -95,3 +169,14 @@ build_flags =
|
||||||
${env.build_flags}
|
${env.build_flags}
|
||||||
upload_port = /dev/esp32
|
upload_port = /dev/esp32
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
|
|
||||||
|
[env:nodemcu-generic]
|
||||||
|
extends = sensors
|
||||||
|
board = nodemcu-32s
|
||||||
|
lib_deps =
|
||||||
|
${env.lib_deps}
|
||||||
|
${sensors.lib_deps}
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
upload_port = /dev/esp32
|
||||||
|
upload_protocol = esptool
|
||||||
|
|
19
post.py
19
post.py
|
@ -42,11 +42,16 @@ def post(source,target,env):
|
||||||
print("found fwname=%s, fwversion=%s"%(fwname,version))
|
print("found fwname=%s, fwversion=%s"%(fwname,version))
|
||||||
python=env.subst("$PYTHONEXE")
|
python=env.subst("$PYTHONEXE")
|
||||||
print("base=%s,esptool=%s,appoffset=%s,uploaderflags=%s"%(base,esptool,appoffset,uploaderflags))
|
print("base=%s,esptool=%s,appoffset=%s,uploaderflags=%s"%(base,esptool,appoffset,uploaderflags))
|
||||||
|
chip="esp32"
|
||||||
uploadparts=uploaderflags.split(" ")
|
uploadparts=uploaderflags.split(" ")
|
||||||
#currently hardcoded last 8 parameters...
|
#currently hardcoded last 8 parameters...
|
||||||
if len(uploadparts) < 6:
|
if len(uploadparts) < 6:
|
||||||
print("uploaderflags does not have enough parameter")
|
print("uploaderflags does not have enough parameter")
|
||||||
return
|
return
|
||||||
|
for i in range(0,len(uploadparts)):
|
||||||
|
if uploadparts[i]=="--chip":
|
||||||
|
if i < (len(uploadparts) -1):
|
||||||
|
chip=uploadparts[i+1]
|
||||||
uploadfiles=uploadparts[-6:]
|
uploadfiles=uploadparts[-6:]
|
||||||
for i in range(1,len(uploadfiles),2):
|
for i in range(1,len(uploadfiles),2):
|
||||||
if not os.path.isfile(uploadfiles[i]):
|
if not os.path.isfile(uploadfiles[i]):
|
||||||
|
@ -58,17 +63,17 @@ def post(source,target,env):
|
||||||
for f in glob.glob(os.path.join(outdir,base+"*.bin")):
|
for f in glob.glob(os.path.join(outdir,base+"*.bin")):
|
||||||
print("removing old file %s"%f)
|
print("removing old file %s"%f)
|
||||||
os.unlink(f)
|
os.unlink(f)
|
||||||
ofversion=''
|
outfile=os.path.join(outdir,"%s-all.bin"%(base))
|
||||||
if not version.startswith('dev'):
|
cmd=[python,esptool,"--chip",chip,"merge_bin","--target-offset",offset,"-o",outfile]
|
||||||
ofversion="-"+version
|
|
||||||
versionedFile=os.path.join(outdir,"%s%s-update.bin"%(base,ofversion))
|
|
||||||
shutil.copyfile(firmware,versionedFile)
|
|
||||||
outfile=os.path.join(outdir,"%s%s-all.bin"%(base,ofversion))
|
|
||||||
cmd=[python,esptool,"--chip","esp32","merge_bin","--target-offset",offset,"-o",outfile]
|
|
||||||
cmd+=uploadfiles
|
cmd+=uploadfiles
|
||||||
cmd+=[appoffset,firmware]
|
cmd+=[appoffset,firmware]
|
||||||
print("running %s"%" ".join(cmd))
|
print("running %s"%" ".join(cmd))
|
||||||
env.Execute(" ".join(cmd),"#testpost")
|
env.Execute(" ".join(cmd),"#testpost")
|
||||||
|
ofversion="-"+version
|
||||||
|
versionedFile=os.path.join(outdir,"%s%s-update.bin"%(base,ofversion))
|
||||||
|
shutil.copyfile(firmware,versionedFile)
|
||||||
|
versioneOutFile=os.path.join(outdir,"%s%s-all.bin"%(base,ofversion))
|
||||||
|
shutil.copyfile(outfile,versioneOutFile)
|
||||||
env.AddPostAction(
|
env.AddPostAction(
|
||||||
"$BUILD_DIR/${PROGNAME}.bin",
|
"$BUILD_DIR/${PROGNAME}.bin",
|
||||||
post
|
post
|
||||||
|
|
202
src/main.cpp
202
src/main.cpp
|
@ -14,9 +14,10 @@
|
||||||
#include "GwAppInfo.h"
|
#include "GwAppInfo.h"
|
||||||
// #define GW_MESSAGE_DEBUG_ENABLED
|
// #define GW_MESSAGE_DEBUG_ENABLED
|
||||||
//#define FALLBACK_SERIAL
|
//#define FALLBACK_SERIAL
|
||||||
//#define CAN_ESP_DEBUG
|
#define OWN_LOOP
|
||||||
const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "Preferences.h"
|
||||||
#include "GwApi.h"
|
#include "GwApi.h"
|
||||||
#include "GwHardware.h"
|
#include "GwHardware.h"
|
||||||
|
|
||||||
|
@ -54,8 +55,6 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||||
#include "GwSerial.h"
|
#include "GwSerial.h"
|
||||||
#include "GwWebServer.h"
|
#include "GwWebServer.h"
|
||||||
#include "NMEA0183DataToN2K.h"
|
#include "NMEA0183DataToN2K.h"
|
||||||
#include "GwButtons.h"
|
|
||||||
#include "GwLeds.h"
|
|
||||||
#include "GwCounter.h"
|
#include "GwCounter.h"
|
||||||
#include "GwXDRMappings.h"
|
#include "GwXDRMappings.h"
|
||||||
#include "GwSynchronized.h"
|
#include "GwSynchronized.h"
|
||||||
|
@ -65,18 +64,7 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||||
#include "GwTcpClient.h"
|
#include "GwTcpClient.h"
|
||||||
#include "GwChannel.h"
|
#include "GwChannel.h"
|
||||||
#include "GwChannelList.h"
|
#include "GwChannelList.h"
|
||||||
|
#include "GwTimer.h"
|
||||||
#include <NMEA2000_esp32.h> // forked from https://github.com/ttlappalainen/NMEA2000_esp32
|
|
||||||
#ifdef FALLBACK_SERIAL
|
|
||||||
#ifdef CAN_ESP_DEBUG
|
|
||||||
#define CDBS &Serial
|
|
||||||
#else
|
|
||||||
#define CDBS NULL
|
|
||||||
#endif
|
|
||||||
tNMEA2000 &NMEA2000=*(new tNMEA2000_esp32(ESP32_CAN_TX_PIN,ESP32_CAN_RX_PIN,CDBS));
|
|
||||||
#else
|
|
||||||
tNMEA2000 &NMEA2000=*(new tNMEA2000_esp32());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#define MAX_NMEA2000_MESSAGE_SEASMART_SIZE 500
|
#define MAX_NMEA2000_MESSAGE_SEASMART_SIZE 500
|
||||||
|
@ -88,6 +76,7 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||||
//assert length of firmware name and version
|
//assert length of firmware name and version
|
||||||
CASSERT(strlen(FIRMWARE_TYPE) <= 32, "environment name (FIRMWARE_TYPE) must not exceed 32 chars");
|
CASSERT(strlen(FIRMWARE_TYPE) <= 32, "environment name (FIRMWARE_TYPE) must not exceed 32 chars");
|
||||||
CASSERT(strlen(VERSION) <= 32, "VERSION must not exceed 32 chars");
|
CASSERT(strlen(VERSION) <= 32, "VERSION must not exceed 32 chars");
|
||||||
|
CASSERT(strlen(IDF_VERSION) <= 32,"IDF_VERSION must not exceed 32 chars");
|
||||||
//https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/app_image_format.html
|
//https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/app_image_format.html
|
||||||
//and removed the bugs in the doc...
|
//and removed the bugs in the doc...
|
||||||
__attribute__((section(".rodata_custom_desc"))) esp_app_desc_t custom_app_desc = {
|
__attribute__((section(".rodata_custom_desc"))) esp_app_desc_t custom_app_desc = {
|
||||||
|
@ -98,7 +87,7 @@ __attribute__((section(".rodata_custom_desc"))) esp_app_desc_t custom_app_desc =
|
||||||
FIRMWARE_TYPE,
|
FIRMWARE_TYPE,
|
||||||
"00:00:00",
|
"00:00:00",
|
||||||
"2021/12/13",
|
"2021/12/13",
|
||||||
"0000",
|
IDF_VERSION,
|
||||||
{},
|
{},
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
@ -111,6 +100,35 @@ typedef std::map<String,String> StringMap;
|
||||||
|
|
||||||
GwLog logger(LOGLEVEL,NULL);
|
GwLog logger(LOGLEVEL,NULL);
|
||||||
GwConfigHandler config(&logger);
|
GwConfigHandler config(&logger);
|
||||||
|
|
||||||
|
#include "Nmea2kTwai.h"
|
||||||
|
static const unsigned long CAN_RECOVERY_PERIOD=3000; //ms
|
||||||
|
static const unsigned long NMEA2000_HEARTBEAT_INTERVAL=5000;
|
||||||
|
class Nmea2kTwaiLog : public Nmea2kTwai{
|
||||||
|
private:
|
||||||
|
GwLog* logger;
|
||||||
|
public:
|
||||||
|
Nmea2kTwaiLog(gpio_num_t _TxPin, gpio_num_t _RxPin, unsigned long recoveryPeriod,GwLog *l):
|
||||||
|
Nmea2kTwai(_TxPin,_RxPin,recoveryPeriod,recoveryPeriod),logger(l){}
|
||||||
|
virtual void logDebug(int level, const char *fmt,...){
|
||||||
|
va_list args;
|
||||||
|
va_start(args,fmt);
|
||||||
|
if (level > 2) level++; //error+info+debug are similar, map msg to 4
|
||||||
|
logger->logDebug(level,fmt,args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef ESP32_CAN_TX_PIN
|
||||||
|
#pragma message "WARNING: ESP32_CAN_TX_PIN not defined"
|
||||||
|
#define ESP32_CAN_TX_PIN GPIO_NUM_NC
|
||||||
|
#endif
|
||||||
|
#ifndef ESP32_CAN_RX_PIN
|
||||||
|
#pragma message "WARNING: ESP32_CAN_RX_PIN not defined"
|
||||||
|
#define ESP32_CAN_RX_PIN GPIO_NUM_NC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Nmea2kTwai &NMEA2000=*(new Nmea2kTwaiLog((gpio_num_t)ESP32_CAN_TX_PIN,(gpio_num_t)ESP32_CAN_RX_PIN,CAN_RECOVERY_PERIOD,&logger));
|
||||||
|
|
||||||
#ifdef GWBUTTON_PIN
|
#ifdef GWBUTTON_PIN
|
||||||
bool fixedApPass=false;
|
bool fixedApPass=false;
|
||||||
#else
|
#else
|
||||||
|
@ -138,9 +156,9 @@ SemaphoreHandle_t mainLock;
|
||||||
GwRequestQueue mainQueue(&logger,20);
|
GwRequestQueue mainQueue(&logger,20);
|
||||||
GwWebServer webserver(&logger,&mainQueue,80);
|
GwWebServer webserver(&logger,&mainQueue,80);
|
||||||
|
|
||||||
GwCounter<unsigned long> countNMEA2KIn("count2Kin");
|
GwCounter<unsigned long> countNMEA2KIn("countNMEA2000in");
|
||||||
GwCounter<unsigned long> countNMEA2KOut("count2Kout");
|
GwCounter<unsigned long> countNMEA2KOut("countNMEA2000out");
|
||||||
|
GwIntervalRunner timers;
|
||||||
|
|
||||||
bool checkPass(String hash){
|
bool checkPass(String hash){
|
||||||
return config.checkPass(hash);
|
return config.checkPass(hash);
|
||||||
|
@ -187,7 +205,6 @@ void handleN2kMessage(const tN2kMsg &n2kMsg,int sourceId, bool isConverted=false
|
||||||
nmea0183Converter->HandleMsg(n2kMsg,sourceId);
|
nmea0183Converter->HandleMsg(n2kMsg,sourceId);
|
||||||
}
|
}
|
||||||
if (sourceId != N2K_CHANNEL_ID && sendOutN2k){
|
if (sourceId != N2K_CHANNEL_ID && sendOutN2k){
|
||||||
countNMEA2KOut.add(n2kMsg.PGN);
|
|
||||||
if (NMEA2000.SendMsg(n2kMsg)){
|
if (NMEA2000.SendMsg(n2kMsg)){
|
||||||
countNMEA2KOut.add(n2kMsg.PGN);
|
countNMEA2KOut.add(n2kMsg.PGN);
|
||||||
}
|
}
|
||||||
|
@ -217,7 +234,7 @@ void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId,bool conv
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiImpl : public GwApi
|
class ApiImpl : public GwApiInternal
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
int sourceId = -1;
|
int sourceId = -1;
|
||||||
|
@ -299,9 +316,21 @@ public:
|
||||||
return &boatData;
|
return &boatData;
|
||||||
}
|
}
|
||||||
virtual const char* getTalkerId(){
|
virtual const char* getTalkerId(){
|
||||||
return config.getString(config.talkerId,String("GP")).c_str();
|
return config.getCString(config.talkerId,"GP");
|
||||||
}
|
}
|
||||||
virtual ~ApiImpl(){}
|
virtual ~ApiImpl(){}
|
||||||
|
virtual TaskInterfaces *taskInterfaces(){ return nullptr;}
|
||||||
|
virtual bool addXdrMapping(const GwXDRMappingDef &mapping){
|
||||||
|
if (! config.userChangesAllowed()){
|
||||||
|
logger.logDebug(GwLog::ERROR,"trying to add an XDR mapping %s after the init phase",mapping.toString().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return xdrMappings.addFixedMapping(mapping);
|
||||||
|
}
|
||||||
|
virtual void addCapability(const String &name, const String &value){}
|
||||||
|
virtual bool addUserTask(GwUserTaskFunction task,const String Name, int stackSize=2000){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
bool delayedRestart(){
|
bool delayedRestart(){
|
||||||
|
@ -353,10 +382,11 @@ public:
|
||||||
protected:
|
protected:
|
||||||
virtual void processRequest()
|
virtual void processRequest()
|
||||||
{
|
{
|
||||||
GwJsonDocument status(256 +
|
GwJsonDocument status(300 +
|
||||||
countNMEA2KIn.getJsonSize()+
|
countNMEA2KIn.getJsonSize()+
|
||||||
countNMEA2KOut.getJsonSize() +
|
countNMEA2KOut.getJsonSize() +
|
||||||
channels.getJsonSize()
|
channels.getJsonSize()+
|
||||||
|
userCodeHandler.getJsonSize()
|
||||||
);
|
);
|
||||||
status["version"] = VERSION;
|
status["version"] = VERSION;
|
||||||
status["wifiConnected"] = gwWifi.clientConnected();
|
status["wifiConnected"] = gwWifi.clientConnected();
|
||||||
|
@ -368,11 +398,27 @@ protected:
|
||||||
GwConfigHandler::toHex(base,buffer,bsize);
|
GwConfigHandler::toHex(base,buffer,bsize);
|
||||||
status["salt"] = buffer;
|
status["salt"] = buffer;
|
||||||
status["fwtype"]= firmwareType;
|
status["fwtype"]= firmwareType;
|
||||||
|
status["chipid"]=CONFIG_IDF_FIRMWARE_CHIP_ID;
|
||||||
status["heap"]=(long)xPortGetFreeHeapSize();
|
status["heap"]=(long)xPortGetFreeHeapSize();
|
||||||
|
Nmea2kTwai::Status n2kState=NMEA2000.getStatus();
|
||||||
|
Nmea2kTwai::STATE driverState=n2kState.state;
|
||||||
|
if (driverState == Nmea2kTwai::ST_RUNNING){
|
||||||
|
unsigned long lastRec=NMEA2000.getLastRecoveryStart();
|
||||||
|
if (lastRec > 0 && (lastRec+NMEA2000_HEARTBEAT_INTERVAL*2) > millis()){
|
||||||
|
//we still report bus off at least for 2 heartbeat intervals
|
||||||
|
//this avoids always reporting BUS_OFF-RUNNING-BUS_OFF if the bus off condition
|
||||||
|
//remains
|
||||||
|
driverState=Nmea2kTwai::ST_BUS_OFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status["n2kstate"]=NMEA2000.stateStr(driverState);
|
||||||
|
status["n2knode"]=NodeAddress;
|
||||||
|
status["minUser"]=MIN_USER_TASK;
|
||||||
//nmea0183Converter->toJson(status);
|
//nmea0183Converter->toJson(status);
|
||||||
countNMEA2KIn.toJson(status);
|
countNMEA2KIn.toJson(status);
|
||||||
countNMEA2KOut.toJson(status);
|
countNMEA2KOut.toJson(status);
|
||||||
channels.toJson(status);
|
channels.toJson(status);
|
||||||
|
userCodeHandler.fillStatus(status);
|
||||||
serializeJson(status, result);
|
serializeJson(status, result);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -397,17 +443,23 @@ class CapabilitiesRequest : public GwRequestMessage{
|
||||||
protected:
|
protected:
|
||||||
virtual void processRequest(){
|
virtual void processRequest(){
|
||||||
int numCapabilities=userCodeHandler.getCapabilities()->size();
|
int numCapabilities=userCodeHandler.getCapabilities()->size();
|
||||||
GwJsonDocument json(JSON_OBJECT_SIZE(numCapabilities*3+6));
|
int numSpecial=config.numSpecial();
|
||||||
|
logger.logDebug(GwLog::LOG,"capabilities user=%d, config=%d",numCapabilities,numSpecial);
|
||||||
|
GwJsonDocument json(JSON_OBJECT_SIZE(numCapabilities*3+numSpecial*2+8));
|
||||||
for (auto it=userCodeHandler.getCapabilities()->begin();
|
for (auto it=userCodeHandler.getCapabilities()->begin();
|
||||||
it != userCodeHandler.getCapabilities()->end();it++){
|
it != userCodeHandler.getCapabilities()->end();it++){
|
||||||
json[it->first]=it->second;
|
json[it->first]=it->second;
|
||||||
}
|
}
|
||||||
#ifdef GWSERIAL_MODE
|
std::vector<String> specialCfg=config.getSpecial();
|
||||||
String serial(F(GWSERIAL_MODE));
|
for (auto it=specialCfg.begin();it != specialCfg.end();it++){
|
||||||
#else
|
GwConfigInterface *cfg=config.getConfigItem(*it);
|
||||||
String serial(F("NONE"));
|
if (cfg){
|
||||||
#endif
|
logger.logDebug(GwLog::LOG,"config mode %s=%d",it->c_str(),(int)(cfg->getType()));
|
||||||
json["serialmode"]=serial;
|
json["CFGMODE"+*it]=(int)cfg->getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json["serialmode"]=channels.getMode(SERIAL1_CHANNEL_ID);
|
||||||
|
json["serial2mode"]=channels.getMode(SERIAL2_CHANNEL_ID);
|
||||||
#ifdef GWBUTTON_PIN
|
#ifdef GWBUTTON_PIN
|
||||||
json["hardwareReset"]="true";
|
json["hardwareReset"]="true";
|
||||||
#endif
|
#endif
|
||||||
|
@ -656,7 +708,37 @@ void handleConfigRequestData(AsyncWebServerRequest *request, uint8_t *data, size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimeMonitor monitor(20,0.2);
|
||||||
|
class DefaultLogWriter: public GwLogWriter{
|
||||||
|
public:
|
||||||
|
virtual ~DefaultLogWriter(){};
|
||||||
|
virtual void write(const char *data){
|
||||||
|
USBSerial.print(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void loopRun();
|
||||||
|
void loop(){
|
||||||
|
#ifdef OWN_LOOP
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
loopRun();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void loopFunction(void *){
|
||||||
|
while (true){
|
||||||
|
loopRun();
|
||||||
|
//we don not call the serialEvent stuff as in the original
|
||||||
|
//main loop as this could cause some sort of a deadlock
|
||||||
|
//if serial writing or reading is done in a different thread
|
||||||
|
//and it remains inside some read/write routine with the uart being
|
||||||
|
//locked
|
||||||
|
//if(Serial1.available()) {}
|
||||||
|
//if(Serial.available()) {}
|
||||||
|
//if(Serial2.available()) {}
|
||||||
|
//delay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
void setup() {
|
void setup() {
|
||||||
mainLock=xSemaphoreCreateMutex();
|
mainLock=xSemaphoreCreateMutex();
|
||||||
uint8_t chipid[6];
|
uint8_t chipid[6];
|
||||||
|
@ -668,9 +750,10 @@ void setup() {
|
||||||
#ifdef FALLBACK_SERIAL
|
#ifdef FALLBACK_SERIAL
|
||||||
fallbackSerial=true;
|
fallbackSerial=true;
|
||||||
//falling back to old style serial for logging
|
//falling back to old style serial for logging
|
||||||
Serial.begin(115200);
|
USBSerial.begin(115200);
|
||||||
Serial.printf("fallback serial enabled\n");
|
USBSerial.printf("fallback serial enabled\n");
|
||||||
logger.prefix="FALLBACK:";
|
logger.prefix="FALLBACK:";
|
||||||
|
logger.setWriter(new DefaultLogWriter());
|
||||||
#endif
|
#endif
|
||||||
userCodeHandler.startInitTasks(MIN_USER_TASK);
|
userCodeHandler.startInitTasks(MIN_USER_TASK);
|
||||||
config.stopChanges();
|
config.stopChanges();
|
||||||
|
@ -780,6 +863,7 @@ void setup() {
|
||||||
logger.flush();
|
logger.flush();
|
||||||
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, NodeAddress);
|
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, NodeAddress);
|
||||||
NMEA2000.SetForwardOwnMessages(false);
|
NMEA2000.SetForwardOwnMessages(false);
|
||||||
|
NMEA2000.SetHeartbeatInterval(NMEA2000_HEARTBEAT_INTERVAL);
|
||||||
if (sendOutN2k){
|
if (sendOutN2k){
|
||||||
// Set the information for other bus devices, which messages we support
|
// Set the information for other bus devices, which messages we support
|
||||||
unsigned long *pgns=toN2KConverter->handledPgns();
|
unsigned long *pgns=toN2KConverter->handledPgns();
|
||||||
|
@ -800,16 +884,26 @@ void setup() {
|
||||||
NMEA2000.Open();
|
NMEA2000.Open();
|
||||||
logger.logDebug(GwLog::LOG,"starting addon tasks");
|
logger.logDebug(GwLog::LOG,"starting addon tasks");
|
||||||
logger.flush();
|
logger.flush();
|
||||||
userCodeHandler.startAddonTask(F("handleButtons"),handleButtons,100);
|
|
||||||
setLedMode(LED_GREEN);
|
|
||||||
userCodeHandler.startAddonTask(F("handleLeds"),handleLeds,101);
|
|
||||||
{
|
{
|
||||||
GWSYNCHRONIZED(&mainLock);
|
GWSYNCHRONIZED(&mainLock);
|
||||||
userCodeHandler.startUserTasks(MIN_USER_TASK);
|
userCodeHandler.startUserTasks(MIN_USER_TASK);
|
||||||
}
|
}
|
||||||
|
timers.addAction(HEAP_REPORT_TIME,[](){
|
||||||
|
if (logger.isActive(GwLog::DEBUG)){
|
||||||
|
logger.logDebug(GwLog::DEBUG,"Heap free=%ld, minFree=%ld",
|
||||||
|
(long)xPortGetFreeHeapSize(),
|
||||||
|
(long)xPortGetMinimumEverFreeHeapSize()
|
||||||
|
);
|
||||||
|
logger.logDebug(GwLog::DEBUG,"Main loop %s",monitor.getLog().c_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
logger.logString("wifi AP pass: %s",fixedApPass? gwWifi.AP_password:config.getString(config.apPassword).c_str());
|
logger.logString("wifi AP pass: %s",fixedApPass? gwWifi.AP_password:config.getString(config.apPassword).c_str());
|
||||||
logger.logString("admin pass: %s",config.getString(config.adminPassword).c_str());
|
logger.logString("admin pass: %s",config.getString(config.adminPassword).c_str());
|
||||||
logger.logDebug(GwLog::LOG,"setup done");
|
logger.logDebug(GwLog::LOG,"setup done");
|
||||||
|
#ifdef OWN_LOOP
|
||||||
|
logger.logDebug(GwLog::LOG,"starting own main loop");
|
||||||
|
xTaskCreateUniversal(loopFunction,"loop",8192,NULL,1,NULL,ARDUINO_RUNNING_CORE);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
//*****************************************************************************
|
//*****************************************************************************
|
||||||
void handleSendAndRead(bool handleRead){
|
void handleSendAndRead(bool handleRead){
|
||||||
|
@ -818,9 +912,8 @@ void handleSendAndRead(bool handleRead){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeMonitor monitor(20,0.2);
|
void loopRun() {
|
||||||
unsigned long lastHeapReport=0;
|
//logger.logDebug(GwLog::DEBUG,"main loop start");
|
||||||
void loop() {
|
|
||||||
monitor.reset();
|
monitor.reset();
|
||||||
GWSYNCHRONIZED(&mainLock);
|
GWSYNCHRONIZED(&mainLock);
|
||||||
logger.flush();
|
logger.flush();
|
||||||
|
@ -828,29 +921,22 @@ void loop() {
|
||||||
gwWifi.loop();
|
gwWifi.loop();
|
||||||
unsigned long now=millis();
|
unsigned long now=millis();
|
||||||
monitor.setTime(2);
|
monitor.setTime(2);
|
||||||
if (HEAP_REPORT_TIME > 0 && now > (lastHeapReport+HEAP_REPORT_TIME)){
|
timers.loop();
|
||||||
lastHeapReport=now;
|
|
||||||
if (logger.isActive(GwLog::DEBUG)){
|
|
||||||
logger.logDebug(GwLog::DEBUG,"Heap free=%ld, minFree=%ld",
|
|
||||||
(long)xPortGetFreeHeapSize(),
|
|
||||||
(long)xPortGetMinimumEverFreeHeapSize()
|
|
||||||
);
|
|
||||||
logger.logDebug(GwLog::DEBUG,"Main loop %s",monitor.getLog().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
monitor.setTime(3);
|
monitor.setTime(3);
|
||||||
|
NMEA2000.loop();
|
||||||
|
monitor.setTime(4);
|
||||||
channels.allChannels([](GwChannel *c){
|
channels.allChannels([](GwChannel *c){
|
||||||
c->loop(true,false);
|
c->loop(true,false);
|
||||||
});
|
});
|
||||||
//reads
|
//reads
|
||||||
monitor.setTime(4);
|
monitor.setTime(5);
|
||||||
channels.allChannels([](GwChannel *c){
|
channels.allChannels([](GwChannel *c){
|
||||||
c->loop(false,true);
|
c->loop(false,true);
|
||||||
});
|
});
|
||||||
//writes
|
//writes
|
||||||
monitor.setTime(5);
|
|
||||||
NMEA2000.ParseMessages();
|
|
||||||
monitor.setTime(6);
|
monitor.setTime(6);
|
||||||
|
NMEA2000.ParseMessages();
|
||||||
|
monitor.setTime(7);
|
||||||
|
|
||||||
int SourceAddress = NMEA2000.GetN2kSource();
|
int SourceAddress = NMEA2000.GetN2kSource();
|
||||||
if (SourceAddress != NodeAddress) { // Save potentially changed Source Address to NVS memory
|
if (SourceAddress != NodeAddress) { // Save potentially changed Source Address to NVS memory
|
||||||
|
@ -861,7 +947,7 @@ void loop() {
|
||||||
logger.logDebug(GwLog::LOG,"Address Change: New Address=%d\n", SourceAddress);
|
logger.logDebug(GwLog::LOG,"Address Change: New Address=%d\n", SourceAddress);
|
||||||
}
|
}
|
||||||
nmea0183Converter->loop();
|
nmea0183Converter->loop();
|
||||||
monitor.setTime(7);
|
monitor.setTime(8);
|
||||||
|
|
||||||
//read channels
|
//read channels
|
||||||
channels.allChannels([](GwChannel *c){
|
channels.allChannels([](GwChannel *c){
|
||||||
|
@ -888,13 +974,13 @@ void loop() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
monitor.setTime(8);
|
monitor.setTime(9);
|
||||||
channels.allChannels([](GwChannel *c){
|
channels.allChannels([](GwChannel *c){
|
||||||
c->parseActisense([](const tN2kMsg &msg,int source){
|
c->parseActisense([](const tN2kMsg &msg,int source){
|
||||||
handleN2kMessage(msg,source);
|
handleN2kMessage(msg,source);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
monitor.setTime(9);
|
monitor.setTime(10);
|
||||||
|
|
||||||
//handle message requests
|
//handle message requests
|
||||||
GwMessage *msg=mainQueue.fetchMessage(0);
|
GwMessage *msg=mainQueue.fetchMessage(0);
|
||||||
|
@ -902,5 +988,7 @@ void loop() {
|
||||||
msg->process();
|
msg->process();
|
||||||
msg->unref();
|
msg->unref();
|
||||||
}
|
}
|
||||||
monitor.setTime(10);
|
monitor.setTime(11);
|
||||||
|
//logger.logDebug(GwLog::DEBUG,"main loop end");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="esp32"
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="esp32"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1001", SYMLINK+="esp32s3"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,129 @@
|
||||||
|
try:
|
||||||
|
import esptool
|
||||||
|
except:
|
||||||
|
import flashtool.esptool as esptool
|
||||||
|
import os
|
||||||
|
VERSION="2.1"
|
||||||
|
|
||||||
|
class Flasher():
|
||||||
|
def getVersion(self):
|
||||||
|
return ("Version %s, esptool %s"%(VERSION,str(esptool.__version__)))
|
||||||
|
|
||||||
|
UPDATE_ADDR = 0x10000
|
||||||
|
HDROFFSET = 288
|
||||||
|
VERSIONOFFSET = 16
|
||||||
|
NAMEOFFSET = 48
|
||||||
|
IDOFFSET=12 #2 byte chipid
|
||||||
|
MINSIZE = HDROFFSET + NAMEOFFSET + 32
|
||||||
|
CHECKBYTES = {
|
||||||
|
288: 0x32, # app header magic
|
||||||
|
289: 0x54,
|
||||||
|
290: 0xcd,
|
||||||
|
291: 0xab
|
||||||
|
}
|
||||||
|
#flash addresses for full images based on chip id
|
||||||
|
FLASH_ADDR={
|
||||||
|
0: 0x1000,
|
||||||
|
9: 0
|
||||||
|
}
|
||||||
|
def getString(self,buffer, offset, len):
|
||||||
|
return buffer[offset:offset + len].rstrip(b'\0').decode('utf-8')
|
||||||
|
def getFirmwareInfo(self,filename,isFull):
|
||||||
|
with open(filename,"rb") as ih:
|
||||||
|
buffer = ih.read(self.MINSIZE)
|
||||||
|
if len(buffer) != self.MINSIZE:
|
||||||
|
return self.setErr("invalid image file %s, to short"%filename)
|
||||||
|
if buffer[0] != 0xe9:
|
||||||
|
return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%buffer[0])
|
||||||
|
chipid= buffer[self.IDOFFSET]+256*buffer[self.IDOFFSET+1]
|
||||||
|
flashoffset=self.FLASH_ADDR.get(chipid)
|
||||||
|
if flashoffset is None:
|
||||||
|
return self.setErr("unknown chip id in image %d",chipid);
|
||||||
|
if isFull:
|
||||||
|
offset=self.UPDATE_ADDR-flashoffset;
|
||||||
|
offset-=self.MINSIZE
|
||||||
|
ih.seek(offset,os.SEEK_CUR)
|
||||||
|
buffer=ih.read(self.MINSIZE)
|
||||||
|
if len(buffer) != self.MINSIZE:
|
||||||
|
return self.setErr("invalid image file %s, to short"%filename)
|
||||||
|
if buffer[0] != 0xe9:
|
||||||
|
return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%buffer[0])
|
||||||
|
for k, v in self.CHECKBYTES.items():
|
||||||
|
if buffer[k] != v:
|
||||||
|
return self.setErr("invalid magic at %d, expected %d got %d"
|
||||||
|
% (k+offset, v, buffer[k]))
|
||||||
|
name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32)
|
||||||
|
version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32)
|
||||||
|
chipid= buffer[self.IDOFFSET]+256*buffer[self.IDOFFSET+1]
|
||||||
|
flashoffset=flashoffset if isFull else self.UPDATE_ADDR
|
||||||
|
return {
|
||||||
|
'error':False,
|
||||||
|
'info':"%s:%s"%(name,version),
|
||||||
|
'chipid':chipid,
|
||||||
|
'flashbase':flashoffset
|
||||||
|
}
|
||||||
|
def setErr(self,err):
|
||||||
|
return {'error':True,'info':err}
|
||||||
|
def checkImageFile(self,filename,isFull):
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
return self.setErr("file %s not found"%filename)
|
||||||
|
return self.getFirmwareInfo(filename,isFull)
|
||||||
|
|
||||||
|
def checkSettings(self,port,fileName,isFull):
|
||||||
|
if port is None:
|
||||||
|
print("ERROR: no com port selected")
|
||||||
|
return
|
||||||
|
if fileName is None or fileName == '':
|
||||||
|
print("ERROR: no filename selected")
|
||||||
|
return
|
||||||
|
info = self.checkImageFile(fileName, isFull)
|
||||||
|
if info['error']:
|
||||||
|
print("ERROR: %s" % info['info'])
|
||||||
|
return
|
||||||
|
return {'fileName': fileName,'port':port,'isFull':isFull,'info':info}
|
||||||
|
def runEspTool(self,command):
|
||||||
|
print("run esptool: %s" % " ".join(command))
|
||||||
|
try:
|
||||||
|
esptool.main(command)
|
||||||
|
print("esptool done")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print("Exception in esptool %s" % e)
|
||||||
|
def verifyChip(self,param):
|
||||||
|
if not param:
|
||||||
|
print("check failed")
|
||||||
|
return
|
||||||
|
imageChipId=param['info']['chipid']
|
||||||
|
try:
|
||||||
|
chip=esptool.ESPLoader.detect_chip(param['port'],trace_enabled=True)
|
||||||
|
print("Detected chip %s, id=%d"%(chip.CHIP_NAME,chip.IMAGE_CHIP_ID))
|
||||||
|
if (chip.IMAGE_CHIP_ID != imageChipId):
|
||||||
|
print("##Error: chip id in image %d does not match detected chip"%imageChipId)
|
||||||
|
return
|
||||||
|
print("Checks OK")
|
||||||
|
param['chipname']=chip.CHIP_NAME
|
||||||
|
except Exception as e:
|
||||||
|
print("ERROR: ",str(e))
|
||||||
|
return param
|
||||||
|
def runCheck(self,port,fileName,isFull):
|
||||||
|
param = self.checkSettings(port,fileName,isFull)
|
||||||
|
if not param:
|
||||||
|
return
|
||||||
|
print("Settings OK")
|
||||||
|
param=self.verifyChip(param)
|
||||||
|
if not param:
|
||||||
|
print("Check Failed")
|
||||||
|
return
|
||||||
|
print("flashbase=0x%x"%param['info']['flashbase'])
|
||||||
|
return param
|
||||||
|
def runFlash(self,param):
|
||||||
|
if not param:
|
||||||
|
return
|
||||||
|
if param['isFull']:
|
||||||
|
command=['--chip',param['chipname'],'--port',param['port'],'write_flash',str(param['info']['flashbase']),param['fileName']]
|
||||||
|
self.runEspTool(command)
|
||||||
|
else:
|
||||||
|
command=['--chip',param['chipname'],'--port',param['port'],'erase_region','0xe000','0x2000']
|
||||||
|
self.runEspTool(command)
|
||||||
|
command = ['--chip', param['chipname'], '--port', param['port'], 'write_flash', str(param['info']['flashbase']), param['fileName']]
|
||||||
|
self.runEspTool(command)
|
|
@ -1,6 +1,39 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
|
import builtins
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
import importlib.abc
|
||||||
|
import importlib.util
|
||||||
|
import types
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Inject a base package for our current directory
|
||||||
|
'''
|
||||||
|
class MyLoader(importlib.abc.InspectLoader):
|
||||||
|
def is_package(self, fullname: str) -> bool:
|
||||||
|
return True
|
||||||
|
def get_source(self, fullname: str):
|
||||||
|
return None
|
||||||
|
def get_code(self, fullname: str):
|
||||||
|
return ""
|
||||||
|
class MyFinder(importlib.abc.MetaPathFinder):
|
||||||
|
def __init__(self,baspkg,basedir=os.path.dirname(__file__),debug=False):
|
||||||
|
self.pkg=baspkg
|
||||||
|
self.dir=basedir
|
||||||
|
self.debug=debug
|
||||||
|
def find_spec(self,fullname, path, target=None):
|
||||||
|
if self.debug:
|
||||||
|
print("F:fullname=%s"%fullname)
|
||||||
|
if fullname == self.pkg:
|
||||||
|
if self.debug:
|
||||||
|
print("F:matching %s(%s)"%(fullname,self.dir))
|
||||||
|
spec=importlib.util.spec_from_file_location(fullname, self.dir,loader=MyLoader(), submodule_search_locations=[self.dir])
|
||||||
|
if self.debug:
|
||||||
|
print("F:injecting:",spec)
|
||||||
|
return spec
|
||||||
|
sys.meta_path.insert(0,MyFinder('flashtool'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import serial
|
import serial
|
||||||
|
@ -16,11 +49,13 @@ import tkinter.font as tkFont
|
||||||
import os
|
import os
|
||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
from tkinter import filedialog as FileDialog
|
from tkinter import filedialog as FileDialog
|
||||||
|
try:
|
||||||
|
from flasher import Flasher
|
||||||
|
except:
|
||||||
|
from flashtool.flasher import Flasher
|
||||||
|
|
||||||
import builtins
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
VERSION="Version 1.1, esptool 3.2"
|
|
||||||
|
|
||||||
oldprint=builtins.print
|
oldprint=builtins.print
|
||||||
def print(*args, **kwargs):
|
def print(*args, **kwargs):
|
||||||
|
@ -32,6 +67,7 @@ def main():
|
||||||
|
|
||||||
class App:
|
class App:
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
|
self.flasher=Flasher()
|
||||||
root.title("ESP32 NMEA2000 Flash Tool")
|
root.title("ESP32 NMEA2000 Flash Tool")
|
||||||
root.geometry("800x600")
|
root.geometry("800x600")
|
||||||
root.resizable(width=True, height=True)
|
root.resizable(width=True, height=True)
|
||||||
|
@ -47,7 +83,7 @@ def main():
|
||||||
frame.columnconfigure(1, weight=3)
|
frame.columnconfigure(1, weight=3)
|
||||||
tk.Label(frame,text="ESP32 NMEA2000 Flash Tool").grid(row=row,column=0,columnspan=2,sticky='ew')
|
tk.Label(frame,text="ESP32 NMEA2000 Flash Tool").grid(row=row,column=0,columnspan=2,sticky='ew')
|
||||||
row+=1
|
row+=1
|
||||||
tk.Label(frame, text=VERSION).grid(row=row,column=0,columnspan=2,sticky="ew",pady=10)
|
tk.Label(frame, text=self.flasher.getVersion()).grid(row=row,column=0,columnspan=2,sticky="ew",pady=10)
|
||||||
row+=1
|
row+=1
|
||||||
self.mode=tk.IntVar()
|
self.mode=tk.IntVar()
|
||||||
self.mode.set(1)
|
self.mode.set(1)
|
||||||
|
@ -72,7 +108,7 @@ def main():
|
||||||
tk.Label(frame,textvariable=self.fileInfo).grid(row=row,column=0,columnspan=2,sticky="ew")
|
tk.Label(frame,textvariable=self.fileInfo).grid(row=row,column=0,columnspan=2,sticky="ew")
|
||||||
row+=1
|
row+=1
|
||||||
self.flashInfo=tk.StringVar()
|
self.flashInfo=tk.StringVar()
|
||||||
self.flashInfo.set("Address 0x1000")
|
self.flashInfo.set("Full Flash")
|
||||||
tk.Label(frame,textvariable=self.flashInfo).grid(row=row,column=0,columnspan=2,sticky='ew',pady=10)
|
tk.Label(frame,textvariable=self.flashInfo).grid(row=row,column=0,columnspan=2,sticky='ew',pady=10)
|
||||||
row+=1
|
row+=1
|
||||||
btFrame=tk.Frame(frame)
|
btFrame=tk.Frame(frame)
|
||||||
|
@ -96,7 +132,7 @@ def main():
|
||||||
def updateFlashInfo(self):
|
def updateFlashInfo(self):
|
||||||
if self.mode.get() == 1:
|
if self.mode.get() == 1:
|
||||||
#full
|
#full
|
||||||
self.flashInfo.set("Address 0x1000")
|
self.flashInfo.set("Full Flash")
|
||||||
else:
|
else:
|
||||||
self.flashInfo.set("Erase(otadata): 0xe000...0xffff, Address 0x10000")
|
self.flashInfo.set("Erase(otadata): 0xe000...0xffff, Address 0x10000")
|
||||||
def changeMode(self):
|
def changeMode(self):
|
||||||
|
@ -108,7 +144,7 @@ def main():
|
||||||
fn=FileDialog.askopenfilename()
|
fn=FileDialog.askopenfilename()
|
||||||
if fn:
|
if fn:
|
||||||
self.filename.set(fn)
|
self.filename.set(fn)
|
||||||
info=self.checkImageFile(fn,self.mode.get() == 1)
|
info=self.flasher.checkImageFile(fn,self.mode.get() == 1)
|
||||||
if info['error']:
|
if info['error']:
|
||||||
self.fileInfo.set("***ERROR: %s"%info['info'])
|
self.fileInfo.set("***ERROR: %s"%info['info'])
|
||||||
else:
|
else:
|
||||||
|
@ -141,51 +177,6 @@ def main():
|
||||||
self.interrupt=False
|
self.interrupt=False
|
||||||
raise Exception("User cancel")
|
raise Exception("User cancel")
|
||||||
|
|
||||||
FULLOFFSET=61440
|
|
||||||
HDROFFSET = 288
|
|
||||||
VERSIONOFFSET = 16
|
|
||||||
NAMEOFFSET = 48
|
|
||||||
MINSIZE = HDROFFSET + NAMEOFFSET + 32
|
|
||||||
CHECKBYTES = {
|
|
||||||
0: 0xe9, # image magic
|
|
||||||
288: 0x32, # app header magic
|
|
||||||
289: 0x54,
|
|
||||||
290: 0xcd,
|
|
||||||
291: 0xab
|
|
||||||
}
|
|
||||||
|
|
||||||
def getString(self,buffer, offset, len):
|
|
||||||
return buffer[offset:offset + len].rstrip(b'\0').decode('utf-8')
|
|
||||||
|
|
||||||
def getFirmwareInfo(self,ih,imageFile,offset):
|
|
||||||
buffer = ih.read(self.MINSIZE)
|
|
||||||
if len(buffer) != self.MINSIZE:
|
|
||||||
return self.setErr("invalid image file %s, to short"%imageFile)
|
|
||||||
for k, v in self.CHECKBYTES.items():
|
|
||||||
if buffer[k] != v:
|
|
||||||
return self.setErr("invalid magic at %d, expected %d got %d"
|
|
||||||
% (k+offset, v, buffer[k]))
|
|
||||||
name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32)
|
|
||||||
version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32)
|
|
||||||
return {'error':False,'info':"%s:%s"%(name,version)}
|
|
||||||
|
|
||||||
def setErr(self,err):
|
|
||||||
return {'error':True,'info':err}
|
|
||||||
def checkImageFile(self,filename,isFull):
|
|
||||||
if not os.path.exists(filename):
|
|
||||||
return self.setErr("file %s not found"%filename)
|
|
||||||
with open(filename,"rb") as fh:
|
|
||||||
offset=0
|
|
||||||
if isFull:
|
|
||||||
b=fh.read(1)
|
|
||||||
if len(b) != 1:
|
|
||||||
return self.setErr("unable to read header")
|
|
||||||
if b[0] != 0xe9:
|
|
||||||
return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%b[0])
|
|
||||||
st=fh.seek(self.FULLOFFSET)
|
|
||||||
offset=self.FULLOFFSET
|
|
||||||
return self.getFirmwareInfo(fh,filename,offset)
|
|
||||||
|
|
||||||
def runCheck(self):
|
def runCheck(self):
|
||||||
self.text_widget.delete("1.0", "end")
|
self.text_widget.delete("1.0", "end")
|
||||||
idx = self.port.current()
|
idx = self.port.current()
|
||||||
|
@ -195,51 +186,30 @@ def main():
|
||||||
return
|
return
|
||||||
port = self.serialDevices[idx]
|
port = self.serialDevices[idx]
|
||||||
fn = self.filename.get()
|
fn = self.filename.get()
|
||||||
if fn is None or fn == '':
|
param = self.flasher.runCheck(port,fn,isFull)
|
||||||
self.addText("ERROR: no filename selected")
|
return param
|
||||||
return
|
|
||||||
info = self.checkImageFile(fn, isFull)
|
|
||||||
if info['error']:
|
|
||||||
print("ERROR: %s" % info['info'])
|
|
||||||
return
|
|
||||||
return {'port':port,'isFull':isFull}
|
|
||||||
|
|
||||||
def runEspTool(self,command):
|
def runFlash(self,param):
|
||||||
for b in self.actionButtons:
|
for b in self.actionButtons:
|
||||||
b.configure(state=tk.DISABLED)
|
b.configure(state=tk.DISABLED)
|
||||||
self.cancelButton.configure(state=tk.NORMAL)
|
self.cancelButton.configure(state=tk.NORMAL)
|
||||||
print("run esptool: %s" % " ".join(command))
|
|
||||||
root.update()
|
root.update()
|
||||||
root.update_idletasks()
|
root.update_idletasks()
|
||||||
try:
|
self.flasher.runFlash(param)
|
||||||
esptool.main(command)
|
|
||||||
print("esptool done")
|
|
||||||
except Exception as e:
|
|
||||||
print("Exception in esptool %s" % e)
|
|
||||||
for b in self.actionButtons:
|
for b in self.actionButtons:
|
||||||
b.configure(state=tk.NORMAL)
|
b.configure(state=tk.NORMAL)
|
||||||
self.cancelButton.configure(state=tk.DISABLED)
|
self.cancelButton.configure(state=tk.DISABLED)
|
||||||
|
|
||||||
def buttonCheck(self):
|
def buttonCheck(self):
|
||||||
param = self.runCheck()
|
param = self.runCheck()
|
||||||
if not param:
|
|
||||||
return
|
|
||||||
print("Settings OK")
|
|
||||||
command = ['--chip', 'ESP32', '--port', param['port'], 'chip_id']
|
|
||||||
self.runEspTool(command)
|
|
||||||
|
|
||||||
def buttonFlash(self):
|
def buttonFlash(self):
|
||||||
param=self.runCheck()
|
param=self.runCheck()
|
||||||
if not param:
|
if not param:
|
||||||
return
|
return
|
||||||
if param['isFull']:
|
self.runFlash(param)
|
||||||
command=['--chip','ESP32','--port',param['port'],'write_flash','0x1000',self.filename.get()]
|
|
||||||
self.runEspTool(command)
|
|
||||||
else:
|
|
||||||
command=['--chip','ESP32','--port',param['port'],'erase_region','0xe000','0x2000']
|
|
||||||
self.runEspTool(command)
|
|
||||||
command = ['--chip', 'ESP32', '--port', param['port'], 'write_flash', '0x10000', self.filename.get()]
|
|
||||||
self.runEspTool(command)
|
|
||||||
|
|
||||||
|
|
||||||
def buttonCancel(self):
|
def buttonCancel(self):
|
||||||
self.interrupt=True
|
self.interrupt=True
|
||||||
|
|
148
web/config.json
148
web/config.json
|
@ -119,6 +119,22 @@
|
||||||
"category": "system",
|
"category": "system",
|
||||||
"capabilities":{"apPwChange":["true"]}
|
"capabilities":{"apPwChange":["true"]}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "apIp",
|
||||||
|
"type": "string",
|
||||||
|
"default":"192.168.15.1",
|
||||||
|
"check": "checkApIp",
|
||||||
|
"description": "The IP address for the access point. Clients will get addresses within the same subnet.",
|
||||||
|
"category":"system"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apMask",
|
||||||
|
"type": "string",
|
||||||
|
"default":"255.255.255.0",
|
||||||
|
"check": "checkNetMask",
|
||||||
|
"description": "The net mask for the access point",
|
||||||
|
"category":"system"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "useAdminPass",
|
"name": "useAdminPass",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
@ -156,6 +172,16 @@
|
||||||
"description": "log level at the USB port",
|
"description": "log level at the USB port",
|
||||||
"category":"system"
|
"category":"system"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"ledBrightness",
|
||||||
|
"label":"led brightness",
|
||||||
|
"type":"number",
|
||||||
|
"default":64,
|
||||||
|
"min":0,
|
||||||
|
"max":255,
|
||||||
|
"description":"the brightness of the led (0..255)",
|
||||||
|
"category":"system"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "minXdrInterval",
|
"name": "minXdrInterval",
|
||||||
"label":"min XDR interval",
|
"label":"min XDR interval",
|
||||||
|
@ -392,6 +418,128 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"category": "serial port"
|
"category": "serial port"
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"name": "serial2Dir",
|
||||||
|
"label": "serial2 direction",
|
||||||
|
"type": "list",
|
||||||
|
"default": "receive",
|
||||||
|
"list": [
|
||||||
|
"send",
|
||||||
|
"receive",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"description": "use the serial2 port to send or receive data",
|
||||||
|
"capabilities": {
|
||||||
|
"serial2mode": [
|
||||||
|
"UNI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "serial2 port"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "serial2Baud",
|
||||||
|
"label": "serial2 baud rate",
|
||||||
|
"type": "list",
|
||||||
|
"default": "115200",
|
||||||
|
"description": "baud rate for the serial port 2",
|
||||||
|
"list": [
|
||||||
|
1200,
|
||||||
|
2400,
|
||||||
|
4800,
|
||||||
|
9600,
|
||||||
|
14400,
|
||||||
|
19200,
|
||||||
|
28800,
|
||||||
|
38400,
|
||||||
|
57600,
|
||||||
|
115200,
|
||||||
|
230400,
|
||||||
|
460800
|
||||||
|
],
|
||||||
|
"capabilities": {
|
||||||
|
"serial2mode": [
|
||||||
|
"RX",
|
||||||
|
"TX",
|
||||||
|
"UNI",
|
||||||
|
"BI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "serial2 port"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sendSerial2",
|
||||||
|
"label": "NMEA to Serial2",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"description": "send out NMEA data on the serial port 2",
|
||||||
|
"capabilities": {
|
||||||
|
"serial2mode": [
|
||||||
|
"TX",
|
||||||
|
"BI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "serial2 port"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "receiveSerial2",
|
||||||
|
"label": "NMEA from Serial2",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"description": "receive NMEA data on the serial port 2",
|
||||||
|
"capabilities": {
|
||||||
|
"serial2mode": [
|
||||||
|
"RX",
|
||||||
|
"BI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "serial2 port"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "serial2ToN2k",
|
||||||
|
"label": "serial2 to NMEA2000",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true",
|
||||||
|
"description": "convert NMEA0183 from the serial port 2 to NMEA2000",
|
||||||
|
"capabilities": {
|
||||||
|
"serial2mode": [
|
||||||
|
"RX",
|
||||||
|
"BI",
|
||||||
|
"UNI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "serial2 port"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "serial2ReadF",
|
||||||
|
"label": "serial2 read Filter",
|
||||||
|
"type": "filter",
|
||||||
|
"default": "",
|
||||||
|
"description": "filter for NMEA0183 data when reading from serial2\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB",
|
||||||
|
"capabilities": {
|
||||||
|
"serial2mode": [
|
||||||
|
"RX",
|
||||||
|
"BI",
|
||||||
|
"UNI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "serial2 port"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "serial2WriteF",
|
||||||
|
"label": "serial2 write Filter",
|
||||||
|
"type": "filter",
|
||||||
|
"default": "",
|
||||||
|
"description": "filter for NMEA0183 data when writing to serial2\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB",
|
||||||
|
"capabilities": {
|
||||||
|
"serial2mode": [
|
||||||
|
"TX",
|
||||||
|
"BI",
|
||||||
|
"UNI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"category": "serial2 port"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "serverPort",
|
"name": "serverPort",
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
<span class="label"># clients</span>
|
<span class="label"># clients</span>
|
||||||
<span class="value" id="numClients">---</span>
|
<span class="value" id="numClients">---</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row even">
|
||||||
<span class="label">TCP client connected</span>
|
<span class="label">TCP client connected</span>
|
||||||
<span class="value" id="clientCon">---</span>
|
<span class="value" id="clientCon">---</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,10 +55,15 @@
|
||||||
<span class="label">TCP client error</span>
|
<span class="label">TCP client error</span>
|
||||||
<span class="value" id="clientErr">---</span>
|
<span class="value" id="clientErr">---</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row even">
|
||||||
<span class="label">Free heap</span>
|
<span class="label">Free heap</span>
|
||||||
<span class="value" id="heap">---</span>
|
<span class="value" id="heap">---</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">NMEA2000 State</span>
|
||||||
|
[<span class="value" id="n2knode">---</span>]
|
||||||
|
<span class="value" id="n2kstate">UNKNOWN</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="reset">Reset</button>
|
<button id="reset">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,6 +100,10 @@
|
||||||
<span class="label">firmware type</span>
|
<span class="label">firmware type</span>
|
||||||
<span class="value status-fwtype">---</span>
|
<span class="value status-fwtype">---</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">chip type</span>
|
||||||
|
<span class="value status-chiptype">---</span>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="label">currentVersion</span>
|
<span class="label">currentVersion</span>
|
||||||
<span class="value status-version">---</span>
|
<span class="value status-version">---</span>
|
||||||
|
|
209
web/index.js
209
web/index.js
|
@ -3,6 +3,8 @@ let lastUpdate = (new Date()).getTime();
|
||||||
let reloadConfig = false;
|
let reloadConfig = false;
|
||||||
let needAdminPass=true;
|
let needAdminPass=true;
|
||||||
let lastSalt="";
|
let lastSalt="";
|
||||||
|
let channelList={};
|
||||||
|
let minUser=200;
|
||||||
function addEl(type, clazz, parent, text) {
|
function addEl(type, clazz, parent, text) {
|
||||||
let el = document.createElement(type);
|
let el = document.createElement(type);
|
||||||
if (clazz) {
|
if (clazz) {
|
||||||
|
@ -65,22 +67,39 @@ function update() {
|
||||||
}
|
}
|
||||||
getJson('/api/status')
|
getJson('/api/status')
|
||||||
.then(function (jsonData) {
|
.then(function (jsonData) {
|
||||||
|
let statusPage=document.getElementById('statusPageContent');
|
||||||
|
let even=true; //first counter
|
||||||
for (let k in jsonData) {
|
for (let k in jsonData) {
|
||||||
if (k == "salt"){
|
if (k == "salt"){
|
||||||
lastSalt=jsonData[k];
|
lastSalt=jsonData[k];
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
if (k == "minUser"){
|
||||||
|
minUser=parseInt(jsonData[k]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! statusPage) continue;
|
||||||
if (typeof (jsonData[k]) === 'object') {
|
if (typeof (jsonData[k]) === 'object') {
|
||||||
for (let sk in jsonData[k]) {
|
if (k.indexOf('count') == 0) {
|
||||||
let key = k + "." + sk;
|
createCounterDisplay(statusPage, k.replace("count", "").replace(/in$/," in").replace(/out$/," out"), k, even);
|
||||||
if (typeof (jsonData[k][sk]) === 'object') {
|
even = !even;
|
||||||
//msg details
|
for (let sk in jsonData[k]) {
|
||||||
updateMsgDetails(key, jsonData[k][sk]);
|
let key = k + "." + sk;
|
||||||
}
|
if (typeof (jsonData[k][sk]) === 'object') {
|
||||||
else {
|
//msg details
|
||||||
let el = document.getElementById(key);
|
updateMsgDetails(key, jsonData[k][sk]);
|
||||||
if (el) el.textContent = jsonData[k][sk];
|
}
|
||||||
|
else {
|
||||||
|
let el = document.getElementById(key);
|
||||||
|
if (el) el.textContent = jsonData[k][sk];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (k.indexOf("ch")==0){
|
||||||
|
//channel def
|
||||||
|
let name=k.substring(2);
|
||||||
|
channelList[name]=jsonData[k];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let el = document.getElementById(k);
|
let el = document.getElementById(k);
|
||||||
|
@ -167,6 +186,21 @@ function checkAdminPass(v){
|
||||||
return checkApPass(v);
|
return checkApPass(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkApIp(v,allValues){
|
||||||
|
if (! v) return "cannot be empty";
|
||||||
|
let err1="must be in the form 192.168.x.x";
|
||||||
|
if (! v.match(/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/))return err1;
|
||||||
|
let parts=v.split(".");
|
||||||
|
if (parts.length != 4) return err1;
|
||||||
|
for (let idx=0;idx < 4;idx++){
|
||||||
|
let iv=parseInt(parts[idx]);
|
||||||
|
if (iv < 0 || iv > 255) return err1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function checkNetMask(v,allValues){
|
||||||
|
return checkApIp(v,allValues);
|
||||||
|
}
|
||||||
|
|
||||||
function checkIpAddress(v,allValues,def){
|
function checkIpAddress(v,allValues,def){
|
||||||
if (allValues.tclEnabled != "true") return;
|
if (allValues.tclEnabled != "true") return;
|
||||||
if (! v) return "cannot be empty";
|
if (! v) return "cannot be empty";
|
||||||
|
@ -212,6 +246,7 @@ function getAllConfigs(omitPass) {
|
||||||
let name = v.getAttribute('name');
|
let name = v.getAttribute('name');
|
||||||
if (!name) continue;
|
if (!name) continue;
|
||||||
if (name.indexOf("_") >= 0) continue;
|
if (name.indexOf("_") >= 0) continue;
|
||||||
|
if (v.getAttribute('disabled')) continue;
|
||||||
let def = getConfigDefition(name);
|
let def = getConfigDefition(name);
|
||||||
if (def.type === 'password' && ( v.value == '' || omitPass)) {
|
if (def.type === 'password' && ( v.value == '' || omitPass)) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -286,9 +321,13 @@ function factoryReset() {
|
||||||
.catch(function (e) { });
|
.catch(function (e) { });
|
||||||
}
|
}
|
||||||
function createCounterDisplay(parent,label,key,isEven){
|
function createCounterDisplay(parent,label,key,isEven){
|
||||||
|
if (parent.querySelector("#"+key)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
let clazz="row icon-row counter-row";
|
let clazz="row icon-row counter-row";
|
||||||
if (isEven) clazz+=" even";
|
if (isEven) clazz+=" even";
|
||||||
let row=addEl('div',clazz,parent);
|
let row=addEl('div',clazz,parent);
|
||||||
|
row.setAttribute("id",key);
|
||||||
let icon=addEl('span','icon icon-more',row);
|
let icon=addEl('span','icon icon-more',row);
|
||||||
addEl('span','label',row,label);
|
addEl('span','label',row,label);
|
||||||
let value=addEl('span','value',row,'---');
|
let value=addEl('span','value',row,'---');
|
||||||
|
@ -331,18 +370,7 @@ function updateMsgDetails(key, details) {
|
||||||
},frame);
|
},frame);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let counters={
|
|
||||||
count2Kin: 'NMEA2000 in',
|
|
||||||
count2Kout: 'NMEA2000 out',
|
|
||||||
countTCPin: 'TCPserver in',
|
|
||||||
countTCPout: 'TCPserver out',
|
|
||||||
countTCPClientin: 'TCPclient in',
|
|
||||||
countTCPClientout: 'TCPclient out',
|
|
||||||
countUSBin: 'USB in',
|
|
||||||
countUSBout: 'USB out',
|
|
||||||
countSERin: 'Serial in',
|
|
||||||
countSERout: 'Serial out'
|
|
||||||
}
|
|
||||||
function showOverlay(text, isHtml) {
|
function showOverlay(text, isHtml) {
|
||||||
let el = document.getElementById('overlayContent');
|
let el = document.getElementById('overlayContent');
|
||||||
if (isHtml) {
|
if (isHtml) {
|
||||||
|
@ -432,6 +460,7 @@ function createInput(configItem, frame,clazz) {
|
||||||
let el;
|
let el;
|
||||||
if (configItem.type === 'boolean' || configItem.type === 'list' || configItem.type == 'boatData') {
|
if (configItem.type === 'boolean' || configItem.type === 'list' || configItem.type == 'boatData') {
|
||||||
el=addEl('select',clazz,frame);
|
el=addEl('select',clazz,frame);
|
||||||
|
if (configItem.readOnly) el.setAttribute('disabled',true);
|
||||||
el.setAttribute('name', configItem.name)
|
el.setAttribute('name', configItem.name)
|
||||||
let slist = [];
|
let slist = [];
|
||||||
if (configItem.list) {
|
if (configItem.list) {
|
||||||
|
@ -464,6 +493,7 @@ function createInput(configItem, frame,clazz) {
|
||||||
return createXdrInput(configItem,frame,clazz);
|
return createXdrInput(configItem,frame,clazz);
|
||||||
}
|
}
|
||||||
el = addEl('input',clazz,frame);
|
el = addEl('input',clazz,frame);
|
||||||
|
if (configItem.readOnly) el.setAttribute('disabled',true);
|
||||||
el.setAttribute('name', configItem.name)
|
el.setAttribute('name', configItem.name)
|
||||||
if (configItem.type === 'password') {
|
if (configItem.type === 'password') {
|
||||||
el.setAttribute('type', 'password');
|
el.setAttribute('type', 'password');
|
||||||
|
@ -579,25 +609,29 @@ function createXdrInput(configItem,frame){
|
||||||
{l:'bidir',v:1},
|
{l:'bidir',v:1},
|
||||||
{l:'to2K',v:2},
|
{l:'to2K',v:2},
|
||||||
{l:'from2K',v:3}
|
{l:'from2K',v:3}
|
||||||
]
|
],
|
||||||
|
readOnly: configItem.readOnly
|
||||||
},d,'xdrdir');
|
},d,'xdrdir');
|
||||||
d=createXdrLine(el,'Category');
|
d=createXdrLine(el,'Category');
|
||||||
let category=createInput({
|
let category=createInput({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: configItem.name+"_cat",
|
name: configItem.name+"_cat",
|
||||||
list:getXdrCategories()
|
list:getXdrCategories(),
|
||||||
|
readOnly: configItem.readOnly
|
||||||
},d,'xdrcat');
|
},d,'xdrcat');
|
||||||
d=createXdrLine(el,'Source');
|
d=createXdrLine(el,'Source');
|
||||||
let selector=createInput({
|
let selector=createInput({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: configItem.name+"_sel",
|
name: configItem.name+"_sel",
|
||||||
list:[]
|
list:[],
|
||||||
|
readOnly: configItem.readOnly
|
||||||
},d,'xdrsel');
|
},d,'xdrsel');
|
||||||
d=createXdrLine(el,'Field');
|
d=createXdrLine(el,'Field');
|
||||||
let field=createInput({
|
let field=createInput({
|
||||||
type:'list',
|
type:'list',
|
||||||
name: configItem.name+'_field',
|
name: configItem.name+'_field',
|
||||||
list: []
|
list: [],
|
||||||
|
readOnly: configItem.readOnly
|
||||||
},d,'xdrfield');
|
},d,'xdrfield');
|
||||||
d=createXdrLine(el,'Instance');
|
d=createXdrLine(el,'Instance');
|
||||||
let imode=createInput({
|
let imode=createInput({
|
||||||
|
@ -608,22 +642,26 @@ function createXdrInput(configItem,frame){
|
||||||
{l:'single',v:0},
|
{l:'single',v:0},
|
||||||
{l:'ignore',v:1},
|
{l:'ignore',v:1},
|
||||||
{l:'auto',v:2}
|
{l:'auto',v:2}
|
||||||
]
|
],
|
||||||
|
readOnly: configItem.readOnly
|
||||||
},d,'xdrimode');
|
},d,'xdrimode');
|
||||||
let instance=createInput({
|
let instance=createInput({
|
||||||
type:'number',
|
type:'number',
|
||||||
name: configItem.name+"_instance",
|
name: configItem.name+"_instance",
|
||||||
|
readOnly: configItem.readOnly
|
||||||
},d,'xdrinstance');
|
},d,'xdrinstance');
|
||||||
d=createXdrLine(el,'Transducer');
|
d=createXdrLine(el,'Transducer');
|
||||||
let xdrName=createInput({
|
let xdrName=createInput({
|
||||||
type:'text',
|
type:'text',
|
||||||
name: configItem.name+"_xdr"
|
name: configItem.name+"_xdr",
|
||||||
|
readOnly: configItem.readOnly
|
||||||
},d,'xdrname');
|
},d,'xdrname');
|
||||||
d=createXdrLine(el,'Example');
|
d=createXdrLine(el,'Example');
|
||||||
let example=addEl('div','xdrexample',d,'');
|
let example=addEl('div','xdrexample',d,'');
|
||||||
let data = addEl('input','xdrvalue',el);
|
let data = addEl('input','xdrvalue',el);
|
||||||
data.setAttribute('type', 'hidden');
|
data.setAttribute('type', 'hidden');
|
||||||
data.setAttribute('name', configItem.name);
|
data.setAttribute('name', configItem.name);
|
||||||
|
if (configItem.readOnly) data.setAttribute('disabled',true);
|
||||||
let changeFunction = function () {
|
let changeFunction = function () {
|
||||||
let parts=data.value.split(',');
|
let parts=data.value.split(',');
|
||||||
direction.value=parts[1] || 0;
|
direction.value=parts[1] || 0;
|
||||||
|
@ -718,16 +756,19 @@ function createFilterInput(configItem, frame) {
|
||||||
let ais = createInput({
|
let ais = createInput({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: configItem.name + "_ais",
|
name: configItem.name + "_ais",
|
||||||
list: ['aison', 'aisoff']
|
list: ['aison', 'aisoff'],
|
||||||
|
readOnly: configItem.readOnly
|
||||||
}, el);
|
}, el);
|
||||||
let mode = createInput({
|
let mode = createInput({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: configItem.name + "_mode",
|
name: configItem.name + "_mode",
|
||||||
list: ['whitelist', 'blacklist']
|
list: ['whitelist', 'blacklist'],
|
||||||
|
readOnly: configItem.readOnly
|
||||||
}, el);
|
}, el);
|
||||||
let sentences = createInput({
|
let sentences = createInput({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: configItem.name + "_sentences",
|
name: configItem.name + "_sentences",
|
||||||
|
readOnly: configItem.readOnly
|
||||||
}, el);
|
}, el);
|
||||||
let data = addEl('input',undefined,el);
|
let data = addEl('input',undefined,el);
|
||||||
data.setAttribute('type', 'hidden');
|
data.setAttribute('type', 'hidden');
|
||||||
|
@ -755,6 +796,7 @@ function createFilterInput(configItem, frame) {
|
||||||
changeFunction();
|
changeFunction();
|
||||||
});
|
});
|
||||||
data.setAttribute('name', configItem.name);
|
data.setAttribute('name', configItem.name);
|
||||||
|
if (configItem.readOnly) data.setAttribute('disabled',true);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
let moreicons=['icon-more','icon-less'];
|
let moreicons=['icon-more','icon-less'];
|
||||||
|
@ -978,9 +1020,7 @@ function toggleClass(el,id,classList){
|
||||||
}
|
}
|
||||||
|
|
||||||
function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
let category;
|
let categories={};
|
||||||
let categoryEl;
|
|
||||||
let categoryFrame;
|
|
||||||
let frame = parent.querySelector('.configFormRows');
|
let frame = parent.querySelector('.configFormRows');
|
||||||
if (!frame) throw Error("no config form");
|
if (!frame) throw Error("no config form");
|
||||||
frame.innerHTML = '';
|
frame.innerHTML = '';
|
||||||
|
@ -994,22 +1034,24 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
else{
|
else{
|
||||||
if(includeXdr) return;
|
if(includeXdr) return;
|
||||||
}
|
}
|
||||||
if (item.category != category || !categoryEl) {
|
let catEntry;
|
||||||
if (categoryFrame && ! currentCategoryPopulated){
|
if (categories[item.category] === undefined){
|
||||||
categoryFrame.remove();
|
catEntry={
|
||||||
|
populated:false,
|
||||||
|
frame: undefined,
|
||||||
|
element: undefined
|
||||||
}
|
}
|
||||||
currentCategoryPopulated=false;
|
categories[item.category]=catEntry
|
||||||
categoryFrame = addEl('div', 'category', frame);
|
catEntry.frame = addEl('div', 'category', frame);
|
||||||
categoryFrame.setAttribute('data-category',item.category)
|
catEntry.frame.setAttribute('data-category',item.category)
|
||||||
let categoryTitle = addEl('div', 'title', categoryFrame);
|
let categoryTitle = addEl('div', 'title', catEntry.frame);
|
||||||
let categoryButton = addEl('span', 'icon icon-more', categoryTitle);
|
let categoryButton = addEl('span', 'icon icon-more', categoryTitle);
|
||||||
addEl('span', 'label', categoryTitle, item.category);
|
addEl('span', 'label', categoryTitle, item.category);
|
||||||
addEl('span','categoryAdd',categoryTitle);
|
addEl('span','categoryAdd',categoryTitle);
|
||||||
categoryEl = addEl('div', 'content', categoryFrame);
|
catEntry.element = addEl('div', 'content', catEntry.frame);
|
||||||
categoryEl.classList.add('hidden');
|
catEntry.element.classList.add('hidden');
|
||||||
let currentEl = categoryEl;
|
|
||||||
categoryTitle.addEventListener('click', function (ev) {
|
categoryTitle.addEventListener('click', function (ev) {
|
||||||
let rs = currentEl.classList.toggle('hidden');
|
let rs = catEntry.element.classList.toggle('hidden');
|
||||||
if (rs) {
|
if (rs) {
|
||||||
toggleClass(categoryButton,0,moreicons);
|
toggleClass(categoryButton,0,moreicons);
|
||||||
}
|
}
|
||||||
|
@ -1017,7 +1059,9 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
toggleClass(categoryButton,1,moreicons);
|
toggleClass(categoryButton,1,moreicons);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
category = item.category;
|
}
|
||||||
|
else{
|
||||||
|
catEntry=categories[item.category];
|
||||||
}
|
}
|
||||||
let showItem=true;
|
let showItem=true;
|
||||||
let itemCapabilities=item.capabilities||{};
|
let itemCapabilities=item.capabilities||{};
|
||||||
|
@ -1036,17 +1080,26 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
});
|
});
|
||||||
if (!found) showItem=false;
|
if (!found) showItem=false;
|
||||||
}
|
}
|
||||||
|
let readOnly=false;
|
||||||
|
let mode=capabilities['CFGMODE'+item.name];
|
||||||
|
if (mode == 1) {
|
||||||
|
//hide
|
||||||
|
showItem=false;
|
||||||
|
}
|
||||||
|
if (mode == 2){
|
||||||
|
readOnly=true;
|
||||||
|
}
|
||||||
if (showItem) {
|
if (showItem) {
|
||||||
currentCategoryPopulated=true;
|
item.readOnly=readOnly;
|
||||||
let row = addEl('div', 'row', categoryEl);
|
catEntry.populated=true;
|
||||||
|
let row = addEl('div', 'row', catEntry.element);
|
||||||
let label = item.label || item.name;
|
let label = item.label || item.name;
|
||||||
addEl('span', 'label', row, label);
|
addEl('span', 'label', row, label);
|
||||||
let valueFrame = addEl('div', 'value', row);
|
let valueFrame = addEl('div', 'value', row);
|
||||||
let valueEl = createInput(item, valueFrame);
|
let valueEl = createInput(item, valueFrame);
|
||||||
if (!valueEl) return;
|
if (!valueEl) return;
|
||||||
valueEl.setAttribute('data-default', item.default);
|
valueEl.setAttribute('data-default', item.default);
|
||||||
valueEl.addEventListener('change', function (ev) {
|
if (! readOnly) valueEl.addEventListener('change', function (ev) {
|
||||||
let el = ev.target;
|
let el = ev.target;
|
||||||
checkChange(el, row, item.name);
|
checkChange(el, row, item.name);
|
||||||
})
|
})
|
||||||
|
@ -1063,13 +1116,15 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
}
|
}
|
||||||
if (item.check) valueEl.setAttribute('data-check', item.check);
|
if (item.check) valueEl.setAttribute('data-check', item.check);
|
||||||
let btContainer = addEl('div', 'buttonContainer', row);
|
let btContainer = addEl('div', 'buttonContainer', row);
|
||||||
let bt = addEl('button', 'defaultButton', btContainer, 'X');
|
if (!readOnly) {
|
||||||
bt.setAttribute('data-default', item.default);
|
let bt = addEl('button', 'defaultButton', btContainer, 'X');
|
||||||
bt.addEventListener('click', function (ev) {
|
bt.setAttribute('data-default', item.default);
|
||||||
valueEl.value = valueEl.getAttribute('data-default');
|
bt.addEventListener('click', function (ev) {
|
||||||
let changeEvent = new Event('change');
|
valueEl.value = valueEl.getAttribute('data-default');
|
||||||
valueEl.dispatchEvent(changeEvent);
|
let changeEvent = new Event('change');
|
||||||
})
|
valueEl.dispatchEvent(changeEvent);
|
||||||
|
})
|
||||||
|
}
|
||||||
bt = addEl('button', 'infoButton', btContainer, '?');
|
bt = addEl('button', 'infoButton', btContainer, '?');
|
||||||
bt.addEventListener('click', function (ev) {
|
bt.addEventListener('click', function (ev) {
|
||||||
if (item.description) {
|
if (item.description) {
|
||||||
|
@ -1083,8 +1138,11 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (categoryFrame && ! currentCategoryPopulated){
|
for (let cat in categories){
|
||||||
categoryFrame.remove();
|
let catEntry=categories[cat];
|
||||||
|
if (! catEntry.populated){
|
||||||
|
catEntry.frame.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function loadConfigDefinitions() {
|
function loadConfigDefinitions() {
|
||||||
|
@ -1448,13 +1506,13 @@ function createDashboard() {
|
||||||
frame.innerHTML = '';
|
frame.innerHTML = '';
|
||||||
}
|
}
|
||||||
function sourceName(v){
|
function sourceName(v){
|
||||||
if (v == 0) return "N2K";
|
for (let n in channelList){
|
||||||
if (v == 1) return "USB";
|
if (v >= channelList[n].id && v <= channelList[n].max){
|
||||||
if (v == 2) return "SER";
|
return n;
|
||||||
if (v == 3) return "TCPcl"
|
}
|
||||||
if (v >= 4 && v <= 20) return "TCPser";
|
}
|
||||||
if (v >= 200) return "USER";
|
if (v < minUser) return "---";
|
||||||
return "---";
|
return "USER["+v+"]";
|
||||||
}
|
}
|
||||||
let lastSelectList=[];
|
let lastSelectList=[];
|
||||||
function updateDashboard(data) {
|
function updateDashboard(data) {
|
||||||
|
@ -1552,9 +1610,15 @@ function uploadBin(ev){
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
let currentType;
|
let currentType;
|
||||||
let currentVersion;
|
let currentVersion;
|
||||||
|
let chipid;
|
||||||
forEl('.status-version', function (el) { currentVersion = el.textContent });
|
forEl('.status-version', function (el) { currentVersion = el.textContent });
|
||||||
forEl('.status-fwtype', function (el) { currentType = el.textContent });
|
forEl('.status-fwtype', function (el) { currentType = el.textContent });
|
||||||
|
forEl('.status-chipid', function (el) { chipid = el.textContent });
|
||||||
let confirmText = 'Ready to update firmware?\n';
|
let confirmText = 'Ready to update firmware?\n';
|
||||||
|
if (result.chipId != chipid){
|
||||||
|
confirmText += "WARNING: the chipid in the image ("+result.chipId;
|
||||||
|
confirmText +=") does not match the current chip id ("+chipid+").\n";
|
||||||
|
}
|
||||||
if (currentType != result.fwtype) {
|
if (currentType != result.fwtype) {
|
||||||
confirmText += "WARNING: image has different type: " + result.fwtype + "\n";
|
confirmText += "WARNING: image has different type: " + result.fwtype + "\n";
|
||||||
confirmText += "** Really update anyway? - device can become unusable **";
|
confirmText += "** Really update anyway? - device can become unusable **";
|
||||||
|
@ -1631,7 +1695,8 @@ function uploadBin(ev){
|
||||||
let HDROFFSET=288;
|
let HDROFFSET=288;
|
||||||
let VERSIONOFFSET=16;
|
let VERSIONOFFSET=16;
|
||||||
let NAMEOFFSET=48;
|
let NAMEOFFSET=48;
|
||||||
let MINSIZE=HDROFFSET+NAMEOFFSET+32;
|
let MINSIZE = HDROFFSET + NAMEOFFSET + 32;
|
||||||
|
let CHIPIDOFFSET=12; //2 bytes chip id here
|
||||||
let imageCheckBytes={
|
let imageCheckBytes={
|
||||||
0: 0xe9, //image magic
|
0: 0xe9, //image magic
|
||||||
288: 0x32, //app header magic
|
288: 0x32, //app header magic
|
||||||
|
@ -1650,6 +1715,10 @@ function decodeFromBuffer(buffer,start,length){
|
||||||
start+length));
|
start+length));
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
function getChipId(buffer){
|
||||||
|
if (buffer.length < CHIPIDOFFSET+2) return -1;
|
||||||
|
return buffer[CHIPIDOFFSET]+256*buffer[CHIPIDOFFSET+1];
|
||||||
|
}
|
||||||
function checkImageFile(file){
|
function checkImageFile(file){
|
||||||
return new Promise(function(resolve,reject){
|
return new Promise(function(resolve,reject){
|
||||||
if (! file) reject("no file");
|
if (! file) reject("no file");
|
||||||
|
@ -1666,9 +1735,11 @@ function checkImageFile(file){
|
||||||
}
|
}
|
||||||
let version=decodeFromBuffer(content,HDROFFSET+VERSIONOFFSET,32);
|
let version=decodeFromBuffer(content,HDROFFSET+VERSIONOFFSET,32);
|
||||||
let fwtype=decodeFromBuffer(content,HDROFFSET+NAMEOFFSET,32);
|
let fwtype=decodeFromBuffer(content,HDROFFSET+NAMEOFFSET,32);
|
||||||
|
let chipId=getChipId(content);
|
||||||
let rt={
|
let rt={
|
||||||
fwtype:fwtype,
|
fwtype:fwtype,
|
||||||
version: version,
|
version: version,
|
||||||
|
chipId:chipId
|
||||||
};
|
};
|
||||||
resolve(rt);
|
resolve(rt);
|
||||||
});
|
});
|
||||||
|
@ -1716,13 +1787,13 @@ window.addEventListener('load', function () {
|
||||||
}
|
}
|
||||||
}catch(e){}
|
}catch(e){}
|
||||||
let statusPage=document.getElementById('statusPageContent');
|
let statusPage=document.getElementById('statusPageContent');
|
||||||
if (statusPage){
|
/*if (statusPage){
|
||||||
let even=true;
|
let even=true;
|
||||||
for (let c in counters){
|
for (let c in counters){
|
||||||
createCounterDisplay(statusPage,counters[c],c,even);
|
createCounterDisplay(statusPage,counters[c],c,even);
|
||||||
even=!even;
|
even=!even;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
forEl('#uploadFile',function(el){
|
forEl('#uploadFile',function(el){
|
||||||
el.addEventListener('change',function(ev){
|
el.addEventListener('change',function(ev){
|
||||||
if (ev.target.files.length < 1) return;
|
if (ev.target.files.length < 1) return;
|
||||||
|
@ -1730,7 +1801,9 @@ window.addEventListener('load', function () {
|
||||||
checkImageFile(file)
|
checkImageFile(file)
|
||||||
.then(function(res){
|
.then(function(res){
|
||||||
forEl('#imageProperties',function(iel){
|
forEl('#imageProperties',function(iel){
|
||||||
iel.textContent=res.fwtype+", "+res.version;
|
let txt="["+res.chipId+"] ";
|
||||||
|
txt+=res.fwtype+", "+res.version;
|
||||||
|
iel.textContent=txt;
|
||||||
iel.classList.remove("error");
|
iel.classList.remove("error");
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,504 @@
|
||||||
|
# structure
|
||||||
|
# below config we define the structure to be displayed
|
||||||
|
# basically there are 2 object types:
|
||||||
|
# children - a list of cfg objects that define the inputs to be shown
|
||||||
|
# if the parent is selected
|
||||||
|
# parameters:
|
||||||
|
# key: unique key - defines the name(part) in the cfg
|
||||||
|
# if not set a potential "value" is taken
|
||||||
|
# null (empty) is a valid key
|
||||||
|
# label: title to be shown, if unset key will be used
|
||||||
|
# resorce: a resource that is used by the value children
|
||||||
|
# simple string use as is
|
||||||
|
# string + ':' - add value to the resource
|
||||||
|
# type: if empty or 'frame' only the children are considered
|
||||||
|
# children: only for type empty or 'frame' - list of child objects
|
||||||
|
# target: how the selected child value should be stored:
|
||||||
|
# environment - set the environment to the child value
|
||||||
|
# define - add -D<childValue> to the flags
|
||||||
|
# define:name - add -D<name>=<childValue> to the flags
|
||||||
|
# values - a list of value objects for a particular config
|
||||||
|
# if the object is just a string it is converted to an object
|
||||||
|
# with value being set to the string
|
||||||
|
# parameters:
|
||||||
|
# key: unique key, defines the name(part) and the value store in cfg
|
||||||
|
# for the parent
|
||||||
|
# if not set, value is used
|
||||||
|
# null (empty) is a valid key
|
||||||
|
# value: the value (mandatory)
|
||||||
|
# if null the value will be set to undefined and ignored
|
||||||
|
# label: text to be shown
|
||||||
|
# if not set value will be used
|
||||||
|
# description,url
|
||||||
|
# resource: for parent-target environment:
|
||||||
|
# an object with allowed resource counts
|
||||||
|
# for other values: the resource to be counted
|
||||||
|
#
|
||||||
|
#
|
||||||
|
types:
|
||||||
|
- &m5base
|
||||||
|
type: select
|
||||||
|
target: define
|
||||||
|
label: 'M5 Atom light Base'
|
||||||
|
key: m5lightbase
|
||||||
|
values:
|
||||||
|
- label: "CAN KIT"
|
||||||
|
value: M5_CAN_KIT
|
||||||
|
description: "M5 Stack CAN Kit"
|
||||||
|
url: "https://docs.m5stack.com/en/atom/atom_can"
|
||||||
|
resource: can
|
||||||
|
- value: M5_SERIAL_KIT_232
|
||||||
|
description: "M5 Stack RS232 Base"
|
||||||
|
label: "Atomic RS232 Base"
|
||||||
|
url: "https://docs.m5stack.com/en/atom/Atomic%20RS232%20Base"
|
||||||
|
resource: serial
|
||||||
|
- value: M5_SERIAL_KIT_485
|
||||||
|
description: "M5 Stack RS485 Base"
|
||||||
|
label: "Atomic RS485 Base"
|
||||||
|
url: "https://docs.m5stack.com/en/atom/Atomic%20RS485%20Base"
|
||||||
|
resource: serial
|
||||||
|
- value: M5_GPS_KIT
|
||||||
|
description: "M5 Stack Gps Kit"
|
||||||
|
label: "Gps Base"
|
||||||
|
url: "https://docs.m5stack.com/en/atom/atomicgps"
|
||||||
|
resource: serial
|
||||||
|
|
||||||
|
- &m5groovei2c
|
||||||
|
type: frame
|
||||||
|
key: m5groovei2c
|
||||||
|
label: "M5 I2C Groove Units"
|
||||||
|
children:
|
||||||
|
- label: "M5 ENV3"
|
||||||
|
type: checkbox
|
||||||
|
key: m5env3
|
||||||
|
target: define
|
||||||
|
url: "https://docs.m5stack.com/en/unit/envIII"
|
||||||
|
description: "M5 sensor module temperature, humidity, pressure"
|
||||||
|
values:
|
||||||
|
- value: M5_ENV3
|
||||||
|
key: true
|
||||||
|
- &m5groovecan
|
||||||
|
type: select
|
||||||
|
key: m5groovecan
|
||||||
|
target: define
|
||||||
|
label: "M5 Groove CAN Units"
|
||||||
|
values:
|
||||||
|
- label: "CAN Unit"
|
||||||
|
url: "https://docs.m5stack.com/en/unit/can"
|
||||||
|
description: "M5 Can unit"
|
||||||
|
value: M5_CANUNIT
|
||||||
|
resource: can
|
||||||
|
- &m5grooveserial
|
||||||
|
type: select
|
||||||
|
label: "M5 Groove Serial Unit"
|
||||||
|
target: define
|
||||||
|
key: m5grooveserial
|
||||||
|
values:
|
||||||
|
- label: "RS485"
|
||||||
|
key: unit485
|
||||||
|
value: SERIAL_GROOVE_485
|
||||||
|
description: "M5 RS485 unit"
|
||||||
|
url: "https://docs.m5stack.com/en/unit/rs485"
|
||||||
|
resource: serial
|
||||||
|
- label: "Tail485"
|
||||||
|
value: SERIAL_GROOVE_485
|
||||||
|
key: tail485
|
||||||
|
description: "M5 Tail 485"
|
||||||
|
url: "https://docs.m5stack.com/en/atom/tail485"
|
||||||
|
resource: serial
|
||||||
|
- label: "Gps Unit"
|
||||||
|
value: M5_GPS_UNIT
|
||||||
|
description: "M5 Gps Unit"
|
||||||
|
url: "https://docs.m5stack.com/en/unit/gps"
|
||||||
|
resource: serial
|
||||||
|
|
||||||
|
- &m5groove
|
||||||
|
type: select
|
||||||
|
key: m5groove
|
||||||
|
label: 'M5 groove type'
|
||||||
|
help: 'Select the functionality that should be available at the M5 groove pins'
|
||||||
|
values:
|
||||||
|
- key: 'CAN'
|
||||||
|
children:
|
||||||
|
- *m5groovecan
|
||||||
|
- key: 'I2C'
|
||||||
|
children:
|
||||||
|
- *m5groovei2c
|
||||||
|
- key: 'Serial'
|
||||||
|
children:
|
||||||
|
- *m5grooveserial
|
||||||
|
- &gpiopin
|
||||||
|
type: dropdown
|
||||||
|
resource: "gpio:"
|
||||||
|
help: 'Select the number of the GPIO pin for this function'
|
||||||
|
values:
|
||||||
|
- {label: unset,value:}
|
||||||
|
- {label: "0: Low at boot!",value: 0}
|
||||||
|
- 1
|
||||||
|
- {label: "2: Float/Low at boot!", value: 2}
|
||||||
|
- 3
|
||||||
|
- {label: "4: Strapping!",value: 4}
|
||||||
|
- {label: "5: Hight at boot!", value: 5}
|
||||||
|
- {label: "12: Low at boot!", value: 12}
|
||||||
|
- 13
|
||||||
|
- 14
|
||||||
|
- {label: "15: High at boot!", value: 15}
|
||||||
|
- 16
|
||||||
|
- 17
|
||||||
|
- 18
|
||||||
|
- 19
|
||||||
|
- 21
|
||||||
|
- 22
|
||||||
|
- 23
|
||||||
|
- 25
|
||||||
|
- 26
|
||||||
|
- 27
|
||||||
|
- 32
|
||||||
|
- 31
|
||||||
|
- 32
|
||||||
|
- 33
|
||||||
|
- 37
|
||||||
|
- 38
|
||||||
|
|
||||||
|
- &gpioinput
|
||||||
|
type: dropdown
|
||||||
|
resource: "gpio:"
|
||||||
|
help: 'Select the number of the GPIO pin for this function'
|
||||||
|
values:
|
||||||
|
- {label: unset,value:}
|
||||||
|
- {label: "0: Low at boot!",value: 0}
|
||||||
|
- 1
|
||||||
|
- {label: "2: Float/Low at boot!", value: 2}
|
||||||
|
- 3
|
||||||
|
- {label: "4: Strapping!",value: 4}
|
||||||
|
- {label: "5: Hight at boot!", value: 5}
|
||||||
|
- {label: "12: Low at boot!", value: 12}
|
||||||
|
- 13
|
||||||
|
- 14
|
||||||
|
- {label: "15: High at boot!", value: 15}
|
||||||
|
- 16
|
||||||
|
- 17
|
||||||
|
- 18
|
||||||
|
- 19
|
||||||
|
- 21
|
||||||
|
- 22
|
||||||
|
- 23
|
||||||
|
- 25
|
||||||
|
- 26
|
||||||
|
- 27
|
||||||
|
- 32
|
||||||
|
- 31
|
||||||
|
- 32
|
||||||
|
- 33
|
||||||
|
- 34
|
||||||
|
- 35
|
||||||
|
- 36
|
||||||
|
- 37
|
||||||
|
- 38
|
||||||
|
- 39
|
||||||
|
|
||||||
|
- &serialRX
|
||||||
|
<<: *gpioinput
|
||||||
|
key: RX
|
||||||
|
help: 'number of the GPIO pin for the receive function'
|
||||||
|
target: "define:#serial#RX"
|
||||||
|
mandatory: true
|
||||||
|
- &serialTX
|
||||||
|
<<: *gpiopin
|
||||||
|
key: TX
|
||||||
|
help: 'number of the GPIO pin for the transmit function'
|
||||||
|
target: "define:#serial#TX"
|
||||||
|
mandatory: true
|
||||||
|
- &serialValues
|
||||||
|
- key: true
|
||||||
|
children:
|
||||||
|
- type: select
|
||||||
|
key: type
|
||||||
|
target: "define:#serial#TYPE"
|
||||||
|
label: "Serial Type"
|
||||||
|
values:
|
||||||
|
- key: uni
|
||||||
|
value: 1
|
||||||
|
label: "UNI"
|
||||||
|
description: "Select direction at Config UI"
|
||||||
|
help: 'On the config UI you can select if the serial should be a transmitter or a receiver'
|
||||||
|
children:
|
||||||
|
- *serialRX
|
||||||
|
- *serialTX
|
||||||
|
- key: bi
|
||||||
|
value: 2
|
||||||
|
label: "BiDir"
|
||||||
|
description: "Input and Output"
|
||||||
|
help: 'The serial device can run both receive and transmit. Typically for RS232.'
|
||||||
|
children:
|
||||||
|
- *serialRX
|
||||||
|
- *serialTX
|
||||||
|
- key: rx
|
||||||
|
value: 3
|
||||||
|
label: "RX"
|
||||||
|
description: "Input only"
|
||||||
|
children:
|
||||||
|
- *serialRX
|
||||||
|
- key: tx
|
||||||
|
value: 1
|
||||||
|
label: "TX"
|
||||||
|
description: "output only"
|
||||||
|
children:
|
||||||
|
- *serialTX
|
||||||
|
- &serial1
|
||||||
|
type: checkbox
|
||||||
|
label: 'Serial 1'
|
||||||
|
key: serial1
|
||||||
|
base:
|
||||||
|
serial: GWSERIAL_
|
||||||
|
values: *serialValues
|
||||||
|
|
||||||
|
- &serial2
|
||||||
|
type: checkbox
|
||||||
|
label: 'Serial 2'
|
||||||
|
key: serial2
|
||||||
|
base:
|
||||||
|
serial: GWSERIAL2_
|
||||||
|
values: *serialValues
|
||||||
|
|
||||||
|
- &can
|
||||||
|
type: checkbox
|
||||||
|
label: CAN(NMEA2000)
|
||||||
|
key: can
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
children:
|
||||||
|
- <<: *gpioinput
|
||||||
|
label: RX
|
||||||
|
key: rx
|
||||||
|
mandatory: true
|
||||||
|
help: 'set the number of the GPIO pin for the CAN(NMEA2000) RX function'
|
||||||
|
target: "define:ESP32_CAN_RX_PIN"
|
||||||
|
- <<: *gpiopin
|
||||||
|
label: TX
|
||||||
|
key: tx
|
||||||
|
mandatory: true
|
||||||
|
help: 'set the number of the GPIO pin for the CAN(NMEA2000) TX function'
|
||||||
|
target: "define:ESP32_CAN_TX_PIN"
|
||||||
|
|
||||||
|
- &resetButton
|
||||||
|
type: checkbox
|
||||||
|
label: reset button
|
||||||
|
key: resetButton
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
children:
|
||||||
|
- <<: *gpiopin
|
||||||
|
label: Button
|
||||||
|
key: button
|
||||||
|
target: "define:GWBUTTON_PIN"
|
||||||
|
help: 'the gpio pin for a reset to factory settings'
|
||||||
|
- type: dropdown
|
||||||
|
label: active mode
|
||||||
|
help: 'select if the button should be active high or low'
|
||||||
|
key: resetButtonMode
|
||||||
|
target: "define:GWBUTTON_ACTIVE"
|
||||||
|
values:
|
||||||
|
- label: unset
|
||||||
|
value:
|
||||||
|
- label: LOW
|
||||||
|
value: 0
|
||||||
|
- label: HIGH
|
||||||
|
value: 1
|
||||||
|
- type: checkbox
|
||||||
|
label: pullupdown
|
||||||
|
description: "pull up/pull down resistor"
|
||||||
|
key: resetButtonPUD
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
target: define
|
||||||
|
value: GWBUTTON_PULLUPDOWN
|
||||||
|
|
||||||
|
- &led
|
||||||
|
type: checkbox
|
||||||
|
label: Led
|
||||||
|
key: led
|
||||||
|
description: 'RGB LED'
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
children:
|
||||||
|
- <<: *gpiopin
|
||||||
|
label: LedPin
|
||||||
|
key: ledpin
|
||||||
|
mandatory: true
|
||||||
|
target: "define:GWLED_PIN"
|
||||||
|
- type: dropdown
|
||||||
|
label: ledtype
|
||||||
|
help: "the type of the led"
|
||||||
|
key: ledtype
|
||||||
|
target: "define:GWLED_CODE"
|
||||||
|
mandatory: true
|
||||||
|
values:
|
||||||
|
- label: unset
|
||||||
|
value:
|
||||||
|
- label: SK6812
|
||||||
|
value: 0
|
||||||
|
key: sk6812
|
||||||
|
- label: WS2812
|
||||||
|
key: ws2812
|
||||||
|
value: 1
|
||||||
|
- type: dropdown
|
||||||
|
key: ledorder
|
||||||
|
label: color order
|
||||||
|
target: "define:GWLED_SCHEMA"
|
||||||
|
mandatory: true
|
||||||
|
values:
|
||||||
|
- label: unset
|
||||||
|
value:
|
||||||
|
- label: RGB
|
||||||
|
value: 10
|
||||||
|
- label: RBG
|
||||||
|
value: 17
|
||||||
|
- label: GRB
|
||||||
|
value: 66
|
||||||
|
- label: GBR
|
||||||
|
value: 80
|
||||||
|
- label: BRG
|
||||||
|
value: 129
|
||||||
|
- label: BGR
|
||||||
|
value: 136
|
||||||
|
- type: range
|
||||||
|
label: brigthness
|
||||||
|
target: "define:GWLED_BRIGHTNESS"
|
||||||
|
key: brightness
|
||||||
|
min: 0
|
||||||
|
max: 255
|
||||||
|
|
||||||
|
|
||||||
|
- &iicsensors
|
||||||
|
type: checkbox
|
||||||
|
label: "I2C #busname#"
|
||||||
|
key: "i2c#busname#"
|
||||||
|
description: "I2C Bus #busname#"
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
children:
|
||||||
|
- <<: *gpiopin
|
||||||
|
label: SDA
|
||||||
|
key: sda
|
||||||
|
mandatory: true
|
||||||
|
target: "define:GWIIC_SDA#bus#"
|
||||||
|
- <<: *gpiopin
|
||||||
|
label: SCL
|
||||||
|
key: scl
|
||||||
|
mandatory: true
|
||||||
|
target: "define:GWIIC_SCL#bus#"
|
||||||
|
- type: checkbox
|
||||||
|
label: SHT3X-#busname#-1
|
||||||
|
description: "SHT30 temperature and humidity sensor 0x44"
|
||||||
|
key: sht3x1
|
||||||
|
target: define
|
||||||
|
url: "https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/SHT3x_Datasheet_digital.pdf"
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
value: GWSHT3X#busname#1
|
||||||
|
- type: checkbox
|
||||||
|
label: SHT3X-#busname#-1
|
||||||
|
description: "SHT30 temperature and humidity sensor 0x45"
|
||||||
|
key: sht3x2
|
||||||
|
target: define
|
||||||
|
url: "https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/SHT3x_Datasheet_digital.pdf"
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
value: GWSHT3X#busname#2
|
||||||
|
- type: checkbox
|
||||||
|
label: QMP6988-#busname#-1
|
||||||
|
description: "QMP6988 pressure sensor addr 86"
|
||||||
|
key: qmp69881
|
||||||
|
target: define
|
||||||
|
url: "https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/enviii/QMP6988%20Datasheet.pdf"
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
value: GWQMP6988#busname#1
|
||||||
|
- type: checkbox
|
||||||
|
label: QMP6988-#busname#-2
|
||||||
|
description: "QMP6988 pressure sensor addr 112"
|
||||||
|
key: qmp69882
|
||||||
|
target: define
|
||||||
|
url: "https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/enviii/QMP6988%20Datasheet.pdf"
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
value: GWQMP6988#busname#2
|
||||||
|
- type: checkbox
|
||||||
|
label: BME280-#busname#-1
|
||||||
|
description: "BME280 temperature/humidity/pressure sensor 0x76"
|
||||||
|
key: bme2801
|
||||||
|
target: define
|
||||||
|
url: "https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf"
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
value: GWBME280#busname#1
|
||||||
|
- type: checkbox
|
||||||
|
label: BME280-#busname#-2
|
||||||
|
description: "BME280 temperature/humidity/pressure sensor 0x77"
|
||||||
|
key: bme2802
|
||||||
|
target: define
|
||||||
|
url: "https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf"
|
||||||
|
values:
|
||||||
|
- key: true
|
||||||
|
value: GWBME280#busname#2
|
||||||
|
|
||||||
|
|
||||||
|
resources:
|
||||||
|
default: &esp32default
|
||||||
|
serial: 2
|
||||||
|
can: 1
|
||||||
|
i2c: 1
|
||||||
|
gpio: 1
|
||||||
|
|
||||||
|
config:
|
||||||
|
children:
|
||||||
|
- type: select
|
||||||
|
target: environment
|
||||||
|
label: 'Board'
|
||||||
|
key: board
|
||||||
|
values:
|
||||||
|
- value: m5stack-atom-generic
|
||||||
|
label: m5stack-atom
|
||||||
|
description: "M5 Stack Atom light"
|
||||||
|
url: "http://docs.m5stack.com/en/core/atom_lite"
|
||||||
|
resource: *esp32default
|
||||||
|
children:
|
||||||
|
- *m5base
|
||||||
|
- *m5groove
|
||||||
|
- value: m5stack-atoms3-generic
|
||||||
|
label: m5stack-atoms3
|
||||||
|
description: "M5 Stack AtomS3 light"
|
||||||
|
url: "http://docs.m5stack.com/en/core/AtomS3%20Lite"
|
||||||
|
resource: *esp32default
|
||||||
|
children:
|
||||||
|
- *m5base
|
||||||
|
- *m5groove
|
||||||
|
- value: m5stickc-atom-generic
|
||||||
|
label: m5stick+ atom
|
||||||
|
description: "M5 Stick C+"
|
||||||
|
url: "http://docs.m5stack.com/en/core/m5stickc_plus"
|
||||||
|
resource: *esp32default
|
||||||
|
children:
|
||||||
|
- *m5groove
|
||||||
|
|
||||||
|
|
||||||
|
- value: nodemcu-generic
|
||||||
|
label: nodemcu
|
||||||
|
description: "Node mcu esp32"
|
||||||
|
url: "https://docs.platformio.org/en/stable/boards/espressif32/nodemcu-32s.html"
|
||||||
|
resource: *esp32default
|
||||||
|
children:
|
||||||
|
- *serial1
|
||||||
|
- *serial2
|
||||||
|
- *can
|
||||||
|
- *resetButton
|
||||||
|
- *led
|
||||||
|
- <<: *iicsensors
|
||||||
|
base:
|
||||||
|
busname: "1"
|
||||||
|
bus: ""
|
||||||
|
- <<: *iicsensors
|
||||||
|
base:
|
||||||
|
busname: "2"
|
||||||
|
bus: "2"
|
|
@ -0,0 +1,214 @@
|
||||||
|
.hidden{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reused stuff from configui */
|
||||||
|
.configui.container{
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.configui .info{
|
||||||
|
margin-bottom: 1em;
|
||||||
|
opacity: 0.6;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
.configui .parameters {
|
||||||
|
border-bottom: 1px solid grey;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
.configui .row input[type="checkbox"] {
|
||||||
|
flex-grow: 1;
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configui .row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0.5em;
|
||||||
|
flex-wrap: unset;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configui .row .label {
|
||||||
|
width: 10em;
|
||||||
|
opacity: 0.6;
|
||||||
|
padding: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.configui .row .value{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.configui .since {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
.configui input[type=checkbox] {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
opacity: 1;
|
||||||
|
z-index: unset;
|
||||||
|
appearance: auto;
|
||||||
|
float:none;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configui .buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.configui button {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
.configui .footer {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 1em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
.configui .visually-hidden {
|
||||||
|
position: absolute !important;
|
||||||
|
height: 1px;
|
||||||
|
width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.configui .dialogBack {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: #8a8c8ec7;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.configui .hidden{
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.configui .dialog{
|
||||||
|
max-width: 35em;
|
||||||
|
margin: auto;
|
||||||
|
background-color: white;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
.configui #warn{
|
||||||
|
display: none;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.configui .error .value{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.configui #warn.warn{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.configui .radioFrame {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.configui input.radioCi {
|
||||||
|
appearance: auto;
|
||||||
|
float: none;
|
||||||
|
opacity: 1;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
z-index: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configui .selector .title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.configui .selector.level2 {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.configui .selector.level3 {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
.configui .selector.level4 {
|
||||||
|
margin-left: 1.5em;
|
||||||
|
}
|
||||||
|
.configui .selector.tframe {
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: unset;
|
||||||
|
}
|
||||||
|
.configui .childFrame {
|
||||||
|
border-top: 1px solid grey;
|
||||||
|
margin-top: 0.3em;
|
||||||
|
}
|
||||||
|
.configui .tframe>.childFrame {
|
||||||
|
border-top: unset;
|
||||||
|
margin-top: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configui .tcheckbox>.inputFrame,
|
||||||
|
.configui .tdropdown>.inputFrame,
|
||||||
|
.configui .trange>.inputFrame {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.configui .title.tdropdown,
|
||||||
|
.configui .title.tcheckbox,
|
||||||
|
.configui .title.trange {
|
||||||
|
width: 10em;
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
.configui .titleFrame {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configui form#upload {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
/* display: flex; */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.configui .label {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
.configui .row input{
|
||||||
|
flex-grow: 1;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
.configui .overlayContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #80808070;
|
||||||
|
display: flex;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.configui .overlay {
|
||||||
|
margin: auto;
|
||||||
|
background-color: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.configui .overlayContent {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
.configui div#overlayContent.text{
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
.configui .overlayButtons {
|
||||||
|
border-top: 1px solid grey;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
.configui button.help {
|
||||||
|
margin-left: 1em;
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<script type="module" src="cibuild.js"></script>
|
||||||
|
<link rel="stylesheet" href="cibuild.css"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="configui cibuild container">
|
||||||
|
<h1>Build your own ESP32-NMEA2000</h1>
|
||||||
|
<h3>New Build</h3>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label" id="branchOrTag"></span>
|
||||||
|
<span class="value" id="branchOrTagValue">---</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label" >GitSha</span>
|
||||||
|
<span class="value" id="gitSha">---</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label" >Version</span>
|
||||||
|
<span class="value" id="buildVersion"></span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<button id="downloadConfig">SaveCfg</button>
|
||||||
|
<button id="uploadConfig">LoadCfg</button>
|
||||||
|
</div>
|
||||||
|
<div id="selectors">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">Board type</span>
|
||||||
|
<div class="value" id="environment" ></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">Build Flags</span>
|
||||||
|
<div class="value" id="buildflags"></div>
|
||||||
|
<button class="help" id="buildCommand">?</button>
|
||||||
|
</div>
|
||||||
|
<div class="row hidden error">
|
||||||
|
<span class="label">Error</span>
|
||||||
|
<span class="value" id="configError"></span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<button id="start">Start</button>
|
||||||
|
</div>
|
||||||
|
<h3 id="resultTitle">Last Build</h3>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">Job Id</span>
|
||||||
|
<div id="pipeline">---</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">Status</span>
|
||||||
|
<div id="status">---</div>
|
||||||
|
</div>
|
||||||
|
<div class="row hidden error">
|
||||||
|
<span class="label">Error</span>
|
||||||
|
<div class="value" id="error"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row hidden">
|
||||||
|
<span class="label">Web Status</span>
|
||||||
|
<a target="_" id="status_url">Link</a>
|
||||||
|
</div>
|
||||||
|
<div class="row hidden">
|
||||||
|
<button id="download">Download</button>
|
||||||
|
<button id="webinstall">Install</button>
|
||||||
|
</div>
|
||||||
|
<div class="overlayContainer hidden" id="overlayContainer">
|
||||||
|
<div id="overlay" class="overlay">
|
||||||
|
<div id="overlayContent" class="overlayContent">
|
||||||
|
AHA
|
||||||
|
</div>
|
||||||
|
<div class="overlayButtons">
|
||||||
|
<button id="secondDialogButton"></button>
|
||||||
|
<button id="hideOverlay">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<iframe id="dlframe" width="1" height="1"></iframe>
|
||||||
|
<form id="upload">
|
||||||
|
<input type="file" id="fileSelect"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,852 @@
|
||||||
|
import { addEl, setButtons,fillValues, setValue, buildUrl, fetchJson, setVisible, enableEl, setValues, getParam, fillSelect, forEachEl, readFile } from "./helper.js";
|
||||||
|
import {load as yamlLoad} from "https://cdn.skypack.dev/js-yaml@4.1.0";
|
||||||
|
import fileDownload from "https://cdn.skypack.dev/js-file-download@0.4.12"
|
||||||
|
class PipelineInfo{
|
||||||
|
constructor(id){
|
||||||
|
this.STFIELDS=['status','error','status_url'];
|
||||||
|
this.reset(id);
|
||||||
|
this.lastUpdate=0;
|
||||||
|
}
|
||||||
|
update(state){
|
||||||
|
this.lastUpdate=(new Date()).getTime();
|
||||||
|
if (state.pipeline_id !== undefined && state.pipeline_id !== this.id){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.STFIELDS.forEach((i)=>{
|
||||||
|
let v=state[i];
|
||||||
|
if (v !== undefined)this[i]=v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
reset(id,opt_state){
|
||||||
|
this.id=id;
|
||||||
|
this.STFIELDS.forEach((i)=>this[i]=undefined);
|
||||||
|
this.downloadUrl=undefined;
|
||||||
|
if (opt_state) {
|
||||||
|
this.update(opt_state);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if (id !== undefined) this.status='fetching';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid(){
|
||||||
|
return this.id !== undefined;
|
||||||
|
}
|
||||||
|
isRunning(){
|
||||||
|
if (! this.valid()) return false;
|
||||||
|
if (this.status === undefined) return false;
|
||||||
|
return ['error','success','canceled','failed','errored'].indexOf(this.status) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
(function(){
|
||||||
|
const STATUS_INTERVAL=2000;
|
||||||
|
const CURRENT_PIPELINE='pipeline';
|
||||||
|
const API="cibuild.php";
|
||||||
|
const GITAPI="install.php";
|
||||||
|
const GITUSER="wellenvogel";
|
||||||
|
const GITREPO="esp32-nmea2000";
|
||||||
|
let currentPipeline=new PipelineInfo();
|
||||||
|
let timer=undefined;
|
||||||
|
let structure=undefined;
|
||||||
|
let config={}; //values as read and stored
|
||||||
|
let configStruct={}; //complete struct merged of config and struct
|
||||||
|
let displayMode='last';
|
||||||
|
let delayedSearch=undefined;
|
||||||
|
let gitSha=undefined;
|
||||||
|
let buildVersion=undefined;
|
||||||
|
let configName="buildconfig";
|
||||||
|
let isModified=false;
|
||||||
|
const modeStrings={
|
||||||
|
last: 'Last Build',
|
||||||
|
existing: 'Existing Build',
|
||||||
|
current: 'Current Build'
|
||||||
|
};
|
||||||
|
const setDisplayMode=(mode)=>{
|
||||||
|
let old=displayMode;
|
||||||
|
let ms=modeStrings[mode];
|
||||||
|
if (ms === undefined){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
displayMode=mode;
|
||||||
|
setValue('resultTitle',ms);
|
||||||
|
return mode !== old;
|
||||||
|
}
|
||||||
|
const updateStatus=()=>{
|
||||||
|
setValues(currentPipeline,{
|
||||||
|
id: 'pipeline'
|
||||||
|
});
|
||||||
|
setVisible('download',currentPipeline.valid() && currentPipeline.downloadUrl!==undefined,true);
|
||||||
|
setVisible('status_url',currentPipeline.valid() && currentPipeline.status_url!==undefined,true);
|
||||||
|
setVisible('error',currentPipeline.error!==undefined,true);
|
||||||
|
let values={};
|
||||||
|
fillValues(values,['configError','environment']);
|
||||||
|
setVisible('buildCommand',values.environment !== "" && ! values.configError);
|
||||||
|
if (values.configError) {
|
||||||
|
enableEl('start',false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!values.environment){
|
||||||
|
enableEl('start',false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (displayMode != 'existing'){
|
||||||
|
if (currentPipeline.valid()){
|
||||||
|
//check pipeline state
|
||||||
|
if (['error','success','canceled','failed'].indexOf(currentPipeline.status) >= 0){
|
||||||
|
enableEl('start',true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
enableEl('start',false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
enableEl('start',true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//display mode existing
|
||||||
|
//allow start if either no pipeline or not running and status != success
|
||||||
|
enableEl('start',!currentPipeline.valid() || (!currentPipeline.isRunning() && currentPipeline.status != "success"));
|
||||||
|
}
|
||||||
|
const isRunning=()=>{
|
||||||
|
return currentPipeline.isRunning();
|
||||||
|
}
|
||||||
|
const fetchStatus=(initial)=>{
|
||||||
|
if (! currentPipeline.valid()){
|
||||||
|
updateStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let queryPipeline=currentPipeline.id;
|
||||||
|
fetchJson(API,{api:'status',pipeline:currentPipeline.id})
|
||||||
|
.then((st)=>{
|
||||||
|
if (queryPipeline !== currentPipeline.id) return;
|
||||||
|
let stid=st.pipeline_id||st.id;
|
||||||
|
if (currentPipeline.id !== stid) return;
|
||||||
|
if (st.status === undefined) st.status=st.state;
|
||||||
|
currentPipeline.update(st);
|
||||||
|
updateStatus();
|
||||||
|
if (st.status === 'error' || st.status === 'errored' || st.status === 'canceled'){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (st.status === 'success'){
|
||||||
|
fetchJson(API,{api:'artifacts',pipeline:currentPipeline.id})
|
||||||
|
.then((ar)=>{
|
||||||
|
if (! ar.items || ar.items.length < 1){
|
||||||
|
throw new Error("no download link");
|
||||||
|
}
|
||||||
|
currentPipeline.downloadUrl=buildUrl(API,{
|
||||||
|
download: currentPipeline.id
|
||||||
|
});
|
||||||
|
updateStatus();
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
currentPipeline.update({
|
||||||
|
status:'error',
|
||||||
|
error:"Unable to get build result: "+err
|
||||||
|
});
|
||||||
|
updateStatus();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timer=window.setTimeout(fetchStatus,STATUS_INTERVAL)
|
||||||
|
})
|
||||||
|
.catch((e)=>{
|
||||||
|
timer=window.setTimeout(fetchStatus,STATUS_INTERVAL);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const setCurrentPipeline=(pipeline,doStore)=>{
|
||||||
|
currentPipeline.reset(pipeline);
|
||||||
|
if (doStore) window.localStorage.setItem(CURRENT_PIPELINE,pipeline);
|
||||||
|
};
|
||||||
|
const startBuild=()=>{
|
||||||
|
let param={};
|
||||||
|
currentPipeline.reset(undefined,{status:'requested'});
|
||||||
|
if (timer) window.clearTimeout(timer);
|
||||||
|
timer=undefined;
|
||||||
|
fillValues(param,['environment','buildflags']);
|
||||||
|
setDisplayMode('current');
|
||||||
|
updateStatus();
|
||||||
|
if (gitSha !== undefined) param.tag=gitSha;
|
||||||
|
param.config=JSON.stringify(config);
|
||||||
|
if (buildVersion !== undefined){
|
||||||
|
param.suffix="-"+buildVersion;
|
||||||
|
}
|
||||||
|
fetchJson(API,Object.assign({
|
||||||
|
api:'start'},param))
|
||||||
|
.then((json)=>{
|
||||||
|
let status=json.status || json.state|| 'error';
|
||||||
|
if (status === 'error'){
|
||||||
|
currentPipeline.update({status:status,error:json.error})
|
||||||
|
updateStatus();
|
||||||
|
throw new Error("unable to create job "+(json.error||''));
|
||||||
|
}
|
||||||
|
if (!json.id) {
|
||||||
|
let error="unable to create job, no id"
|
||||||
|
currentPipeline.update({status:'error',error:error});
|
||||||
|
updateStatus();
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
setCurrentPipeline(json.id,true);
|
||||||
|
updateStatus();
|
||||||
|
timer=window.setTimeout(fetchStatus,STATUS_INTERVAL);
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
currentPipeline.update({status:'error',error:err});
|
||||||
|
updateStatus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const runDownload=()=>{
|
||||||
|
if (! currentPipeline.downloadUrl) return;
|
||||||
|
let df=document.getElementById('dlframe');
|
||||||
|
if (df){
|
||||||
|
df.setAttribute('src',null);
|
||||||
|
df.setAttribute('src',currentPipeline.downloadUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const webInstall=()=>{
|
||||||
|
if (! currentPipeline.downloadUrl) return;
|
||||||
|
let url=buildUrl("install.html",{custom:currentPipeline.downloadUrl});
|
||||||
|
window.location.href=url;
|
||||||
|
}
|
||||||
|
const uploadConfig=()=>{
|
||||||
|
let form=document.getElementById("upload");
|
||||||
|
form.reset();
|
||||||
|
let fsel=document.getElementById("fileSelect");
|
||||||
|
fsel.onchange=async ()=>{
|
||||||
|
if (fsel.files.length < 1) return;
|
||||||
|
let file=fsel.files[0];
|
||||||
|
if (! file.name.match(/json$/)){
|
||||||
|
alert("only json files");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
let content=await readFile(file,true);
|
||||||
|
let newConfig=JSON.parse(content);
|
||||||
|
removeSelectors(ROOT_PATH,true);
|
||||||
|
config=newConfig;
|
||||||
|
buildSelectors(ROOT_PATH,structure.config.children,true);
|
||||||
|
findPipeline();
|
||||||
|
} catch (e){
|
||||||
|
alert("upload "+fsel.files[0].name+" failed: "+e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
fsel.click();
|
||||||
|
}
|
||||||
|
const downloadConfig=()=>{
|
||||||
|
let name=configName;
|
||||||
|
if (isModified) name=name.replace(/[0-9]*$/,'')+formatDate(undefined,true);
|
||||||
|
name+=".json";
|
||||||
|
fileDownload(JSON.stringify(config),name);
|
||||||
|
}
|
||||||
|
const showOverlay=(text, isHtml, secondButton)=>{
|
||||||
|
let el = document.getElementById('overlayContent');
|
||||||
|
if (isHtml) {
|
||||||
|
el.innerHTML = text;
|
||||||
|
el.classList.remove("text");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
el.textContent = text;
|
||||||
|
el.classList.add("text");
|
||||||
|
}
|
||||||
|
let container = document.getElementById('overlayContainer');
|
||||||
|
container.classList.remove('hidden');
|
||||||
|
let db=document.getElementById("secondDialogButton");
|
||||||
|
if (db) {
|
||||||
|
if (secondButton && secondButton.callback) {
|
||||||
|
db.classList.remove("hidden");
|
||||||
|
if (secondButton.title){db.textContent=secondButton.title}
|
||||||
|
db.onclick=secondButton.callback;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
db.classList.add("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hideOverlay=()=> {
|
||||||
|
let container = document.getElementById('overlayContainer');
|
||||||
|
container.classList.add('hidden');
|
||||||
|
}
|
||||||
|
const loadConfig=async (url)=>{
|
||||||
|
let config=await fetch(url).then((r)=>{
|
||||||
|
if (!r.ok) throw new Error("unable to fetch: "+r.statusText);
|
||||||
|
return r.text()
|
||||||
|
});
|
||||||
|
let parsed=yamlLoad(config);
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
const showBuildCommand= async ()=>{
|
||||||
|
let v={};
|
||||||
|
fillValues(v,['environment','buildflags']);
|
||||||
|
if (v.environment !== ""){
|
||||||
|
let help="Run the build from a command line:\n";
|
||||||
|
let cmd="PLATFORMIO_BUILD_FLAGS=\"";
|
||||||
|
cmd+=v.buildflags;
|
||||||
|
cmd+="\" pio run -e "+v.environment;
|
||||||
|
help+=cmd;
|
||||||
|
showOverlay(help,false,{title:"Copy",callback:(ev)=>{
|
||||||
|
try{
|
||||||
|
navigator.clipboard.writeText(cmd);
|
||||||
|
//alert("copied:"+cmd);
|
||||||
|
}
|
||||||
|
catch (e){
|
||||||
|
alert("Unable to copy:"+e);
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const btConfig={
|
||||||
|
start:startBuild,
|
||||||
|
download:runDownload,
|
||||||
|
webinstall:webInstall,
|
||||||
|
uploadConfig: uploadConfig,
|
||||||
|
downloadConfig: downloadConfig,
|
||||||
|
hideOverlay: hideOverlay,
|
||||||
|
buildCommand: showBuildCommand
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const PATH_ATTR='data-path';
|
||||||
|
const SEPARATOR=':';
|
||||||
|
const expandObject=(obj,parent)=>{
|
||||||
|
if (typeof(obj) !== 'object'){
|
||||||
|
obj={value:obj}
|
||||||
|
}
|
||||||
|
let rt=Object.assign({},obj);
|
||||||
|
if (rt.value === undefined && rt.key !== undefined) rt.value=rt.key;
|
||||||
|
if (rt.key === undefined) rt.key=rt.value;
|
||||||
|
if (rt.value === null) rt.value=undefined;
|
||||||
|
if (rt.label === undefined){
|
||||||
|
if (rt.value !== undefined) rt.label=rt.value;
|
||||||
|
else rt.label=rt.key;
|
||||||
|
}
|
||||||
|
if (rt.resource === undefined && typeof(parent) === 'object'){
|
||||||
|
if (parent.resource !== undefined){
|
||||||
|
if (parent.resource.match(/:$/)){
|
||||||
|
if(rt.value !== undefined && rt.value !== null){
|
||||||
|
rt.resource=parent.resource+rt.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
rt.resource=parent.resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rt.target === undefined && typeof(parent) === 'object'){
|
||||||
|
rt.target=parent.target;
|
||||||
|
}
|
||||||
|
if (rt.mandatory === undefined && typeof(parent) === 'object'){
|
||||||
|
rt.mandatory=parent.mandatory;
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
const expandList=(lst,parent)=>{
|
||||||
|
let rt=[];
|
||||||
|
if (! lst) return rt;
|
||||||
|
lst.forEach((e)=>rt.push(expandObject(e,parent)));
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addDescription=(v,frame)=>{
|
||||||
|
if (frame === undefined) return;
|
||||||
|
if (v.description){
|
||||||
|
if(v.url) {
|
||||||
|
let lnk = addEl('a', 'description', frame, v.description);
|
||||||
|
lnk.setAttribute('href', v.url);
|
||||||
|
lnk.setAttribute('target', '_');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
let de=addEl('div','description',frame,v.description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v.help){
|
||||||
|
let bt=addEl('button','help',frame,'?');
|
||||||
|
bt.addEventListener('click',()=>showOverlay(v.help));
|
||||||
|
}
|
||||||
|
else if (v.helpHtml){
|
||||||
|
let bt=addEl('button','help',frame,'?');
|
||||||
|
bt.addEventListener('click',()=>showOverlay(v.helpHtml,true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {build a selector} parent
|
||||||
|
* @param {*} config
|
||||||
|
* @param {*} name
|
||||||
|
* @param {*} current
|
||||||
|
* @param {*} callback will be called with: children,key,value,initial
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const buildSelector=(parent,cfgBase,name,current,callback)=>{
|
||||||
|
let config=expandObject(cfgBase);
|
||||||
|
if (current === undefined && config.default !== undefined){
|
||||||
|
current=config.default;
|
||||||
|
}
|
||||||
|
let rep=new RegExp("[^"+SEPARATOR+"]*","g");
|
||||||
|
let level=name.replace(rep,'');
|
||||||
|
let frame=addEl('div','selector level'+level.length+' t'+config.type,parent);
|
||||||
|
frame.setAttribute(PATH_ATTR,name);
|
||||||
|
let inputFrame=addEl('div','inputFrame',frame);
|
||||||
|
let titleFrame=undefined;
|
||||||
|
if (config.label !== undefined){
|
||||||
|
titleFrame=addEl('div','titleFrame t'+config.type,inputFrame);
|
||||||
|
addEl('div','title t'+config.type,titleFrame,config.label);
|
||||||
|
}
|
||||||
|
let initialConfig=undefined
|
||||||
|
if (config.type === 'frame' || config.type === undefined){
|
||||||
|
initialConfig=config;
|
||||||
|
}
|
||||||
|
let expandedValues=expandList(config.values,config);
|
||||||
|
expandedValues.forEach((v)=>{
|
||||||
|
if (v.type !== undefined && v.type !== "frame"){
|
||||||
|
let err="value element with wrong type "+v.type+" at "+name;
|
||||||
|
alert(err);
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (config.type === 'select') {
|
||||||
|
addDescription(config,titleFrame);
|
||||||
|
for (let idx=0;idx<expandedValues.length;idx++){
|
||||||
|
let v=expandedValues[idx];
|
||||||
|
if (v.key === undefined) continue;
|
||||||
|
let ef = addEl('div', 'radioFrame', inputFrame);
|
||||||
|
addEl('div', 'label', ef, v.label);
|
||||||
|
let re = addEl('input', 'radioCi', ef);
|
||||||
|
re.setAttribute('type', 'radio');
|
||||||
|
re.setAttribute('name', name);
|
||||||
|
re.addEventListener('change', (ev) => callback(v,false));
|
||||||
|
addDescription(v,ef);
|
||||||
|
if (v.key == current) {
|
||||||
|
re.setAttribute('checked','checked');
|
||||||
|
initialConfig=v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (expandedValues.length > 0 && config.type === 'dropdown'){
|
||||||
|
let sel=addEl('select','t'+config.type,inputFrame);
|
||||||
|
for (let idx=0;idx<expandedValues.length;idx++){
|
||||||
|
let v=expandedValues[idx];
|
||||||
|
if (v.key === undefined) continue;
|
||||||
|
let opt=addEl('option','',sel,v.label);
|
||||||
|
opt.setAttribute('value',idx);
|
||||||
|
if (v.key == current){
|
||||||
|
opt.setAttribute('selected',true);
|
||||||
|
initialConfig=v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addDescription(config,inputFrame);
|
||||||
|
sel.addEventListener('change',(ev)=>{
|
||||||
|
let v=expandedValues[ev.target.value];
|
||||||
|
if (! v) return;
|
||||||
|
callback(v,false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.type === 'range'){
|
||||||
|
if (config.min !== undefined && config.max !== undefined) {
|
||||||
|
let min=config.min+0;
|
||||||
|
let step=1;
|
||||||
|
if (config.step !== undefined) step=config.step+0;
|
||||||
|
let max=config.max+0;
|
||||||
|
let valid=false;
|
||||||
|
if (step > 0){
|
||||||
|
if (min < max) valid=true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if (min > max) {
|
||||||
|
let tmp=max;
|
||||||
|
max=min;
|
||||||
|
min=tmp;
|
||||||
|
valid=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! valid){
|
||||||
|
console.log("invalid range config",config);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let sel = addEl('select', 'tdropdown', inputFrame);
|
||||||
|
for (let idx=min;idx <=max;idx+=step){
|
||||||
|
let opt=addEl('option','',sel,idx);
|
||||||
|
opt.setAttribute('value',idx);
|
||||||
|
if (idx == current){
|
||||||
|
opt.setAttribute('selected',true);
|
||||||
|
initialConfig=expandObject({key:idx,value:idx},config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! initialConfig){
|
||||||
|
initialConfig=expandObject({key:min,value:min},config);
|
||||||
|
}
|
||||||
|
addDescription(config, inputFrame);
|
||||||
|
sel.addEventListener('change', (ev) => {
|
||||||
|
let v = expandObject({ key: ev.target.value, value: ev.target.value },config);
|
||||||
|
callback(v, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expandedValues.length > 0 && config.type === 'checkbox'){
|
||||||
|
let act=undefined;
|
||||||
|
let inact=undefined;
|
||||||
|
expandedValues.forEach((ev)=>{
|
||||||
|
if (ev.key === true || ev.key === undefined){
|
||||||
|
act=ev;
|
||||||
|
if (act.key === undefined) act.key=true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inact=ev;
|
||||||
|
});
|
||||||
|
if (act !== undefined){
|
||||||
|
if (inact === undefined) inact={key:false};
|
||||||
|
let cb=addEl('input','t'+config.type,inputFrame);
|
||||||
|
cb.setAttribute('type','checkbox');
|
||||||
|
if (current) {
|
||||||
|
cb.setAttribute('checked',true);
|
||||||
|
initialConfig=act;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
initialConfig=inact;
|
||||||
|
}
|
||||||
|
addDescription(config,inputFrame);
|
||||||
|
cb.addEventListener('change',(ev)=>{
|
||||||
|
if (ev.target.checked){
|
||||||
|
callback(act,false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback(inact,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let childFrame=addEl('div','childFrame',frame);
|
||||||
|
if (initialConfig !== undefined){
|
||||||
|
callback(initialConfig,true,childFrame);
|
||||||
|
}
|
||||||
|
return childFrame;
|
||||||
|
}
|
||||||
|
const removeSelectors=(prefix,removeValues)=>{
|
||||||
|
forEachEl('.selectorFrame',(el)=>{
|
||||||
|
let path=el.getAttribute(PATH_ATTR);
|
||||||
|
if (! path) return;
|
||||||
|
if (path.indexOf(prefix) == 0){
|
||||||
|
el.remove();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (removeValues){
|
||||||
|
let removeKeys=[];
|
||||||
|
for (let k in configStruct){
|
||||||
|
if (k.indexOf(prefix) == 0) removeKeys.push(k);
|
||||||
|
}
|
||||||
|
for (let k in config){
|
||||||
|
if (k.indexOf(prefix) == 0) removeKeys.push(k);
|
||||||
|
}
|
||||||
|
removeKeys.forEach((k)=>{
|
||||||
|
delete config[k];
|
||||||
|
delete configStruct[k];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const buildSelectors=(prefix,configList,initial,base,parent)=>{
|
||||||
|
if (!parent) parent=document.getElementById("selectors");;
|
||||||
|
if (!configList) return;
|
||||||
|
let frame=addEl('div','selectorFrame',parent);
|
||||||
|
frame.setAttribute(PATH_ATTR,prefix);
|
||||||
|
let expandedList=expandList(configList);
|
||||||
|
expandedList.forEach((cfg)=>{
|
||||||
|
let currentBase=Object.assign({},base,cfg.base);
|
||||||
|
cfg=replaceValues(cfg,currentBase);
|
||||||
|
if (cfg.key === undefined){
|
||||||
|
if (cfg.type !== undefined && cfg.type !== 'frame'){
|
||||||
|
console.log("config without key",cfg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let name=prefix;
|
||||||
|
if (name !== undefined){
|
||||||
|
if (cfg.key !== undefined) {
|
||||||
|
name=prefix+SEPARATOR+cfg.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
name=cfg.key;
|
||||||
|
}
|
||||||
|
let current=config[name];
|
||||||
|
let childFrame=buildSelector(frame,cfg,name,current,
|
||||||
|
(child,initial,opt_frame)=>{
|
||||||
|
if(cfg.key !== undefined) removeSelectors(name,!initial);
|
||||||
|
if (! initial) isModified=true;
|
||||||
|
buildSelectors(name,child.children,initial,currentBase,opt_frame||childFrame);
|
||||||
|
if (cfg.key !== undefined) configStruct[name]={cfg:child,base:currentBase};
|
||||||
|
buildValues(initial);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const replaceValues=(str,base)=>{
|
||||||
|
if (! base) return str;
|
||||||
|
if (typeof(str) === 'string'){
|
||||||
|
for (let k in base){
|
||||||
|
let r=new RegExp("#"+k+"#","g");
|
||||||
|
str=str.replace(r,base[k]);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
if (str instanceof Array){
|
||||||
|
let rt=[];
|
||||||
|
str.forEach((el)=>{
|
||||||
|
rt.push(replaceValues(el,base));
|
||||||
|
})
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
if (str instanceof Object){
|
||||||
|
let rt={};
|
||||||
|
for (let k in str){
|
||||||
|
rt[k]=replaceValues(str[k],base);
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
const ROOT_PATH='root';
|
||||||
|
const buildValues=(initial)=>{
|
||||||
|
let environment;
|
||||||
|
let flags="";
|
||||||
|
if (! initial){
|
||||||
|
config={};
|
||||||
|
}
|
||||||
|
let allowedResources={};
|
||||||
|
let currentResources={};
|
||||||
|
let errors="";
|
||||||
|
for (let round = 0; round <= 1; round++) {
|
||||||
|
//round1: find allowed resources
|
||||||
|
//round2: really collect values
|
||||||
|
for (let k in configStruct) {
|
||||||
|
let container = configStruct[k];
|
||||||
|
if (! container.cfg) continue;
|
||||||
|
let struct=container.cfg;
|
||||||
|
if (round > 0) config[k] = struct.key;
|
||||||
|
if (struct.target !== undefined ) {
|
||||||
|
if (struct.value === undefined){
|
||||||
|
if (struct.mandatory && round > 0){
|
||||||
|
errors+=" missing value for "+k+"\n";
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let target=replaceValues(struct.target,container.base);
|
||||||
|
if (target === 'environment' ) {
|
||||||
|
if (round > 0 && struct.key !== undefined) environment = struct.value;
|
||||||
|
else allowedResources=struct.resource;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (round < 1) continue;
|
||||||
|
if (struct.resource){
|
||||||
|
let resList=currentResources[struct.resource];
|
||||||
|
if (! resList){
|
||||||
|
resList=[];
|
||||||
|
currentResources[struct.resource]=resList;
|
||||||
|
}
|
||||||
|
resList.push(struct);
|
||||||
|
}
|
||||||
|
if (target === 'define') {
|
||||||
|
flags += " -D" + struct.value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const DEFPRFX = "define:";
|
||||||
|
if (target.indexOf(DEFPRFX) == 0) {
|
||||||
|
let def = target.substring(DEFPRFX.length);
|
||||||
|
flags += " -D" + def + "=" + struct.value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buildVersion !== undefined){
|
||||||
|
flags+=" -DGWRELEASEVERSION="+buildVersion;
|
||||||
|
}
|
||||||
|
setValues({environment:environment,buildflags:flags});
|
||||||
|
//check resources
|
||||||
|
for (let k in currentResources){
|
||||||
|
let ak=k.replace(/:.*/,'');
|
||||||
|
let resList=currentResources[k];
|
||||||
|
if (allowedResources[ak] !== undefined){
|
||||||
|
if (resList.length > allowedResources[ak]){
|
||||||
|
errors+=" more than "+allowedResources[ak]+" "+k+" device(s) used";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errors){
|
||||||
|
setValue('configError',errors);
|
||||||
|
setVisible('configError',true,true);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
setValue('configError','');
|
||||||
|
setVisible('configError',false,true);
|
||||||
|
}
|
||||||
|
if (! initial) findPipeline();
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
let findIdx=0;
|
||||||
|
const findPipeline=()=>{
|
||||||
|
if (delayedSearch !== undefined){
|
||||||
|
window.clearTimeout(delayedSearch);
|
||||||
|
delayedSearch=undefined;
|
||||||
|
}
|
||||||
|
if (isRunning()) {
|
||||||
|
delayedSearch=window.setTimeout(findPipeline,500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
findIdx++;
|
||||||
|
let queryIdx=findIdx;
|
||||||
|
let param={find:1};
|
||||||
|
fillValues(param,['environment','buildflags']);
|
||||||
|
if (gitSha !== undefined) param.tag=gitSha;
|
||||||
|
fetchJson(API,param)
|
||||||
|
.then((res)=>{
|
||||||
|
if (queryIdx != findIdx) return;
|
||||||
|
setCurrentPipeline(res.pipeline);
|
||||||
|
if (res.pipeline) currentPipeline.status="found";
|
||||||
|
setDisplayMode('existing');
|
||||||
|
updateStatus();
|
||||||
|
fetchStatus(true);
|
||||||
|
})
|
||||||
|
.catch((e)=>{
|
||||||
|
console.log("findPipeline error ",e)
|
||||||
|
if (displayMode == 'existing'){
|
||||||
|
setCurrentPipeline();
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
const formatDate=(opt_date,opt_includeMs)=>{
|
||||||
|
const fmt=(v)=>{
|
||||||
|
return ((v<10)?"0":"")+v;
|
||||||
|
}
|
||||||
|
let now=opt_date|| new Date();
|
||||||
|
let rt=now.getFullYear()+fmt(now.getMonth()+1)+fmt(now.getDate());
|
||||||
|
if (opt_includeMs){
|
||||||
|
rt+=fmt(now.getHours())+fmt(now.getMinutes())+fmt(now.getSeconds());
|
||||||
|
}
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
window.onload=async ()=>{
|
||||||
|
setButtons(btConfig);
|
||||||
|
let pipeline=window.localStorage.getItem(CURRENT_PIPELINE);
|
||||||
|
setDisplayMode('last');
|
||||||
|
if (pipeline){
|
||||||
|
setCurrentPipeline(pipeline);
|
||||||
|
updateStatus();
|
||||||
|
fetchStatus(true);
|
||||||
|
}
|
||||||
|
let gitParam={user:GITUSER,repo:GITREPO};
|
||||||
|
let branch=getParam('branch');
|
||||||
|
if (branch){
|
||||||
|
try{
|
||||||
|
let info=await fetchJson(GITAPI,Object.assign({},gitParam,{branch:branch}));
|
||||||
|
if (info.object){
|
||||||
|
gitSha=info.object.sha;
|
||||||
|
setValue('branchOrTag','branch');
|
||||||
|
setValue('branchOrTagValue',branch);
|
||||||
|
}
|
||||||
|
}catch (e){
|
||||||
|
console.log("branch query error",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gitSha === undefined) {
|
||||||
|
let tag = getParam('tag');
|
||||||
|
let type="tag";
|
||||||
|
if (!tag) {
|
||||||
|
try {
|
||||||
|
let relinfo = await fetchJson(GITAPI, Object.assign({}, gitParam, { api: 1 }));
|
||||||
|
if (relinfo.tag_name) {
|
||||||
|
tag = relinfo.tag_name;
|
||||||
|
type="release";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert("unable to query latest release");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert("unable to query release info " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tag){
|
||||||
|
try{
|
||||||
|
let info=await fetchJson(GITAPI,Object.assign({},gitParam,{tag:tag}));
|
||||||
|
if (info.object){
|
||||||
|
gitSha=info.object.sha;
|
||||||
|
setValue('branchOrTag',type);
|
||||||
|
setValue('branchOrTagValue',tag);
|
||||||
|
}
|
||||||
|
}catch(e){
|
||||||
|
alert("cannot get sha for tag "+tag+": "+e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gitSha === undefined){
|
||||||
|
//last resort: no sha, let the CI pick up latest
|
||||||
|
setValue('gitSha','unknown');
|
||||||
|
setValue('branchOrTag','branch');
|
||||||
|
setValue('branchOrTagValue','master');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
setValue('gitSha',gitSha);
|
||||||
|
}
|
||||||
|
let bot=document.getElementById('branchOrTag');
|
||||||
|
let botv=document.getElementById('branchOrTagValue');
|
||||||
|
if (bot && botv){
|
||||||
|
let type=bot.textContent;
|
||||||
|
let val=botv.textContent;
|
||||||
|
if (type && val){
|
||||||
|
if (type != 'release' && type != 'tag' && type != 'branch'){
|
||||||
|
val=type+val;
|
||||||
|
}
|
||||||
|
if (type == 'branch'){
|
||||||
|
val=val+formatDate();
|
||||||
|
}
|
||||||
|
val=val.replace(/[:.]/g,'_');
|
||||||
|
val=val.replace(/[^a-zA-Z0-9_]*/g,'');
|
||||||
|
if (val.length > 32){
|
||||||
|
val=val.substring(val.length-32)
|
||||||
|
}
|
||||||
|
if (val.length > 0){
|
||||||
|
buildVersion=val;
|
||||||
|
setValue('buildVersion',buildVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gitSha !== undefined){
|
||||||
|
let url=buildUrl(GITAPI,Object.assign({},gitParam,{sha:gitSha,proxy:'webinstall/build.yaml'}));
|
||||||
|
try{
|
||||||
|
structure=await loadConfig(url);
|
||||||
|
}catch (e){
|
||||||
|
alert("unable to load config for selected release:\n "+e+"\n falling back to default");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! structure){
|
||||||
|
structure=await loadConfig("build.yaml");
|
||||||
|
}
|
||||||
|
let ucfg=getParam('config');
|
||||||
|
let loadedCfg=undefined;
|
||||||
|
if (ucfg){
|
||||||
|
ucfg=ucfg.replace(/[^.a-zA-Z_0-9-]/g,'');
|
||||||
|
if (gitSha !== undefined){
|
||||||
|
try{
|
||||||
|
loadedCfg=await fetchJson(GITAPI,Object.assign({},gitParam,{sha:gitSha,proxy:'webinstall/config/'+ucfg+".json"}));
|
||||||
|
}catch(e){
|
||||||
|
alert("unable to load config "+ucfg+" for selected release, trying latest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (loadedCfg === undefined){
|
||||||
|
try{
|
||||||
|
loadedCfg=await fetchJson('config/'+ucfg+".json");
|
||||||
|
}catch(e){
|
||||||
|
alert("unable to load config "+ucfg+": "+e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (loadedCfg !== undefined){
|
||||||
|
configName=ucfg;
|
||||||
|
config=loadedCfg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildSelectors(ROOT_PATH,structure.config.children,true);
|
||||||
|
if (! isRunning()) findPipeline();
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
})();
|
|
@ -0,0 +1,325 @@
|
||||||
|
<?php
|
||||||
|
include("token.php");
|
||||||
|
include("functions.php");
|
||||||
|
include("config.php");
|
||||||
|
if (! isset($CI_TOKEN)) die("no token");
|
||||||
|
const apiBase="https://circleci.com/api/v2/";
|
||||||
|
const webApp="https://app.circleci.com/";
|
||||||
|
const apiRepo="project/gh/#user#/#repo#";
|
||||||
|
const workflowName="build-workflow";
|
||||||
|
const jobName="pio-build";
|
||||||
|
const defaultBranch='master';
|
||||||
|
const defaultUser='wellenvogel';
|
||||||
|
const defaultRepo='esp32-nmea2000';
|
||||||
|
const TABLENAME="CIBUILDS";
|
||||||
|
const KEEPINTERVAL="30"; //days
|
||||||
|
|
||||||
|
function getTokenHeaders(){
|
||||||
|
global $CI_TOKEN;
|
||||||
|
return array('Circle-Token'=>$CI_TOKEN);
|
||||||
|
}
|
||||||
|
function getPipeline($pipeline){
|
||||||
|
$url=apiBase."/pipeline/$pipeline";
|
||||||
|
$token=getTokenHeaders();
|
||||||
|
return getJson($url,$token,true);
|
||||||
|
}
|
||||||
|
function getWorkflow($pipeline,$workflowName){
|
||||||
|
$url=apiBase."/pipeline/$pipeline/workflow";
|
||||||
|
$token=getTokenHeaders();
|
||||||
|
$pstate=getJson($url,$token,true);
|
||||||
|
if (! isset($pstate['items'])){
|
||||||
|
throw new Exception("no workflows in pipeline");
|
||||||
|
}
|
||||||
|
foreach ($pstate['items'] as $workflow){
|
||||||
|
if (isset($workflow['name']) && $workflow['name'] == $workflowName){
|
||||||
|
if (!isset($workflow['id'])){
|
||||||
|
throw new Exception("no workflow id found");
|
||||||
|
}
|
||||||
|
return $workflow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception("workflow $workflowName not found");
|
||||||
|
}
|
||||||
|
function getJob($pipeline,$workflow,$jobName){
|
||||||
|
$url=apiBase."/workflow/".$workflow."/job";
|
||||||
|
$token=getTokenHeaders();
|
||||||
|
$wstate=getJson($url,$token,true);
|
||||||
|
if (! isset($wstate['items'])){
|
||||||
|
throw new Exception("no jobs in workflow");
|
||||||
|
}
|
||||||
|
foreach ($wstate['items'] as $job){
|
||||||
|
if (isset($job['name']) && $job['name'] == $jobName){
|
||||||
|
if (! isset($job['id'])){
|
||||||
|
throw new Exception("no job id found");
|
||||||
|
}
|
||||||
|
return $job;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception("job $jobName not found");
|
||||||
|
}
|
||||||
|
function getJobStatus($pipeline,$wf=workflowName,$job=jobName){
|
||||||
|
$pstat=getPipeline($pipeline);
|
||||||
|
if (isset($pstat['error'])){
|
||||||
|
throw new Exception($pstat["error"]);
|
||||||
|
}
|
||||||
|
if (! isset($pstat['state'])){
|
||||||
|
throw new Exception("state not set");
|
||||||
|
}
|
||||||
|
if ($pstat['state'] != 'created'){
|
||||||
|
return $pstat;
|
||||||
|
}
|
||||||
|
$pipeline_id=$pstat['id'];
|
||||||
|
$pipeline_number=$pstat['number'];
|
||||||
|
$vcs=$pstat['vcs'];
|
||||||
|
$pstat=getWorkflow($pipeline,$wf);
|
||||||
|
$workflow_id=$pstat['id'];
|
||||||
|
$workflow_number=$pstat['workflow_number'];
|
||||||
|
$pstat=getJob($pipeline,$pstat['id'],$job);
|
||||||
|
$pstat['pipeline_id']=$pipeline_id;
|
||||||
|
$pstat['pipeline_number']=$pipeline_number;
|
||||||
|
$pstat['workflow_id']=$workflow_id;
|
||||||
|
$pstat['workflow_number']=$workflow_number;
|
||||||
|
if (isset($pstat['project_slug'])){
|
||||||
|
$pstat['status_url']=webApp."/pipelines/".
|
||||||
|
preg_replace('/^gh/','github',$pstat['project_slug'])."/".
|
||||||
|
$pipeline_number."/workflows/".$workflow_id."/jobs/".$pstat['job_number'];
|
||||||
|
}
|
||||||
|
$pstat['vcs']=$vcs;
|
||||||
|
return $pstat;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArtifacts($job,$slug){
|
||||||
|
$url=apiBase."/project/$slug/$job/artifacts";
|
||||||
|
return getJson($url,getTokenHeaders(),true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertPipeline($id,$requestParam){
|
||||||
|
$database=openDb();
|
||||||
|
if (! isset($database)) return false;
|
||||||
|
$param=$requestParam['parameters'];
|
||||||
|
try {
|
||||||
|
$status='created';
|
||||||
|
$tag=null;
|
||||||
|
if (isset($requestParam['tag'])) $tag=$requestParam['tag'];
|
||||||
|
$stmt = $database->prepare("INSERT into " . TABLENAME .
|
||||||
|
"(id,status,config,environment,buildflags,tag) VALUES (?,?,?,?,?,?)");
|
||||||
|
$stmt->bind_param("ssssss",
|
||||||
|
$id,
|
||||||
|
$status,
|
||||||
|
$param['config'],
|
||||||
|
$param['environment'],
|
||||||
|
$param['build_flags'],
|
||||||
|
$tag);
|
||||||
|
$stmt->execute();
|
||||||
|
$database->query("DELETE from ". TABLENAME. " where timestamp < NOW() - interval ". KEEPINTERVAL. " DAY");
|
||||||
|
return true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("insert pipeline $id failed: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updatePipeline($id,$status,$tag=null){
|
||||||
|
$database=openDb();
|
||||||
|
if (! isset($database)) return false;
|
||||||
|
try{
|
||||||
|
$stmt=null;
|
||||||
|
if ($tag != null){
|
||||||
|
$stmt=$database->prepare("UPDATE ".TABLENAME." SET status=?,tag=? where id=? and ( status <> ? or tag <> ?)");
|
||||||
|
$stmt->bind_param("sssss",$status,$tag,$id,$status,$tag);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$stmt=$database->prepare("UPDATE ".TABLENAME." SET status=? where id=? AND status <> ?");
|
||||||
|
$stmt->bind_param("sss",$status,$id,$status);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
}catch (Exception $e){
|
||||||
|
error_log("update pipeline $id failed: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPipeline($param)
|
||||||
|
{
|
||||||
|
$database=openDb();
|
||||||
|
if (!isset($database))
|
||||||
|
return false;
|
||||||
|
try {
|
||||||
|
$stmt = null;
|
||||||
|
$database->query("DELETE from ". TABLENAME. " where timestamp < NOW() - interval ". KEEPINTERVAL. " DAY");
|
||||||
|
if (isset($param['tag'])) {
|
||||||
|
$stmt = $database->prepare("SELECT id,UNIX_TIMESTAMP(timestamp) from " . TABLENAME .
|
||||||
|
" where status IN('success','running','created') and environment=? and buildflags=? and tag=? order by timestamp desc");
|
||||||
|
$stmt->bind_param("sss", $param['environment'], $param['buildflags'], $param['tag']);
|
||||||
|
} else {
|
||||||
|
$stmt = $database->prepare("SELECT id,UNIX_TIMESTAMP(timestamp) from " . TABLENAME .
|
||||||
|
" where status IN('success','running','created') and environment=? and buildflags=? order by timestamp desc");
|
||||||
|
$stmt->bind_param("ss", $param['environment'], $param['buildflags']);
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
$id=null;
|
||||||
|
$timestamp=null;
|
||||||
|
$stmt->bind_result($id,$timestamp);
|
||||||
|
if ($stmt->fetch()){
|
||||||
|
return array('pipeline'=>$id,'timestamp'=>$timestamp);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("find pipeline failed: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArtifactsForPipeline($pipeline,$wf=workflowName,$job=jobName){
|
||||||
|
$jstat=getJobStatus($pipeline,$wf,$job);
|
||||||
|
if (! isset($jstat['job_number'])){
|
||||||
|
throw new Exception("no job number");
|
||||||
|
}
|
||||||
|
if (! isset($jstat['status'])){
|
||||||
|
throw new Exception("no job status");
|
||||||
|
}
|
||||||
|
if ($jstat['status'] != 'success'){
|
||||||
|
throw new Exception("invalid job status ".$jstat['status']);
|
||||||
|
}
|
||||||
|
$astat=getArtifacts($jstat['job_number'],$jstat['project_slug']);
|
||||||
|
return $astat;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (isset($_REQUEST['api'])) {
|
||||||
|
$action = $_REQUEST['api'];
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
$par = array();
|
||||||
|
if ($action == 'status') {
|
||||||
|
addVars(
|
||||||
|
$par,
|
||||||
|
['pipeline', 'workflow', 'job'],
|
||||||
|
array('workflow' => workflowName, 'job' => jobName)
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
$pstat = getJobStatus($par['pipeline'], $par['workflow'], $par['job']);
|
||||||
|
if (isset($pstat['vcs'])){
|
||||||
|
updatePipeline($par['pipeline'],$pstat['status'],$pstat['vcs']['revision']);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
updatePipeline($par['pipeline'],$pstat['status']);
|
||||||
|
}
|
||||||
|
echo (json_encode($pstat));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$rt = array('status' => 'error', 'error' => $e->getMessage());
|
||||||
|
echo (json_encode($rt));
|
||||||
|
}
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if ($action == 'artifacts') {
|
||||||
|
addVars(
|
||||||
|
$par,
|
||||||
|
['pipeline', 'workflow', 'job'],
|
||||||
|
array('workflow' => workflowName, 'job' => jobName)
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
$astat = getArtifactsForPipeline($par['pipeline'], $par['workflow'], $par['job']);
|
||||||
|
echo (json_encode($astat));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo (json_encode(array('status' => 'error', 'error' => $e->getMessage())));
|
||||||
|
}
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if ($action == 'pipeline'){
|
||||||
|
addVars(
|
||||||
|
$par,
|
||||||
|
['number','user','repo'],
|
||||||
|
array('user'=>defaultUser,'repo'=>defaultRepo)
|
||||||
|
);
|
||||||
|
$url=apiBase."/".replaceVars(apiRepo,fillUserAndRepo(null,$par))."/pipeline/".$par['number'];
|
||||||
|
$rt=getJson($url,getTokenHeaders(),true);
|
||||||
|
echo(json_encode($rt));
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if ($action == 'pipelineuuid'){
|
||||||
|
addVars(
|
||||||
|
$par,
|
||||||
|
['pipeline']
|
||||||
|
);
|
||||||
|
$url=apiBase."/pipeline/".$par['pipeline'];
|
||||||
|
$rt=getJson($url,getTokenHeaders(),true);
|
||||||
|
echo(json_encode($rt));
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if ($action == 'start'){
|
||||||
|
addVars(
|
||||||
|
$par,
|
||||||
|
['environment','buildflags','config','suffix','user','repo'],
|
||||||
|
array('suffix'=>'',
|
||||||
|
'config'=>'{}',
|
||||||
|
'user'=>defaultUser,
|
||||||
|
'repo'=>defaultRepo,
|
||||||
|
'buildflags'=>''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$requestParam=array(
|
||||||
|
'parameters'=> array(
|
||||||
|
'run_build'=>true,
|
||||||
|
'environment'=>$par['environment'],
|
||||||
|
'suffix'=>$par['suffix'],
|
||||||
|
'config'=>$par['config'],
|
||||||
|
'build_flags'=>$par['buildflags']
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (isset($_REQUEST['tag'])){
|
||||||
|
$requestParam['tag']=safeName($_REQUEST['tag']);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$requestParam['branch']=defaultBranch;
|
||||||
|
}
|
||||||
|
$userRepo=fillUserAndRepo(null,$par);
|
||||||
|
$url=apiBase."/".replaceVars(apiRepo,$userRepo)."/pipeline";
|
||||||
|
$rt=getJson($url,getTokenHeaders(),true,$requestParam);
|
||||||
|
insertPipeline($rt['id'],$requestParam);
|
||||||
|
echo (json_encode($rt));
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
throw new Exception("invalid api $action");
|
||||||
|
}
|
||||||
|
if (isset($_REQUEST['download'])) {
|
||||||
|
$pipeline = $_REQUEST['download'];
|
||||||
|
$par = array('pipeline' => $pipeline);
|
||||||
|
addVars(
|
||||||
|
$par,
|
||||||
|
['workflow', 'job'],
|
||||||
|
array('workflow' => workflowName, 'job' => jobName)
|
||||||
|
);
|
||||||
|
$astat = getArtifactsForPipeline($par['pipeline'], $par['workflow'], $par['job']);
|
||||||
|
if (!isset($astat['items']) || count($astat['items']) < 1) {
|
||||||
|
die("no artifacts for job");
|
||||||
|
}
|
||||||
|
$dlurl = $astat['items'][0]['url'];
|
||||||
|
#echo("DL: $dlurl\n");
|
||||||
|
header('Content-Disposition: attachment; filename="'.$astat['items'][0]['path'].'"');
|
||||||
|
proxy($dlurl);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if (isset($_REQUEST['find'])){
|
||||||
|
$par=array();
|
||||||
|
addVars($par,['environment','buildflags']);
|
||||||
|
if (isset($_REQUEST['tag'])) $par['tag']=$_REQUEST['tag'];
|
||||||
|
$rt=findPipeline($par);
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
if (!$rt){
|
||||||
|
$rt=array();
|
||||||
|
}
|
||||||
|
$rt['status']='OK';
|
||||||
|
echo(json_encode($rt));
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
die("no action");
|
||||||
|
} catch (HTTPErrorException $h) {
|
||||||
|
header($_SERVER['SERVER_PROTOCOL'] . " " . $h->code . " " . $h->getMessage());
|
||||||
|
die($h->getMessage());
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header($_SERVER['SERVER_PROTOCOL'] . ' 500 ' . $e->getMessage());
|
||||||
|
die($e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
$allowed=array(
|
||||||
|
'user'=> array('wellenvogel'),
|
||||||
|
'repo'=> array('esp32-nmea2000')
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
function fillUserAndRepo($vars=null,$source=null){
|
||||||
|
global $allowed;
|
||||||
|
if ($vars == null) {
|
||||||
|
$vars=array();
|
||||||
|
}
|
||||||
|
if ($source == null){
|
||||||
|
$source=$_REQUEST;
|
||||||
|
}
|
||||||
|
foreach (array('user','repo') as $n){
|
||||||
|
if (! isset($source[$n])){
|
||||||
|
throw new Exception("missing parameter $n");
|
||||||
|
}
|
||||||
|
$v=$source[$n];
|
||||||
|
$av=$allowed[$n];
|
||||||
|
if (! in_array($v,$av)){
|
||||||
|
throw new Exception("value $v for $n not allowed");
|
||||||
|
}
|
||||||
|
$vars[$n]=$v;
|
||||||
|
}
|
||||||
|
return $vars;
|
||||||
|
}
|
||||||
|
?>
|
|
@ -0,0 +1 @@
|
||||||
|
{"root:board":"m5stack-atom-generic","root:board:m5groove":"CAN","root:board:m5groove:m5groovecan":"M5_CANUNIT"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"root:board":"m5stack-atom-generic","root:board:m5lightbase":"M5_GPS_KIT","root:board:m5groove":"CAN","root:board:m5groove:m5groovecan":"M5_CANUNIT"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"root:board":"m5stack-atom-generic","root:board:m5lightbase":"M5_SERIAL_KIT_232","root:board:m5groove":"CAN","root:board:m5groove:m5groovecan":"M5_CANUNIT"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"root:board":"m5stack-atom-generic","root:board:m5lightbase":"M5_SERIAL_KIT_485","root:board:m5groove":"CAN","root:board:m5groove:m5groovecan":"M5_CANUNIT"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"root:board":"m5stack-atom-generic","root:board:m5lightbase":"M5_CAN_KIT","root:board:m5groove":"Serial","root:board:m5groove:m5grooveserial":"tail485"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"root:board":"m5stickc-atom-generic","root:board:m5groove":"CAN","root:board:m5groove:m5groovecan":"M5_CANUNIT"}
|
|
@ -0,0 +1 @@
|
||||||
|
{"root:board:serial2":false,"root:board:led":false,"root:board":"nodemcu-generic","root:board:can":true,"root:board:can:tx":5,"root:board:can:rx":4,"root:board:resetButton":true,"root:board:resetButton:button":0,"root:board:resetButton:resetButtonMode":0,"root:board:resetButton:resetButtonPUD":true,"root:board:serial1":true,"root:board:serial1:type":"rx","root:board:serial1:type:RX":16}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
echo "CREATEDB\n";
|
||||||
|
include("functions.php");
|
||||||
|
$database=openDb();
|
||||||
|
if (! isset($database)){
|
||||||
|
die("ERROR: db not set\n");
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
$sql="CREATE TABLE `CIBUILDS` (
|
||||||
|
`id` varchar(255) NOT NULL,
|
||||||
|
`tag` varchar(255),
|
||||||
|
`status` varchar(255) NOT NULL,
|
||||||
|
`config` longtext,
|
||||||
|
`environment` VARCHAR(255),
|
||||||
|
`buildflags` longtext,
|
||||||
|
`timestamp` timestamp ,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
);";
|
||||||
|
|
||||||
|
echo "executing $sql<br>";
|
||||||
|
$rt=$database->query($sql);
|
||||||
|
echo "execute OK<br>";
|
||||||
|
} catch (Exception $e){
|
||||||
|
echo "ERROR: ".$e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,236 @@
|
||||||
|
<?php
|
||||||
|
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||||
|
include("../../../cibuild_db.php");
|
||||||
|
function openDb(){
|
||||||
|
if (! defined('database::SERVER')) return null;
|
||||||
|
try{
|
||||||
|
$db=new mysqli(database::SERVER, database::USER, database::PASS,database::DB);
|
||||||
|
#$db->query("SET CHARACTER SET 'utf8'");
|
||||||
|
return $db;
|
||||||
|
}catch (Exception $e){
|
||||||
|
error_log("openDB error $e");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function safeName($name)
|
||||||
|
{
|
||||||
|
return preg_replace('[^0-9_a-zA-Z.-]', '', $name);
|
||||||
|
}
|
||||||
|
function replaceVars($str, $vars)
|
||||||
|
{
|
||||||
|
foreach ($vars as $n => &$v) {
|
||||||
|
$str = str_replace("#" . $n . "#", $v, $str);
|
||||||
|
}
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
if (!function_exists('getallheaders')) {
|
||||||
|
function getallheaders()
|
||||||
|
{
|
||||||
|
$headers = [];
|
||||||
|
foreach ($_SERVER as $name => $value) {
|
||||||
|
if (substr($name, 0, 5) == 'HTTP_') {
|
||||||
|
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function addVars(&$vars,$names,$defaults=null){
|
||||||
|
foreach ($names as $n){
|
||||||
|
$v=null;
|
||||||
|
if (! isset($_REQUEST[$n])){
|
||||||
|
if ($defaults == null || ! isset($defaults[$n])) throw new Exception("missing parameter $n");
|
||||||
|
$v=$defaults[$n];
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$v=safeName($_REQUEST[$n]);
|
||||||
|
}
|
||||||
|
$vars[$n]=$v;
|
||||||
|
}
|
||||||
|
return $vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
function curl_exec_follow(/*resource*/ $ch, /*int*/ &$maxredirect = null) {
|
||||||
|
$mr = $maxredirect === null ? 5 : intval($maxredirect);
|
||||||
|
#echo("###handling redirects $mr\n");
|
||||||
|
if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off') && false) {
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $mr > 0);
|
||||||
|
curl_setopt($ch, CURLOPT_MAXREDIRS, $mr);
|
||||||
|
} else {
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||||
|
if ($mr > 0) {
|
||||||
|
$newurl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||||
|
$rch = curl_copy_handle($ch);
|
||||||
|
curl_setopt($rch, CURLOPT_HEADER, true);
|
||||||
|
#curl_setopt($rch, CURLOPT_NOBODY, true);
|
||||||
|
curl_setopt($rch, CURLOPT_FORBID_REUSE, false);
|
||||||
|
curl_setopt($rch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
do {
|
||||||
|
#echo("###trying $newurl\n");
|
||||||
|
curl_setopt($rch, CURLOPT_URL, $newurl);
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $newurl);
|
||||||
|
$header = curl_exec($rch);
|
||||||
|
if (curl_errno($rch)) {
|
||||||
|
$code = 0;
|
||||||
|
} else {
|
||||||
|
$code = curl_getinfo($rch, CURLINFO_HTTP_CODE);
|
||||||
|
#echo("###code=$code\n");
|
||||||
|
if ($code == 301 || $code == 302) {
|
||||||
|
preg_match('/Location:(.*?)\n/', $header, $matches);
|
||||||
|
$newurl = trim(array_pop($matches));
|
||||||
|
} else {
|
||||||
|
if ($code >= 300){
|
||||||
|
trigger_error("HTTP error $code");
|
||||||
|
}
|
||||||
|
$code = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while ($code && --$mr);
|
||||||
|
curl_close($rch);
|
||||||
|
if (!$mr) {
|
||||||
|
if ($maxredirect === null) {
|
||||||
|
trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.', E_USER_WARNING);
|
||||||
|
} else {
|
||||||
|
$maxredirect = 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $newurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curl_setopt(
|
||||||
|
$ch,
|
||||||
|
CURLOPT_HEADERFUNCTION,
|
||||||
|
function ($curl, $header) {
|
||||||
|
header($header);
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
curl_setopt(
|
||||||
|
$ch,
|
||||||
|
CURLOPT_WRITEFUNCTION,
|
||||||
|
function ($curl, $body) {
|
||||||
|
echo $body;
|
||||||
|
return strlen($body);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
header('Access-Control-Allow-Origin:*');
|
||||||
|
return curl_exec($ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFwHeaders($aheaders=null){
|
||||||
|
$headers=getallheaders();
|
||||||
|
$FWHDR = ['User-Agent'];
|
||||||
|
$outHeaders = array();
|
||||||
|
foreach ($FWHDR as $k) {
|
||||||
|
if (isset($headers[$k])) {
|
||||||
|
array_push($outHeaders, "$k: $headers[$k]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($aheaders != null){
|
||||||
|
foreach ($aheaders as $hk => $hv){
|
||||||
|
array_push($outHeaders,"$hk: $hv");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $outHeaders;
|
||||||
|
}
|
||||||
|
function getJson($url,$headers=null,$doThrow=false,$jsonData=null){
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt($curl, CURLOPT_URL,$url);
|
||||||
|
curl_setopt($curl,CURLOPT_RETURNTRANSFER, true);
|
||||||
|
$outHeaders=getFwHeaders($headers);
|
||||||
|
if ($jsonData != null){
|
||||||
|
$json=json_encode($jsonData);
|
||||||
|
array_push($outHeaders,"Content-Type: application/json");
|
||||||
|
array_push($outHeaders,"Content-length: ".strlen($json));
|
||||||
|
curl_setopt($curl, CURLOPT_POSTFIELDS,$json);
|
||||||
|
}
|
||||||
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $outHeaders);
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||||
|
#echo("curl exec for $url:$response:$httpcode\n");
|
||||||
|
if($e = curl_error($curl)) {
|
||||||
|
curl_close($curl);
|
||||||
|
if ($doThrow) throw new Exception($e);
|
||||||
|
return array('error'=>$e);
|
||||||
|
} else {
|
||||||
|
if ($httpcode >= 300){
|
||||||
|
curl_close($curl);
|
||||||
|
if ($doThrow) throw new Exception("HTTP error $httpcode");
|
||||||
|
return array('error'=>"HTTP code ".$httpcode);
|
||||||
|
}
|
||||||
|
curl_close($curl);
|
||||||
|
return json_decode($response, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class HTTPErrorException extends Exception{
|
||||||
|
public $code=0;
|
||||||
|
public function __construct($c,$text){
|
||||||
|
parent::__construct($text);
|
||||||
|
$this->code=$c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function proxy_impl($url, $timeout=30,$headers=null,$num = 5)
|
||||||
|
{
|
||||||
|
$nexturl=$url;
|
||||||
|
while ($num > 0 && $nexturl != null) {
|
||||||
|
$num--;
|
||||||
|
$code=0;
|
||||||
|
$ch = curl_init($nexturl);
|
||||||
|
$nexturl=null;
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT,$timeout);
|
||||||
|
if ($headers != null){
|
||||||
|
curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
|
||||||
|
}
|
||||||
|
curl_setopt(
|
||||||
|
$ch,
|
||||||
|
CURLOPT_HEADERFUNCTION,
|
||||||
|
function ($curl, $header) use(&$nexturl,&$code){
|
||||||
|
#echo ("###header:$header\n");
|
||||||
|
if ($code == 0){
|
||||||
|
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||||
|
}
|
||||||
|
#echo ("???code=$code\n");
|
||||||
|
if ($code == 301 || $code == 302) {
|
||||||
|
if(preg_match('/[Ll]ocation:(.*?)\n/', $header, $matches)){
|
||||||
|
$nexturl = trim(array_pop($matches));
|
||||||
|
#echo("???nexturl=$nexturl\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($code != 0 && $code < 300){
|
||||||
|
header($header);
|
||||||
|
}
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
curl_setopt(
|
||||||
|
$ch,
|
||||||
|
CURLOPT_WRITEFUNCTION,
|
||||||
|
function ($curl, $body) use(&$code) {
|
||||||
|
if ($code != 0 && $code < 300){
|
||||||
|
#echo ("### body part " . strlen($body)."\n");
|
||||||
|
echo $body;
|
||||||
|
return strlen($body);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$rs = curl_exec($ch);
|
||||||
|
#echo ("###code=$code\n");
|
||||||
|
curl_close($ch);
|
||||||
|
if ($nexturl == null){
|
||||||
|
if ($code != 200) throw new HTTPErrorException($code,"HTTP status $code");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new HTTPErrorException(500,"too many redirects");
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxy($url)
|
||||||
|
{
|
||||||
|
header('Access-Control-Allow-Origin:*');
|
||||||
|
return proxy_impl($url,30,getFwHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,140 @@
|
||||||
|
const getParam = (key,opt_default) => {
|
||||||
|
if (opt_default === undefined) opt_default="";
|
||||||
|
let value = RegExp("" + key + "[^&]+").exec(window.location.search);
|
||||||
|
// Return the unescaped value minus everything starting from the equals sign or an empty string
|
||||||
|
return decodeURIComponent(!!value ? value.toString().replace(/^[^=]+./, "") : opt_default);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* add an HTML element
|
||||||
|
* @param {*} type
|
||||||
|
* @param {*} clazz
|
||||||
|
* @param {*} parent
|
||||||
|
* @param {*} text
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const addEl = (type, clazz, parent, text) => {
|
||||||
|
let el = document.createElement(type);
|
||||||
|
if (clazz) {
|
||||||
|
if (!(clazz instanceof Array)) {
|
||||||
|
clazz = clazz.split(/ */);
|
||||||
|
}
|
||||||
|
clazz.forEach(function (ce) {
|
||||||
|
el.classList.add(ce);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (text !== undefined) el.textContent = text;
|
||||||
|
if (parent) parent.appendChild(el);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* call a function for each matching element
|
||||||
|
* @param {*} selector
|
||||||
|
* @param {*} cb
|
||||||
|
*/
|
||||||
|
const forEachEl = (selector, cb) => {
|
||||||
|
let arr = document.querySelectorAll(selector);
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
cb(arr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setButtons=(config)=>{
|
||||||
|
for (let k in config){
|
||||||
|
let bt=document.getElementById(k);
|
||||||
|
if (bt){
|
||||||
|
bt.addEventListener('click',config[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fillValues=(values,items)=>{
|
||||||
|
items.forEach((it)=>{
|
||||||
|
let e=document.getElementById(it);
|
||||||
|
if (e){
|
||||||
|
if (e.tagName == 'INPUT') values[it]=e.value;
|
||||||
|
if (e.tagName == 'DIV' || e.tagName == 'SPAN') values [it]=e.textContent;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const setValue=(id,value)=>{
|
||||||
|
let el=document.getElementById(id);
|
||||||
|
if (! el) return;
|
||||||
|
if (el.tagName == 'DIV' || el.tagName == 'SPAN' || el.tagName == 'P'){
|
||||||
|
el.textContent=value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (el.tagName == 'INPUT'){
|
||||||
|
el.value=value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (el.tagName.match(/^H[0-9]/)){
|
||||||
|
el.textContent=value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (el.tagName == 'A'){
|
||||||
|
el.setAttribute('href',value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const setValues=(data,translations)=>{
|
||||||
|
for (let k in data){
|
||||||
|
let id=k;
|
||||||
|
if (translations){
|
||||||
|
let t=translations[k];
|
||||||
|
if (t !== undefined) id=t;
|
||||||
|
}
|
||||||
|
setValue(id,data[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const buildUrl=(url,pars)=>{
|
||||||
|
let delim=(url.match("[?]"))?"&":"?";
|
||||||
|
for (let k in pars){
|
||||||
|
url+=delim;
|
||||||
|
delim="&";
|
||||||
|
url+=encodeURIComponent(k);
|
||||||
|
url+="=";
|
||||||
|
url+=encodeURIComponent(pars[k]);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
const fetchJson=(url,pars)=>{
|
||||||
|
let furl=buildUrl(url,pars);
|
||||||
|
return fetch(furl).then((rs)=>rs.json());
|
||||||
|
}
|
||||||
|
const setVisible=(el,vis,useParent)=>{
|
||||||
|
if (typeof(el) !== 'object') el=document.getElementById(el);
|
||||||
|
if (! el) return;
|
||||||
|
if (useParent) el=el.parentElement;
|
||||||
|
if (! el) return;
|
||||||
|
if (vis) el.classList.remove('hidden');
|
||||||
|
else el.classList.add('hidden');
|
||||||
|
}
|
||||||
|
const enableEl=(id,en)=>{
|
||||||
|
let el=document.getElementById(id);
|
||||||
|
if (!el) return;
|
||||||
|
if (en) el.disabled=false;
|
||||||
|
else el.disabled=true;
|
||||||
|
}
|
||||||
|
const fillSelect=(el,values)=>{
|
||||||
|
if (typeof(el) !== 'object') el=document.getElementById(el);
|
||||||
|
if (! el) return;
|
||||||
|
el.textContent='';
|
||||||
|
let kf=(values instanceof Array)?(k)=>values[k]:(k)=>k;
|
||||||
|
for (let k in values){
|
||||||
|
let o=addEl('option','',el);
|
||||||
|
o.setAttribute('value',kf(k));
|
||||||
|
o.textContent=values[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const readFile=(file,optAsText)=>{
|
||||||
|
return new Promise((resolve,reject)=>{
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.addEventListener('load', function (e) {
|
||||||
|
resolve(e.target.result);
|
||||||
|
|
||||||
|
});
|
||||||
|
reader.addEventListener('error',(e)=>reject(e));
|
||||||
|
if (optAsText) reader.readAsText(file);
|
||||||
|
else reader.readAsBinaryString(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export { readFile, getParam, addEl, forEachEl,setButtons,fillValues, setValue,setValues,buildUrl,fetchJson,setVisible, enableEl,fillSelect }
|
|
@ -22,3 +22,24 @@ body {
|
||||||
font-family: system-ui;
|
font-family: system-ui;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
#loading{
|
||||||
|
height: 6em;
|
||||||
|
}
|
||||||
|
#loadingText{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#loadingFrame{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.hidden{
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.uploadFile{
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.uploadButton{
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue