Merge branch 'wellenvogel:master' into master

This commit is contained in:
Norbert Walter 2024-03-24 11:52:29 +01:00 committed by GitHub
commit f00064ece8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 2144 additions and 538 deletions

View File

@ -38,7 +38,8 @@ What is included
* a WEB UI to configure the gateway and to show the data that has been received
* a USB Actisense to NMEA2000 gateway
* a NMEA2000 to USB Actisense gateway
* starting with 201311xx some I2C Sensors
* starting with 201311xx some [I2C Sensors](doc/Sensors.md)
* starting with 20240324 [SSI rotary encoders](doc/Sensors.md)
For the details of the mapped PGNs and NMEA sentences refer to [Conversions](doc/Conversions.pdf).
@ -165,10 +166,26 @@ For details refer to the [example description](lib/exampletask/Readme.md).
Changelog
---------
[20240324](../../releases/tag/20240324)
**********
* add [SSI rotary encoders](doc/Sensors.md)
* add some options to the converter (RMC rate, RSA parameters)
* support for the [M5 Atomic PortABC](https://shop.m5stack.com/products/atomic-portabc-extension-base) - more grove ports [#58](../../issues/58)
* some restructuring in the hardware definitions
* add SSID to status page [#37](../../issues/37)
* allow to attach i2c sensors to the grove ports in the [build service](doc/BuildService.md)
* add calset and calval config types<br>
This requires to write current values of a sensor at the [api](../lib/api/GwApi.h#L191) with setCalibrationValue.
The user can then bring up a calibration dialog and can set the current value as the config value.
* change log flushing to USB port that prevented ESP32 S3 based boards to start if no USB connection was available
* prevent the Web UI from appearing frozen if there was a large amount of invalid NMEA data received [#60](../../issues/60)
[20231228](../../releases/tag/20231228)
**********
* lock AsyncTCP-esphome to 2.0.1 to avoid compile errors
* own main loop to avoid deadlocks with serial send in user tasks
[20231105](../../releases/tag/20231105)
**********
* support for ESP32-S3

View File

@ -3,6 +3,7 @@ Hardware Configurations
This pages describes a couple of the potential hardware configurations you could use with the code.
Finally this list is not complete and you can easily define your own set up by adding definitions.
Hint: all prebuild binaries can be found at [releases](https://github.com/wellenvogel/esp32-nmea2000/releases).
Additionally you can use the [online build service](BuildService.md) to create binaries for a lot of hardware combinations.
The "Build Defines" describe which of the hardware definitions from [Hardware.h](../lib/hardware/Hardware.h) are used.
M5 Atom CAN

38
doc/Sensors.md Normal file
View File

@ -0,0 +1,38 @@
Sensors
=======
The software contains support for a couple of sensors (starting from [20231228](../../releases/tag/20231228) and extend in consecutive releases).
This includes some I2C Sensors and SSI rotary encoders.
To connect sensors the following steps are necessary:
1. Check for the supported sensors and decide which base you would like to use. For i2c sensors you can typically connect a couple of them to one i2c bus - so the M5 grove connector would suit this well. If you are not directly using the M5 modules you may need to check the voltages: The M5 grove carries 5V but the logic levels are 3.3V.
Check e.g. the [description of the M5 atom lite](https://docs.m5stack.com/en/core/atom_lite) for the pinout of the grove port.
2. Use the [online build service](BuildService.md) to create a binary that matches your configuration and flash it.
3. Use the configuration to fine tune the parameters for the NMEA2000 messages and the NMEA0183 messages. Potentially you also can adjust some calibration values.
Configuration and Measure Flow
------------------------------
During the building of the binary a couple of compile flags will control which buses (i2c, spi) and which sensors will be built into the binary.
On startup the software will try to initialize the sensors and will start the periodic measurement tasks.
Measured sensor data will always be sent as NMEA2000. For each sensor you can additionally define some mapping to NMEA0183 XDR transducer values (if there is no native NMEA0183 record for them).
To ensure that the sensor data will be shown at the "data" page this conversion is necessary (i.e. if you disable this conversion sensor data will still be sent to the NMEA2000 bus - but will not be shown at the data page).
For each sensor you can separately enable and disable it in the configuration.
You can also typically select the NMEA2000 instance id for the sensor value and the measurement interval.
Implementation
--------------
Sensors are implemented in [iictask](../lib/iictask) and in [spitaks](../lib/spitask/).
They are implemented as [usertasks](../lib/exampletask/Readme.md).
Bus Usage
---------
When selecting sensors to be connected at the M5 grove ports in the [online build service](BuildService.md) the system will select the appropriate bus (i2c-1, i2c-2) by it's own. As you can have up to 4 grove ports (one at the device and 3 by using the [M5 Atomic PortABC](https://shop.m5stack.com/products/atomic-portabc-extension-base)) you can use both available i2c buses (and still utilize other groves for serial or CAN).
Implemented Sensors
-------------------
* [BME280](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf): temperature/humidity/pressure [PGNs: 130314,130312, 130313]
* [QMP6988](https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/enviii/QMP6988%20Datasheet.pdf): pressure [PGN: 130314]
* [SHT30](https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/SHT3x_Datasheet_digital.pdf): temperature and humidity [PGNs: 130312, 130313]
* [M5-ENV3](https://docs.m5stack.com/en/unit/envIII): combination of QMP6988 and SHT30 [PGNs: 130314,130312, 130313]
* [DMS22B](https://www.mouser.de/datasheet/2/54/bour_s_a0011704065_1-2262614.pdf)[since 20240324]: SSI rotary encoder (needs level shifters to / from 5V!) [PGN: 127245]

View File

@ -22,6 +22,8 @@ CFG_INCLUDE='GwConfigDefinitions.h'
CFG_INCLUDE_IMPL='GwConfigDefImpl.h'
XDR_INCLUDE='GwXdrTypeMappings.h'
TASK_INCLUDE='GwUserTasks.h'
GROVE_CONFIG="GwM5GroveGen.h"
GROVE_CONFIG_IN="lib/hardware/GwM5Grove.in"
EMBEDDED_INCLUDE="GwEmbeddedFiles.h"
def getEmbeddedFiles(env):
@ -74,7 +76,7 @@ def generateFile(infile,outfile,callback,inMode='rb',outMode='w'):
print("creating %s"%outfile)
oh=None
with open(infile,inMode) as ch:
with open(outfile,'w') as oh:
with open(outfile,outMode) as oh:
try:
callback(ch,oh,inFile=infile)
oh.close()
@ -185,34 +187,13 @@ def generateCfg(inFile,outFile,impl):
name=item.get('name')
if name is None:
continue
data+=' configs[%d]=\n'%(idx)
data+=' configs[%d]='%(idx)
idx+=1
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+=" new GwConfigInterface(%s,\"%s\",%s);\n"%(name,item.get('default'),secret)
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)
def labelFilter(label):
@ -284,6 +265,41 @@ def generateXdrMappings(fp,oh,inFile=''):
oh.write(" #define %s %s\n"%(define,str(v)))
idx+=1
class Grove:
def __init__(self,name) -> None:
self.name=name
def _ss(self,z=False):
if z:
return self.name
return self.name if self.name is not 'Z' else ''
def _suffix(self):
return '_'+self.name if self.name is not 'Z' else ''
def replace(self,line):
if line is None:
return line
return line.replace('$G$',self._ss()).replace('$Z$',self._ss(True)).replace('$GS$',self._suffix())
def generateGroveDefs(inh,outh,inFile=''):
GROVES=[Grove('Z'),Grove('A'),Grove('B'),Grove('C')]
definition=[]
started=False
def writeConfig():
for grove in GROVES:
for cl in definition:
outh.write(grove.replace(cl))
for line in inh:
if re.match(" *#GROVE",line):
started=True
if len(definition) > 0:
writeConfig()
definition=[]
continue
if started:
definition.append(line)
if len(definition) > 0:
writeConfig()
userTaskDirs=[]
@ -444,6 +460,7 @@ def prebuild(env):
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
generateFile(os.path.join(basePath(),GROVE_CONFIG_IN),os.path.join(outPath(),GROVE_CONFIG),generateGroveDefs,inMode='r')
version="dev"+datetime.now().strftime("%Y%m%d")
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])

View File

@ -182,6 +182,13 @@ class GwApi{
* 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;
/**
* set a value that is used for calibration in config values
* for cfg types calset, calvalue
* @param name: the config name this value is used for
* @param value: the current value
*/
virtual void setCalibrationValue(const String &name, double value);
/**
* not thread safe methods

View File

@ -192,7 +192,8 @@ class GwBoatData{
GWBOATDATA(double,HDOP,4000,formatDop)
GWBOATDATA(double,PDOP,4000,formatDop)
GWBOATDATA(double,VDOP,4000,formatDop)
GWBOATDATA(double,RPOS,4000,formatCourse) //RudderPosition
GWBOATDATA(double,RPOS,4000,formatWind) //RudderPosition
GWBOATDATA(double,PRPOS,4000,formatWind) //second rudder pos
GWBOATDATA(double,LAT,4000,formatLatitude)
GWBOATDATA(double,LON,4000,formatLongitude)
GWBOATDATA(double,ALT,4000,formatFixed0) //altitude

View File

@ -106,20 +106,34 @@ void GwChannel::setImpl(GwChannelInterface *impl){
}
void GwChannel::updateCounter(const char *msg, bool out)
{
char key[6];
char key[7];
key[0]=0;
if (msg[0] == '$')
{
strncpy(key, &msg[3], 3);
for (int i=0;i<6 && msg[i] != 0;i++){
if (i>=3) {
if (isalnum(msg[i]))key[i-3]=msg[i];
else key[i-3]='_';
}
key[i-2]=0;
}
key[3] = 0;
}
else if (msg[0] == '!')
{
strncpy(key, &msg[1], 5);
for (int i=0;i<6 && msg[i] != 0;i++){
if (i>=1) {
if (isalnum(msg[i]))key[i-1]=msg[i];
else key[i-1]='_';
}
key[i]=0;
}
key[5] = 0;
}
else{
return;
}
if (key[0] == 0) return;
if (out){
countOut->add(key);
}
@ -159,7 +173,7 @@ void GwChannel::toJson(GwJsonDocument &doc){
if (countIn) countIn->toJson(doc);
}
String GwChannel::toString(){
String rt="CH:"+name;
String rt="CH"+name+"("+sourceId+"):";
rt+=enabled?"[ena]":"[dis]";
rt+=NMEAin?"in,":"";
rt+=NMEAout?"out,":"";

View File

@ -1,9 +1,33 @@
#include "GwChannelList.h"
#include "GwApi.h"
//check for duplicate groove usages
#define GW_PINDEFS
#include "GwHardware.h"
#include "GwSocketServer.h"
#include "GwSerial.h"
#include "GwTcpClient.h"
class SerInit{
public:
int serial=-1;
int rx=-1;
int tx=-1;
int mode=-1;
int fixedBaud=-1;
SerInit(int s,int r,int t, int m, int b=-1):
serial(s),rx(r),tx(t),mode(m),fixedBaud(b){}
};
std::vector<SerInit> serialInits;
#define CFG_SERIAL(ser,...) \
__MSG("serial config " #ser); \
static GwInitializer<SerInit> __serial ## ser ## _init \
(serialInits,SerInit(ser,__VA_ARGS__));
#ifdef _GWI_SERIAL1
CFG_SERIAL(1,_GWI_SERIAL1)
#endif
#ifdef _GWI_SERIAL2
CFG_SERIAL(2,_GWI_SERIAL2)
#endif
class GwSerialLog : public GwLogWriter
{
static const size_t bufferSize = 4096;
@ -11,12 +35,13 @@ class GwSerialLog : public GwLogWriter
int wp = 0;
GwSerial *writer;
bool disabled = false;
long flushTimeout=200;
public:
GwSerialLog(GwSerial *writer, bool disabled)
GwSerialLog(GwSerial *writer, bool disabled,long flushTimeout=200)
{
this->writer = writer;
this->disabled = disabled;
this->flushTimeout=flushTimeout;
logBuffer = new char[bufferSize];
wp = 0;
}
@ -39,16 +64,63 @@ public:
{
while (handled < wp)
{
writer->flush();
if ( !writer->flush(flushTimeout)) break;
size_t rt = writer->sendToClients(logBuffer + handled, -1, true);
handled += rt;
}
if (handled < wp){
if (handled > 0){
memmove(logBuffer,logBuffer+handled,wp-handled);
wp-=handled;
logBuffer[handled]=0;
}
return;
}
}
wp = 0;
logBuffer[0] = 0;
}
};
template<typename T>
class SerialWrapper : public GwChannelList::SerialWrapperBase{
private:
template<class C>
void beginImpl(C *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){}
void beginImpl(HardwareSerial *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){
s->begin(baud,config,rxPin,txPin);
}
template<class C>
void setError(C* s, GwLog *logger){}
void setError(HardwareSerial *s,GwLog *logger){
LOG_DEBUG(GwLog::LOG,"enable serial errors for channel %d",id);
s->onReceiveError([logger,this](hardwareSerial_error_t err){
LOG_DEBUG(GwLog::ERROR,"serial error on id %d: %d",this->id,(int)err);
});
}
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
void beginImpl(HWCDC *s,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1){
s->begin(baud);
}
#endif
T *serial;
int id;
public:
SerialWrapper(T* s,int i):serial(s),id(i){}
virtual void begin(GwLog* logger,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1) override{
beginImpl(serial,baud,config,rxPin,txPin);
setError(serial,logger);
};
virtual Stream *getStream() override{
return serial;
}
virtual int getId() override{
return id;
}
};
GwChannelList::GwChannelList(GwLog *logger, GwConfigHandler *config){
this->logger=logger;
this->config=config;
@ -101,8 +173,18 @@ static SerialParam *getSerialParam(int id){
}
return nullptr;
}
void GwChannelList:: addSerial(HardwareSerial *stream,int id,int type,int rx,int tx){
void GwChannelList::addSerial(int id, int rx, int tx, int type){
if (id == 1){
addSerial(new SerialWrapper<decltype(Serial1)>(&Serial1,SERIAL1_CHANNEL_ID),type,rx,tx);
return;
}
if (id == 2){
addSerial(new SerialWrapper<decltype(Serial2)>(&Serial2,SERIAL2_CHANNEL_ID),type,rx,tx);
return;
}
LOG_DEBUG(GwLog::ERROR,"invalid serial config with id %d",id);
}
void GwChannelList::addSerial(GwChannelList::SerialWrapperBase *stream,int type,int rx,int tx){
const char *mode=nullptr;
switch (type)
{
@ -123,9 +205,16 @@ void GwChannelList:: addSerial(HardwareSerial *stream,int id,int type,int rx,int
LOG_DEBUG(GwLog::ERROR,"unknown serial type %d",type);
return;
}
addSerial(stream,id,mode,rx,tx);
addSerial(stream,mode,rx,tx);
}
void GwChannelList::addSerial(GwChannelList::SerialWrapperBase *serialStream,const String &mode,int rx,int tx){
int id=serialStream->getId();
for (auto &&it:theChannels){
if (it->isOwnSource(id)){
LOG_DEBUG(GwLog::ERROR,"trying to re-add serial id=%d, ignoring",id);
return;
}
}
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);
@ -161,8 +250,8 @@ void GwChannelList::addSerial(HardwareSerial *serialStream,int id,const String &
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);
serialStream->begin(logger,config->getInt(param->baud,115200),SERIAL_8N1,rx,tx);
GwSerial *serial = new GwSerial(logger, serialStream->getStream(), id, canRead);
LOG_DEBUG(GwLog::LOG, "starting serial %d ", id);
GwChannel *channel = new GwChannel(logger, param->name, id);
channel->setImpl(serial);
@ -179,7 +268,36 @@ void GwChannelList::addSerial(HardwareSerial *serialStream,int id,const String &
LOG_DEBUG(GwLog::LOG, "%s", channel->toString().c_str());
theChannels.push_back(channel);
}
void GwChannelList::preinit(){
for (auto &&init:serialInits){
if (init.fixedBaud >= 0){
switch(init.serial){
case 1:
{
LOG_DEBUG(GwLog::DEBUG,"setting fixed baud %d for serial",init.fixedBaud);
config->setValue(GwConfigDefinitions::serialBaud,String(init.fixedBaud),GwConfigInterface::READONLY);
}
break;
case 2:
{
LOG_DEBUG(GwLog::DEBUG,"setting fixed baud %d for serial2",init.fixedBaud);
config->setValue(GwConfigDefinitions::serial2Baud,String(init.fixedBaud),GwConfigInterface::READONLY);
}
break;
default:
LOG_DEBUG(GwLog::ERROR,"invalid serial definition %d found",init.serial)
}
}
}
}
template<typename S>
long getFlushTimeout(S &s){
return 200;
}
template<>
long getFlushTimeout(HardwareSerial &s){
return 2000;
}
void GwChannelList::begin(bool fallbackSerial){
LOG_DEBUG(GwLog::DEBUG,"GwChannelList::begin");
GwChannel *channel=NULL;
@ -187,7 +305,7 @@ void GwChannelList::begin(bool fallbackSerial){
if (! fallbackSerial){
GwSerial *usb=new GwSerial(NULL,&USBSerial,USB_CHANNEL_ID);
USBSerial.begin(config->getInt(config->usbBaud));
logger->setWriter(new GwSerialLog(usb,config->getBool(config->usbActisense)));
logger->setWriter(new GwSerialLog(usb,config->getBool(config->usbActisense),getFlushTimeout(USBSerial)));
logger->prefix="GWSERIAL:";
channel=new GwChannel(logger,"USB",USB_CHANNEL_ID);
channel->setImpl(usb);
@ -223,6 +341,11 @@ void GwChannelList::begin(bool fallbackSerial){
LOG_DEBUG(GwLog::LOG,"%s",channel->toString().c_str());
theChannels.push_back(channel);
//new serial config handling
for (auto &&init:serialInits){
addSerial(init.serial,init.rx,init.tx,init.mode);
}
//handle separate defines
//serial 1
#ifndef GWSERIAL_TX
#define GWSERIAL_TX -1
@ -231,10 +354,10 @@ void GwChannelList::begin(bool fallbackSerial){
#define GWSERIAL_RX -1
#endif
#ifdef GWSERIAL_TYPE
addSerial(&Serial1,SERIAL1_CHANNEL_ID,GWSERIAL_TYPE,GWSERIAL_RX,GWSERIAL_TX);
addSerial(new SerialWrapper<decltype(Serial1)>(&Serial1,SERIAL1_CHANNEL_ID),GWSERIAL_TYPE,GWSERIAL_RX,GWSERIAL_TX);
#else
#ifdef GWSERIAL_MODE
addSerial(&Serial1,SERIAL1_CHANNEL_ID,GWSERIAL_MODE,GWSERIAL_RX,GWSERIAL_TX);
addSerial(new SerialWrapper<decltype(Serial1)>(&Serial1,SERIAL1_CHANNEL_ID),GWSERIAL_MODE,GWSERIAL_RX,GWSERIAL_TX);
#endif
#endif
//serial 2
@ -245,10 +368,10 @@ void GwChannelList::begin(bool fallbackSerial){
#define GWSERIAL2_RX -1
#endif
#ifdef GWSERIAL2_TYPE
addSerial(&Serial2,SERIAL2_CHANNEL_ID,GWSERIAL2_TYPE,GWSERIAL2_RX,GWSERIAL2_TX);
addSerial(new SerialWrapper<decltype(Serial2)>(&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);
addSerial(new SerialWrapper<decltype(Serial2)>(&Serial2,SERIAL2_CHANNEL_ID),GWSERIAL2_MODE,GWSERIAL2_RX,GWSERIAL2_TX);
#endif
#endif
//tcp client

View File

@ -23,6 +23,12 @@ class GwSocketServer;
class GwTcpClient;
class GwChannelList{
private:
class SerialWrapperBase{
public:
virtual void begin(GwLog* logger,unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1)=0;
virtual Stream *getStream()=0;
virtual int getId()=0;
};
GwLog *logger;
GwConfigHandler *config;
typedef std::vector<GwChannel *> ChannelList;
@ -30,12 +36,16 @@ class GwChannelList{
std::map<int,String> modes;
GwSocketServer *sockets;
GwTcpClient *client;
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);
void addSerial(SerialWrapperBase *stream,const String &mode,int rx,int tx);
void addSerial(SerialWrapperBase *stream,int type,int rx,int tx);
public:
void addSerial(int id, int rx, int tx, int type);
GwChannelList(GwLog *logger, GwConfigHandler *config);
typedef std::function<void(GwChannel *)> ChannelAction;
void allChannels(ChannelAction action);
//called to allow setting some predefined configs
//e.g. from serial definitions
void preinit();
//initialize
void begin(bool fallbackSerial=false);
//status

View File

@ -4,6 +4,13 @@
#include <ArduinoJson.h>
#include <string.h>
#include <MD5Builder.h>
using CfgInit=std::function<void(GwConfigHandler *)>;
static std::vector<CfgInit> cfgInits;
#define CFG_INIT(name,value,mode) \
__MSG("config set " #name " " #value " " #mode); \
static GwInitializer<CfgInit> _ ## name ## _init(cfgInits,[](GwConfigHandler *cfg){ \
cfg->setValue(GwConfigDefinitions::name,value,GwConfigInterface::mode); \
});
#include "GwHardware.h"
#include "GwConfigDefImpl.h"
@ -61,6 +68,9 @@ GwConfigHandler::GwConfigHandler(GwLog *logger): GwConfigDefinitions(){
saltBase=esp_random();
configs=new GwConfigInterface*[getNumConfig()];
populateConfigs(configs);
for (auto &&init:cfgInits){
init(this);
}
prefs=new Preferences();
}
GwConfigHandler::~GwConfigHandler(){
@ -126,11 +136,16 @@ void GwConfigHandler::stopChanges(){
allowChanges=false;
}
bool GwConfigHandler::setValue(String name,String value, bool hide){
return setValue(name,value,hide?GwConfigInterface::HIDDEN:GwConfigInterface::READONLY);
}
bool GwConfigHandler::setValue(String name, String value, GwConfigInterface::ConfigType type){
if (! allowChanges) return false;
LOG_DEBUG(GwLog::LOG,"setValue for %s to %s, mode %d",
name.c_str(),value.c_str(),(int)type);
GwConfigInterface *i=getConfigItem(name,false);
if (!i) return false;
i->value=value;
i->type=hide?GwConfigInterface::HIDDEN:GwConfigInterface::READONLY;
i->type=type;
return true;
}

View File

@ -38,6 +38,7 @@ class GwConfigHandler: public GwConfigDefinitions{
* !use with care! no checks of the value
*/
bool setValue(String name, String value, bool hide=false);
bool setValue(String name, String value, GwConfigInterface::ConfigType type);
static void toHex(unsigned long v,char *buffer,size_t bsize);
unsigned long getSaltBase(){return saltBase;}
~GwConfigHandler();
@ -70,6 +71,15 @@ class GwConfigHandler: public GwConfigDefinitions{
target=i->asInt();
return true;
}
bool getValue(float &target, const String &name, float defaultv=0){
GwConfigInterface *i=getConfigItem(name);
if (!i){
target=defaultv;
return false;
}
target=i->asFloat();
return true;
}
bool getValue(bool &target, const String name, bool defaultv=false){
GwConfigInterface *i=getConfigItem(name);
if (!i){

View File

@ -2,7 +2,6 @@
#define _GWCONFIGITEM_H
#include "WString.h"
#include <vector>
class GwConfigHandler;
class GwConfigInterface{
public:
@ -37,6 +36,9 @@ class GwConfigInterface{
virtual int asInt() const{
return (int)value.toInt();
}
virtual float asFloat() const{
return value.toFloat();
}
String getName() const{
return name;
}
@ -75,5 +77,20 @@ class GwNmeaFilter{
#define __XSTR(x) __STR(x)
#define __STR(x) #x
#define __EXPAND(x,sf) x ## sf
#define __EXPAND3(prfx,x,sf) prfx ## x ## sf
#define __MSG(x) _Pragma (__STR(message (x)))
//https://curiouser.cheshireeng.com/2014/08/19/c-compile-time-assert/
#define CASSERT(predicate, text) _impl_CASSERT_LINE(predicate,__LINE__)
#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line) typedef char _impl_PASTE(assertion_failed_CASSERT_,line)[(predicate)?1:-1];
template<typename F>
class GwInitializer{
public:
using List=std::vector<F>;
GwInitializer(List &l,F f){
l.push_back(f);
}
};
#endif

View File

@ -0,0 +1,41 @@
/*
(C) Andreas Vogel andreas@wellenvogel.de
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _GWCONVERTERCONFIG_H
#define _GWCONVERTERCONFIG_H
#include "GWConfig.h"
class GwConverterConfig{
public:
int minXdrInterval=100;
int starboardRudderInstance=0;
int portRudderInstance=-1; //ignore
int min2KInterval=50;
int rmcInterval=1000;
int rmcCheckTime=4000;
void init(GwConfigHandler *config){
minXdrInterval=config->getInt(GwConfigDefinitions::minXdrInterval,100);
starboardRudderInstance=config->getInt(GwConfigDefinitions::stbRudderI,0);
portRudderInstance=config->getInt(GwConfigDefinitions::portRudderI,-1);
min2KInterval=config->getInt(GwConfigDefinitions::min2KInterval,50);
if (min2KInterval < 10)min2KInterval=10;
rmcCheckTime=config->getInt(GwConfigDefinitions::checkRMCt,4000);
if (rmcCheckTime < 1000) rmcCheckTime=1000;
rmcInterval=config->getInt(GwConfigDefinitions::sendRMCi,1000);
if (rmcInterval < 0) rmcInterval=0;
if (rmcInterval > 0 && rmcInterval <100) rmcInterval=100;
}
};
#endif

View File

@ -116,6 +116,10 @@ bool GwWebServer::registerMainHandler(const char *url,RequestCreator creator){
});
return true;
}
bool GwWebServer::registerHandler(const char * url,GwWebServer::HandlerFunction handler){
server->on(url,HTTP_GET,handler);
return true;
}
bool GwWebServer::registerPostHandler(const char *url, ArRequestHandlerFunction requestHandler,
ArBodyHandlerFunction bodyHandler){

View File

@ -11,10 +11,12 @@ class GwWebServer{
GwLog *logger;
public:
typedef GwRequestMessage *(RequestCreator)(AsyncWebServerRequest *request);
using HandlerFunction=std::function<void(AsyncWebServerRequest *)>;
GwWebServer(GwLog *logger, GwRequestQueue *queue,int port);
~GwWebServer();
void begin();
bool registerMainHandler(const char *url,RequestCreator creator);
bool registerHandler(const char * url,HandlerFunction handler);
bool registerPostHandler(const char *url, ArRequestHandlerFunction requestHandler, ArBodyHandlerFunction bodyHandler);
void handleAsyncWebRequest(AsyncWebServerRequest *request, GwRequestMessage *msg);
AsyncWebServer * getServer(){return server;}

View File

@ -10,6 +10,10 @@
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
unfortunately there is some typo here: M5 uses GROVE for their connections
but we have GROOVE here.
But to maintain compatibility to older build commands we keep the (wrong) wording
*/
#ifdef _NOGWHARDWAREUT
#error "you are not allowed to include GwHardware.h in your user task header"
@ -25,6 +29,20 @@
#include "GwAppInfo.h"
#include "GwUserTasks.h"
#ifdef GW_PINDEFS
#define GWRESOURCE_USE(RES,USER) \
__MSG(#RES " used by " #USER) \
static int _resourceUsed ## RES =1;
#define __USAGE __MSG
#else
#define GWRESOURCE_USE(...)
#define __USAGE(...)
#endif
#ifndef CFG_INIT
#define CFG_INIT(...)
#endif
//general definitions for M5AtomLite
//hint for groove pins:
//according to some schematics the numbering is 1,2,3(VCC),4(GND)
@ -42,8 +60,13 @@
#define GWBUTTON_PULLUPDOWN
#define BOARD_LEFT1 GPIO_NUM_22
#define BOARD_LEFT2 GPIO_NUM_19
#define BOARD_LEFT3 GPIO_NUM_23
#define BOARD_LEFT4 GPIO_NUM_33
#define BOARD_RIGHT1 GPIO_NUM_21
#define BOARD_RIGHT2 GPIO_NUM_25
#define USBSerial Serial
#endif
//general definitiones for M5AtomS3
#ifdef PLATFORM_BOARD_M5STACK_ATOMS3
#define GROOVE_PIN_2 GPIO_NUM_2
@ -59,8 +82,11 @@
#define GWBUTTON_PULLUPDOWN
#define BOARD_LEFT1 GPIO_NUM_5
#define BOARD_LEFT2 GPIO_NUM_6
#define BOARD_LEFT3 GPIO_NUM_7
#define BOARD_LEFT4 GPIO_NUM_8
#define BOARD_RIGHT1 GPIO_NUM_39
#define BOARD_RIGHT2 GPIO_NUM_38
#endif
//M5Stick C
#ifdef PLATFORM_BOARD_M5STICK_C
#define GROOVE_PIN_2 GPIO_NUM_32
@ -78,13 +104,13 @@
//150mA if we power from the bus
#define N2K_LOAD_LEVEL 3
//if using tail485
#define SERIAL_GROOVE_485
#define SERIAL_GROOVE_485 1
//brightness 0...255
#define GWLED_BRIGHTNESS 64
#endif
#ifdef BOARD_M5ATOMS3
#define M5_CAN_KIT
#define M5_CAN_KIT 1
//150mA if we power from the bus
#define N2K_LOAD_LEVEL 3
//if using tail485
@ -94,33 +120,33 @@
#endif
#ifdef BOARD_M5ATOM_CANUNIT
#define M5_CANUNIT
#define M5_CANUNIT 1
#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 M5_CANUNIT 1
#define GWLED_BRIGHTNESS 64
#endif
#ifdef BOARD_M5ATOM_RS232_CANUNIT
#define M5_CANUNIT
#define M5_CANUNIT 1
#define M5_SERIAL_KIT_232
#define GWLED_BRIGHTNESS 64
#endif
#ifdef BOARD_M5ATOM_RS485_CANUNIT
#define M5_SERIAL_KIT_485
#define M5_CANUNIT
#define M5_CANUNIT 1
#define GWLED_BRIGHTNESS 64
#endif
#ifdef BOARD_M5STICK_CANUNIT
#define M5_CANUNIT
#define M5_CANUNIT 1
#endif
#ifdef BOARD_HOMBERGER
@ -136,137 +162,14 @@
#define GWBUTTON_PULLUPDOWN
#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
#include "GwM5Base.h"
#include "GwM5Grove.h"
//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
#ifdef _GWI_IIC1
#error "you must not define IIC1 on grove and GWIIC_SDA"
#endif
#ifndef GWIIC_SCL
#error "you must both define GWIIC_SDA and GWIIC_SCL"
#endif
@ -278,6 +181,9 @@
#define _GWIIC
#endif
#ifdef GWIIC_SDA2
#ifdef _GWI_IIC2
#error "you must not define IIC2 on grove and GWIIC_SDA2"
#endif
#ifndef GWIIC_SCL2
#error "you must both define GWIIC_SDA2 and GWIIC_SCL2"
#endif
@ -314,12 +220,13 @@
#endif
#ifdef GWLED_FASTLED
#define CFGMODE_ledBrightness GwConfigInterface::NORMAL
#ifdef GWLED_BRIGHTNESS
#define CFGDEFAULT_ledBrightness GWSTRINGIFY(GWLED_BRIGHTNESS)
CFG_INIT(ledBrightness,GWSTRINGIFY(GWLED_BRIGHTNESS),NORMAL)
#else
CFG_INIT(ledBrightness,"64",NORMAL)
#endif
#else
#define CFGMODE_ledBrightness GwConfigInterface::HIDDEN
CFG_INIT(ledBrightness,"64",HIDDEN)
#endif
#endif

71
lib/hardware/GwM5Base.h Normal file
View File

@ -0,0 +1,71 @@
/*
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
unfortunately there is some typo here: M5 uses GROVE for their connections
but we have GROOVE here.
But to maintain compatibility to older build commands we keep the (wrong) wording
*/
//M5 Base Boards
#ifndef _GWM5BASE_H
#define _GWM5BASE_H
//M5 Serial (Atomic RS232 Base)
#ifdef M5_SERIAL_KIT_232
GWRESOURCE_USE(BASE,M5_SERIAL_KIT_232)
GWRESOURCE_USE(SERIAL1,M5_SERIAL_KIT_232)
#define _GWI_SERIAL1 BOARD_LEFT1,BOARD_LEFT2,GWSERIAL_TYPE_BI
#endif
//M5 Serial (Atomic RS485 Base)
#ifdef M5_SERIAL_KIT_485
GWRESOURCE_USE(BASE,M5_SERIAL_KIT_485)
GWRESOURCE_USE(SERIAL1,M5_SERIAL_KIT_485)
#define _GWI_SERIAL1 BOARD_LEFT1,BOARD_LEFT2,GWSERIAL_TYPE_UNI
#endif
//M5 GPS (Atomic GPS Base)
#ifdef M5_GPS_KIT
GWRESOURCE_USE(BASE,M5_GPS_KIT)
GWRESOURCE_USE(SERIAL1,M5_GPS_KIT)
#define _GWI_SERIAL1 BOARD_LEFT1,-1,GWSERIAL_TYPE_UNI,"9600"
#endif
//M5 ProtoHub
#ifdef M5_PROTO_HUB
GWRESOURCE_USE(BASE,M5_PROTO_HUB)
#define PPIN22 BOARD_LEFT1
#define PPIN19 BOARD_LEFT2
#define PPIN23 BOARD_LEFT3
#define PPIN33 BOARD_LEFT4
#define PPIN21 BOARD_RIGHT1
#define PPIN25 BOARD_RIGHT2
#endif
//M5 PortABC extension
#ifdef M5_PORTABC
GWRESOURCE_USE(BASE,M5_PORTABC)
#define GROOVEA_PIN_2 BOARD_RIGHT2
#define GROOVEA_PIN_1 BOARD_RIGHT1
#define GROOVEB_PIN_2 BOARD_LEFT3
#define GROOVEB_PIN_1 BOARD_LEFT4
#define GROOVEC_PIN_2 BOARD_LEFT1
#define GROOVEC_PIN_1 BOARD_LEFT2
#endif
//can kit for M5 Atom
#ifdef M5_CAN_KIT
GWRESOURCE_USE(BASE,M5_CAN_KIT)
GWRESOURCE_USE(CAN,M5_CANKIT)
#define ESP32_CAN_TX_PIN BOARD_LEFT1
#define ESP32_CAN_RX_PIN BOARD_LEFT2
#endif
#endif

31
lib/hardware/GwM5Grove.h Normal file
View File

@ -0,0 +1,31 @@
/*
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
unfortunately there is some typo here: M5 uses GROVE for their connections
but we have GROOVE here.
But to maintain compatibility to older build commands we keep the (wrong) wording
*/
//M5 Grove stuff
#ifndef _GW5MGROVE_H
#define _GW5MGROVE_H
#ifndef GROOVE_IIC
#define GROOVE_IIC(...)
#endif
#include "GwM5GroveGen.h"
#if defined(_GWI_IIC1) || defined (_GWI_IIC2)
#define _GWIIC
#endif
#endif

145
lib/hardware/GwM5Grove.in Normal file
View File

@ -0,0 +1,145 @@
/*
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
unfortunately there is some typo here: M5 uses GROVE for their connections
but we have GROOVE here.
But to maintain compatibility to older build commands we keep the (wrong) wording
This file contains M5 grove definitions.
They will be expanded to match the supported groves
Each definition must start with a line that start starts with #GROVE
Afterwards you can use normal C header style
$GS$ will be replaced with a grove suffix with _ (empty for first)
$G$ will be replaced by the simple grove name (empty for base)
$Z$ will be replaced by the simple name using "Z" for the first grove
*/
#GROVE
#ifdef SERIAL_GROOVE_485$GS$
GWRESOURCE_USE(GROOVE$G$,SERIAL_GROOVE_485$GS$)
#define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_UNI
#endif
#GROVE
#ifdef SERIAL_GROOVE_232$GS$
GWRESOURCE_USE(GROOVE$G$,SERIAL_GROOVE_232$GS$)
#define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_BI
#endif
#GROVE
//http://docs.m5stack.com/en/unit/gps
#ifdef M5_GPS_UNIT$GS$
GWRESOURCE_USE(GROOVE$G$,M5_GPS_UNIT$GS$)
#define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_RX,9600
#endif
#GROVE
//CAN via groove
#ifdef M5_CANUNIT$GS$
GWRESOURCE_USE(GROOVE$G$,M5_CANUNIT$GS$)
GWRESOURCE_USE(CAN,M5_CANUNIT$GS$)
#define ESP32_CAN_TX_PIN GROOVE$G$_PIN_2
#define ESP32_CAN_RX_PIN GROOVE$G$_PIN_1
#endif
#GROVE
#ifdef M5_ENV3$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(SHT3X,$Z$,1)
GROOVE_IIC(QMP6988,$Z$,1)
#define _GWSHT3X
#define _GWQMP6988
#endif
#GROVE
//example: -DSHT3XG1_A : defines STH3Xn1 on grove A - x depends on the other devices
#ifdef GWSHT3XG1$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(SHT3X,$Z$,1)
#define _GWSHT3X
#endif
#GROVE
#ifdef GWSHT3XG2$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(SHT3X,$Z$,2)
#define _GWSHT3X
#endif
#GROVE
#ifdef GWQMP6988G1$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(QMP6988,$Z$,1)
#define _GWQMP6988
#endif
#GROVE
#ifdef GWQMP6988G2$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(QMP6988,$Z$,2)
#define _GWQMP6988
#endif
#GROVE
#ifdef GWBME280G1$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(BME280,$Z$,1)
#define _GWBME280
#endif
#GROVE
#ifdef GWBME280G2$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(BME280,$Z$,2)
#define _GWBME280
#endif
#GROVE
//select up to 2 IIC devices for grove usage
#ifdef M5_GROOVEIIC$GS$
GWRESOURCE_USE(GROOVE$G$,M5_GROOVEIIC$GS$)
#ifndef _GWI_IIC1
__USAGE("IIC1 used by GROVE$GS$")
#define _GWI_IIC1 "$Z$",GROOVE$G$_PIN_1,GROOVE$G$_PIN_2
#elif ! defined(_GWI_IIC2)
__USAGE("IIC2 used by GROVE$GS$")
#define _GWI_IIC2 "$Z$",GROOVE$G$_PIN_1,GROOVE$G$_PIN_2
#else
#error "both iic buses already in use"
#endif
#endif
#GROVE
#ifdef _GWI_SERIAL_GROOVE$GS$
#ifndef _GWI_SERIAL1
#define _GWI_SERIAL1 GROOVE$G$_PIN_1,GROOVE$G$_PIN_2,_GWI_SERIAL_GROOVE$GS$
#elif ! defined(_GWI_SERIAL2)
#define _GWI_SERIAL2 GROOVE$G$_PIN_1,GROOVE$G$_PIN_2,_GWI_SERIAL_GROOVE$GS$
#else
#error "both serial devices already in use"
#endif
#endif

View File

@ -2,8 +2,6 @@
#ifdef _GWIIC
#if defined(GWBME280) || defined(GWBME28011) || defined(GWBME28012)|| defined(GWBME28021)|| defined(GWBME28022)
#define _GWBME280
#else
#undef _GWBME280
#endif
#else
#undef _GWBME280
@ -17,11 +15,12 @@
#include <Adafruit_BME280.h>
#endif
#ifdef _GWBME280
#define PRFX1 "BME28011"
#define PRFX2 "BME28012"
#define PRFX3 "BME28021"
#define PRFX4 "BME28022"
class BME280Config : public SensorBase{
#define TYPE "BME280"
#define PRFX1 TYPE "11"
#define PRFX2 TYPE "12"
#define PRFX3 TYPE "21"
#define PRFX4 TYPE "22"
class BME280Config : public IICSensorBase{
public:
bool prAct=true;
bool tmAct=true;
@ -36,7 +35,7 @@ class BME280Config : public SensorBase{
float prOff=0;
Adafruit_BME280 *device=nullptr;
uint32_t sensorId=-1;
BME280Config(GwApi * api, const String &prfx):SensorBase(api,prfx){
BME280Config(GwApi * api, const String &prfx):SensorBase(TYPE,api,prfx){
}
virtual bool isActive(){return prAct||huAct||tmAct;}
virtual bool initDevice(GwApi *api,TwoWire *wire){
@ -106,6 +105,7 @@ class BME280Config : public SensorBase{
virtual void readConfig(GwConfigHandler *cfg) override
{
if (ok) return;
if (prefix == PRFX1)
{
busId = 1;
@ -137,11 +137,13 @@ class BME280Config : public SensorBase{
}
};
void registerBME280(GwApi *api,SensorList &sensors){
static IICSensorBase::Creator creator([](GwApi *api, const String &prfx){
return new BME280Config(api,prfx);
});
IICSensorBase::Creator registerBME280(GwApi *api,IICSensorList &sensors){
#if defined(GWBME280) || defined(GWBME28011)
{
BME280Config *cfg=new BME280Config(api,PRFX1);
auto *cfg=creator(api,PRFX1);
sensors.add(api,cfg);
CHECK_IIC1();
#pragma message "GWBME28011 defined"
@ -149,7 +151,7 @@ void registerBME280(GwApi *api,SensorList &sensors){
#endif
#if defined(GWBME28012)
{
BME280Config *cfg=new BME280Config(api,PRFX2);
auto *cfg=creator(api,PRFX2);
sensors.add(api,cfg);
CHECK_IIC1();
#pragma message "GWBME28012 defined"
@ -157,7 +159,7 @@ void registerBME280(GwApi *api,SensorList &sensors){
#endif
#if defined(GWBME28021)
{
BME280Config *cfg=new BME280Config(api,PRFX3);
auto *cfg=creator(api,PRFX3);
sensors.add(api,cfg);
CHECK_IIC2();
#pragma message "GWBME28021 defined"
@ -165,15 +167,17 @@ void registerBME280(GwApi *api,SensorList &sensors){
#endif
#if defined(GWBME28022)
{
BME280Config *cfg=new BME280Config(api,PRFX4);
auto *cfg=creator(api,PRFX4);
sensors.add(api,cfg);
CHECK_IIC1();
#pragma message "GWBME28022 defined"
}
#endif
return creator;
}
#else
void registerBME280(GwApi *api,SensorList &sensors){
IICSensorBase::Creator registerBME280(GwApi *api,IICSensorList &sensors){
return IICSensorBase::Creator();
}
#endif

View File

@ -1,5 +1,5 @@
#ifndef _GWBME280_H
#define _GWBME280_H
#include "GwIicSensors.h"
void registerBME280(GwApi *api,SensorList &sensors);
IICSensorBase::Creator registerBME280(GwApi *api,IICSensorList &sensors);
#endif

View File

@ -4,14 +4,17 @@
#include "N2kMessages.h"
#include "GwXdrTypeMappings.h"
#include "GwHardware.h"
#include "GwSensor.h"
#ifdef _GWIIC
#include <Wire.h>
#else
class TwoWire;
#endif
#define CFG_GET(name,prefix) \
cfg->getValue(name, GwConfigDefinitions::prefix ## name)
using BusType=TwoWire;
using IICSensorList=SensorList<BusType>;
using IICSensorBase=SensorBase<BusType>;
template <class CFG>
bool addPressureXdr(GwApi *api, CFG &cfg)
@ -100,35 +103,15 @@ void sendN2kTemperature(GwApi *api,CFG &cfg,double value, int counterId){
}
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;
};
#ifndef _GWI_IIC1
#define CHECK_IIC1() checkDef(GWIIC_SCL,GWIIC_SDA)
#else
#define CHECK_IIC1()
#endif
#ifndef _GWI_IIC2
#define CHECK_IIC2() checkDef(GWIIC_SCL2,GWIIC_SDA2)
#else
#define CHECK_IIC2()
#endif
#endif

View File

@ -1,4 +1,23 @@
#include "GwIicTask.h"
class IICGrove
{
public:
String base;
String grove;
String suffix;
IICGrove(const String &b, const String &g, const String &s) : base(b), grove(g), suffix(s) {}
String item(const String &grove, const String &bus)
{
if (grove == this->grove)
return base + bus + suffix;
return "";
}
};
static std::vector<IICGrove> iicGroveList;
#define GROOVE_IIC(base, grove, suffix) \
static GwInitializer<IICGrove> base##grove##suffix(iicGroveList, IICGrove(#base, #grove, #suffix));
#include "GwIicSensors.h"
#include "GwHardware.h"
#include "GwBME280.h"
@ -6,6 +25,8 @@
#include "GwSHT3X.h"
#include <map>
#include "GwTimer.h"
#ifndef GWIIC_SDA
#define GWIIC_SDA -1
#endif
@ -19,14 +40,46 @@
#define GWIIC_SCL2 -1
#endif
#include "GwTimer.h"
#include "GwHardware.h"
void runIicTask(GwApi *api);
static SensorList sensors;
static IICSensorList sensors;
static void addGroveItems(std::vector<IICSensorBase::Creator> &creators,GwApi *api, IICSensorList &sensors, const String &bus,const String &grove, int, int)
{
GwLog *logger=api->getLogger();
for (auto &&init : iicGroveList)
{
LOG_DEBUG(GwLog::DEBUG, "trying grove item %s:%s:%s for grove %s, bus %s",
init.base.c_str(),init.grove.c_str(),
init.suffix.c_str(),grove.c_str(),bus.c_str()
);
String prfx = init.item(grove, bus);
if (!prfx.isEmpty())
{
bool found=false;
for (auto &&creator : creators)
{
if (! creator) continue;
auto *scfg = creator(api, prfx);
scfg->readConfig(api->getConfig());
if (scfg->ok)
{
LOG_DEBUG(GwLog::LOG, "adding %s from grove config", prfx.c_str());
sensors.add(api, scfg);
found=true;
break;
}
else
{
LOG_DEBUG(GwLog::DEBUG, "unmatched grove sensor config %s for %s", prfx.c_str(), scfg->type.c_str());
delete scfg;
}
}
if (! found){
LOG_DEBUG(GwLog::ERROR,"no iic sensor found for %s",prfx.c_str());
}
}
}
}
void initIicTask(GwApi *api){
GwLog *logger=api->getLogger();
@ -35,14 +88,21 @@ void initIicTask(GwApi *api){
#else
bool addTask=false;
GwConfigHandler *config=api->getConfig();
registerSHT3X(api,sensors);
registerQMP6988(api,sensors);
registerBME280(api,sensors);
std::vector<IICSensorBase::Creator> creators;
creators.push_back(registerSHT3X(api,sensors));
creators.push_back(registerQMP6988(api,sensors));
creators.push_back(registerBME280(api,sensors));
#ifdef _GWI_IIC1
addGroveItems(creators,api,sensors,"1",_GWI_IIC1);
#endif
#ifdef _GWI_IIC2
addGroveItems(creators,api,sensors,"2",_GWI_IIC2);
#endif
for (auto it=sensors.begin();it != sensors.end();it++){
if ((*it)->preinit(api)) addTask=true;
}
if (addTask){
api->addUserTask(runIicTask,"iicTask",3000);
api->addUserTask(runIicTask,"iicTask",4000);
}
#endif
}
@ -54,6 +114,40 @@ void runIicTask(GwApi *api){
return;
}
#else
bool initWireDo(GwLog *logger, TwoWire &wire, int num, const String &dummy, int scl, int sda)
{
if (sda < 0 || scl < 0)
{
LOG_DEBUG(GwLog::ERROR, "IIC %d invalid config sda=%d,scl=%d",
num, sda, scl);
return false;
}
bool rt = Wire.begin(sda, scl);
if (!rt)
{
LOG_DEBUG(GwLog::ERROR, "unable to initialize IIC %d at sad=%d,scl=%d",
num, sda, scl);
return rt;
}
LOG_DEBUG(GwLog::ERROR, "initialized IIC %d at sda=%d,scl=%d",
num,sda,scl);
return true;
}
bool initWire(GwLog *logger, TwoWire &wire, int num){
if (num == 1){
#ifdef _GWI_IIC1
return initWireDo(logger,wire,num,_GWI_IIC1);
#endif
return initWireDo(logger,wire,num,"",GWIIC_SDA,GWIIC_SCL);
}
if (num == 2){
#ifdef _GWI_IIC2
return initWireDo(logger,wire,num,_GWI_IIC2);
#endif
return initWireDo(logger,wire,num,"",GWIIC_SDA2,GWIIC_SCL2);
}
return false;
}
void runIicTask(GwApi *api){
GwLog *logger=api->getLogger();
std::map<int,TwoWire *> buses;
@ -66,50 +160,15 @@ void runIicTask(GwApi *api){
{
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
{
if (initWire(logger,Wire,1)){
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
{
if (initWire(logger,Wire1,2)){
buses[busId] = &Wire1;
LOG_DEBUG(GwLog::LOG, "initialized IIC 2 at sda=%d,scl=%d",
(int)GWIIC_SDA2, (int)GWIIC_SCL2);
}
}
}
break;
@ -124,7 +183,7 @@ void runIicTask(GwApi *api){
GwIntervalRunner timers;
int counterId=api->addCounter("iicsensors");
for (auto it=sensors.begin();it != sensors.end();it++){
SensorBase *cfg=*it;
IICSensorBase *cfg=*it;
auto bus=buses.find(cfg->busId);
if (! cfg->isActive()) continue;
if (bus == buses.end()){

View File

@ -1,17 +1,19 @@
#define _IIC_GROOVE_LIST
#include "GwQMP6988.h"
#ifdef _GWQMP6988
#define PRFX1 "QMP698811"
#define PRFX2 "QMP698812"
#define PRFX3 "QMP698821"
#define PRFX4 "QMP698822"
class QMP6988Config : public SensorBase{
#define TYPE "QMP6988"
#define PRFX1 TYPE "11"
#define PRFX2 TYPE "12"
#define PRFX3 TYPE "21"
#define PRFX4 TYPE "22"
class QMP6988Config : public IICSensorBase{
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){}
QMP6988Config(GwApi* api,const String &prefix):SensorBase(TYPE,api,prefix){}
virtual bool isActive(){return prAct;};
virtual bool initDevice(GwApi *api,TwoWire *wire){
if (!isActive()) return false;
@ -48,6 +50,7 @@ class QMP6988Config : public SensorBase{
CFG_GET(prOff,prefix);
virtual void readConfig(GwConfigHandler *cfg){
if (ok) return;
if (prefix == PRFX1){
busId=1;
addr=86;
@ -76,7 +79,10 @@ class QMP6988Config : public SensorBase{
}
};
void registerQMP6988(GwApi *api,SensorList &sensors){
static IICSensorBase::Creator creator=[](GwApi *api,const String &prfx){
return new QMP6988Config(api,prfx);
};
IICSensorBase::Creator registerQMP6988(GwApi *api,IICSensorList &sensors){
GwLog *logger=api->getLogger();
#if defined(GWQMP6988) || defined(GWQMP698811)
{
@ -110,8 +116,11 @@ void registerQMP6988(GwApi *api,SensorList &sensors){
#pragma message "GWQMP698822 defined"
}
#endif
return creator;
}
#else
void registerQMP6988(GwApi *api,SensorList &sensors){}
IICSensorBase::Creator registerQMP6988(GwApi *api,IICSensorList &sensors){
return IICSensorBase::Creator();
}
#endif

View File

@ -4,8 +4,6 @@
#ifdef _GWIIC
#if defined(GWQMP6988) || defined(GWQMP698811) || defined(GWQMP698812) || defined(GWQMP698821) || defined(GWQMP698822)
#define _GWQMP6988
#else
#undef _GWQMP6988
#endif
#else
#undef _GWQMP6988
@ -18,5 +16,5 @@
#ifdef _GWQMP6988
#include "QMP6988.h"
#endif
void registerQMP6988(GwApi *api,SensorList &sensors);
IICSensorBase::Creator registerQMP6988(GwApi *api,IICSensorList &sensors);
#endif

View File

@ -1,11 +1,12 @@
#include "GwSHT3X.h"
#ifdef _GWSHT3X
#define PRFX1 "SHT3X11"
#define PRFX2 "SHT3X12"
#define PRFX3 "SHT3X21"
#define PRFX4 "SHT3X22"
class SHT3XConfig : public SensorBase{
#define TYPE "SHT3X"
#define PRFX1 TYPE "11"
#define PRFX2 TYPE "12"
#define PRFX3 TYPE "21"
#define PRFX4 TYPE "22"
class SHT3XConfig : public IICSensorBase{
public:
String tmNam;
String huNam;
@ -15,7 +16,7 @@ class SHT3XConfig : public SensorBase{
tN2kTempSource tmSrc;
SHT3X *device=nullptr;
SHT3XConfig(GwApi *api,const String &prefix):
SensorBase(api,prefix){}
SensorBase(TYPE,api,prefix){}
virtual bool isActive(){
return tmAct || huAct;
}
@ -77,6 +78,7 @@ class SHT3XConfig : public SensorBase{
CFG_GET(tmSrc,prefix);
virtual void readConfig(GwConfigHandler *cfg){
if (ok) return;
if (prefix == PRFX1){
busId=1;
addr=0x44;
@ -104,11 +106,14 @@ class SHT3XConfig : public SensorBase{
intv*=1000;
}
};
void registerSHT3X(GwApi *api,SensorList &sensors){
IICSensorBase::Creator creator=[](GwApi *api,const String &prfx){
return new SHT3XConfig(api,prfx);
};
IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors){
GwLog *logger=api->getLogger();
#if defined(GWSHT3X) || defined (GWSHT3X11)
{
SHT3XConfig *scfg=new SHT3XConfig(api,PRFX1);
auto *scfg=creator(api,PRFX1);
sensors.add(api,scfg);
CHECK_IIC1();
#pragma message "GWSHT3X11 defined"
@ -116,7 +121,7 @@ void registerSHT3X(GwApi *api,SensorList &sensors){
#endif
#if defined(GWSHT3X12)
{
SHT3XConfig *scfg=new SHT3XConfig(api,PRFX2);
auto *scfg=creator(api,PRFX2);
sensors.add(api,scfg);
CHECK_IIC1();
#pragma message "GWSHT3X12 defined"
@ -124,7 +129,7 @@ void registerSHT3X(GwApi *api,SensorList &sensors){
#endif
#if defined(GWSHT3X21)
{
SHT3XConfig *scfg=new SHT3XConfig(api,PRFX3);
auto *scfg=creator(api,PRFX3);
sensors.add(api,scfg);
CHECK_IIC2();
#pragma message "GWSHT3X21 defined"
@ -132,16 +137,18 @@ void registerSHT3X(GwApi *api,SensorList &sensors){
#endif
#if defined(GWSHT3X22)
{
SHT3XConfig *scfg=new SHT3XConfig(api,PRFX4);
auto *scfg=creator(api,PRFX4);
sensors.add(api,scfg);
CHECK_IIC2();
#pragma message "GWSHT3X22 defined"
}
#endif
}
#else
void registerSHT3X(GwApi *api,SensorList &sensors){
return creator;
};
#else
IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors){
return IICSensorBase::Creator();
}
#endif

View File

@ -4,8 +4,6 @@
#ifdef _GWIIC
#if defined(GWSHT3X) || defined(GWSHT3X11) || defined(GWSHT3X12) || defined(GWSHT3X21) || defined(GWSHT3X22)
#define _GWSHT3X
#else
#undef _GWSHT3X
#endif
#else
#undef _GWSHT3X
@ -18,5 +16,5 @@
#ifdef _GWSHT3X
#include "SHT3X.h"
#endif
void registerSHT3X(GwApi *api,SensorList &sensors);
IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors);
#endif

View File

@ -29,7 +29,6 @@ private:
MyAisDecoder *aisDecoder=NULL;
ConverterList<NMEA0183DataToN2KFunctions, SNMEA0183Msg> converters;
std::map<String,unsigned long> lastSends;
unsigned long minSendInterval=50;
GwXDRMappings *xdrMappings;
class WaypointNumber{
public:
@ -92,7 +91,7 @@ private:
return false;
}
bool send(tN2kMsg &msg, int sourceId,String key=""){
return send(msg,key,minSendInterval,sourceId);
return send(msg,key,config.min2KInterval,sourceId);
}
bool updateDouble(GwBoatItem<double> *target,double v, int sourceId){
if (v != NMEA0183DoubleNA){
@ -304,7 +303,7 @@ private:
LOG_DEBUG(GwLog::DEBUG + 1, "convert RMB");
tRMB rmb;
if (! NMEA0183ParseRMB_nc(msg,rmb)){
LOG_DEBUG(GwLog::DEBUG, "failed to parse RMC %s", msg.line);
LOG_DEBUG(GwLog::DEBUG, "failed to parse RMB %s", msg.line);
return;
}
tN2kMsg n2kMsg;
@ -359,6 +358,7 @@ private:
LOG_DEBUG(GwLog::DEBUG, "invalid status %c for RMC %s",status, msg.line);
return;
}
lastRmc=millis(); //we received an RMC that is not from us
tN2kMsg n2kMsg;
if (
UD(GPST) &&
@ -666,21 +666,33 @@ private:
void convertDBT(const SNMEA0183Msg &msg){
return convertDBKx(msg,DBT);
}
#define validInstance(name) (name >= 0 && name <= 253)
void convertRSA(const SNMEA0183Msg &msg){
double RPOS=NMEA0183DoubleNA;
double PRPOS=NMEA0183DoubleNA;
if (msg.FieldCount() < 4)
{
LOG_DEBUG(GwLog::DEBUG, "failed to parse RSA %s", msg.line);
return;
}
if (msg.FieldLen(0)>0){
if (msg.Field(1)[0] != 'A') return;
RPOS=degToRad*atof(msg.Field(0));
tN2kMsg n2kMsg;
if (! UD(RPOS)) return;
SetN2kRudder(n2kMsg,RPOS);
send(n2kMsg,msg.sourceId);
if (msg.FieldLen(0)>0){
if (msg.Field(1)[0] == 'A'){
RPOS=degToRad*atof(msg.Field(0));
if (UD(RPOS) && validInstance(config.starboardRudderInstance)) {
SetN2kRudder(n2kMsg,RPOS,config.starboardRudderInstance);
send(n2kMsg,msg.sourceId,"127245S");
}
}
}
if (msg.FieldLen(2)>0){
if (msg.Field(3)[0] == 'A'){
PRPOS=degToRad*atof(msg.Field(2));
if (UD(PRPOS) && validInstance(config.portRudderInstance)){
SetN2kRudder(n2kMsg,PRPOS,config.portRudderInstance);
send(n2kMsg,msg.sourceId,"127245P");
}
}
}
}
@ -1061,10 +1073,10 @@ public:
NMEA0183DataToN2KFunctions(GwLog *logger, GwBoatData *boatData, N2kSender callback,
GwXDRMappings *xdrMappings,
unsigned long minSendInterval)
const GwConverterConfig &cfg)
: NMEA0183DataToN2K(logger, boatData, callback)
{
this->minSendInterval=minSendInterval;
this->config=cfg;
this->xdrMappings=xdrMappings;
aisDecoder= new MyAisDecoder(logger,this->sender);
registerConverters();
@ -1074,7 +1086,7 @@ public:
NMEA0183DataToN2K* NMEA0183DataToN2K::create(GwLog *logger,GwBoatData *boatData,N2kSender callback,
GwXDRMappings *xdrMappings,
unsigned long minSendInterval){
return new NMEA0183DataToN2KFunctions(logger, boatData,callback,xdrMappings,minSendInterval);
const GwConverterConfig &config){
return new NMEA0183DataToN2KFunctions(logger, boatData,callback,xdrMappings,config);
}

View File

@ -4,6 +4,7 @@
#include "GwBoatData.h"
#include "N2kMessages.h"
#include "GwXDRMappings.h"
#include "GwConverterConfig.h"
class NMEA0183DataToN2K{
public:
@ -12,14 +13,17 @@ class NMEA0183DataToN2K{
GwLog * logger;
GwBoatData *boatData;
N2kSender sender;
GwConverterConfig config;
unsigned long lastRmc=millis();
public:
NMEA0183DataToN2K(GwLog *logger,GwBoatData *boatData,N2kSender callback);
virtual bool parseAndSend(const char *buffer, int sourceId)=0;
virtual unsigned long *handledPgns()=0;
virtual int numConverters()=0;
virtual String handledKeys()=0;
unsigned long getLastRmc()const {return lastRmc; }
static NMEA0183DataToN2K* create(GwLog *logger,GwBoatData *boatData,N2kSender callback,
GwXDRMappings *xdrMappings,
unsigned long minSendInterval);
const GwConverterConfig &config);
};
#endif

View File

@ -43,7 +43,7 @@ N2kDataToNMEA0183::N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData,
//*****************************************************************************
void N2kDataToNMEA0183::loop() {
void N2kDataToNMEA0183::loop(unsigned long) {
}
//*****************************************************************************
@ -62,20 +62,19 @@ class N2kToNMEA0183Functions : public N2kDataToNMEA0183
{
private:
int minXdrInterval=100; //minimal interval between 2 sends of the same transducer
GwXDRMappings *xdrMappings;
ConverterList<N2kToNMEA0183Functions,tN2kMsg> converters;
std::map<String,unsigned long> lastSendTransducers;
static const unsigned long RMCPeriod = 500;
tNMEA0183Msg xdrMessage;
bool xdrOpened=false;
int xdrCount=0;
unsigned long lastRmcSent=0;
bool addToXdr(GwXDRFoundMapping::XdrEntry entry){
auto it=lastSendTransducers.find(entry.transducer);
unsigned long now=millis();
if (it != lastSendTransducers.end()){
if ((it->second + minXdrInterval) > now) return false;
if ((it->second + config.minXdrInterval) > now) return false;
}
lastSendTransducers[entry.transducer]=now;
if (! xdrOpened){
@ -134,9 +133,6 @@ private:
return boatData->update((double)value,sourceId,mapping);
}
unsigned long LastPosSend;
unsigned long NextRMCSend;
unsigned long lastLoopTime;
virtual unsigned long *handledPgns()
{
@ -166,7 +162,6 @@ private:
{
return converters.numConverters();
}
void SetNextRMCSend() { NextRMCSend = millis() + RMCPeriod; }
//*************** the converters ***********************
void HandleHeading(const tN2kMsg &N2kMsg)
@ -546,11 +541,9 @@ private:
void SendRMC()
{
long now = millis();
if (NextRMCSend <= millis() &&
boatData->LAT->isValid(now) &&
boatData->LAT->getLastSource() == sourceId
)
if (boatData->LAT->isValid(now) && boatData->LON->isValid(now))
{
lastRmcSent=now;
tNMEA0183Msg NMEA0183Msg;
if (NMEA0183SetRMC(NMEA0183Msg,
@ -565,7 +558,6 @@ private:
{
SendMessage(NMEA0183Msg);
}
SetNextRMCSend();
}
}
@ -611,24 +603,38 @@ private:
if (ParseN2kRudder(N2kMsg, RudderPosition, Instance, RudderDirectionOrder, AngleOrder))
{
if (Instance == config.starboardRudderInstance){
updateDouble(boatData->RPOS, RudderPosition);
if (Instance != 0)
}
else if (Instance == config.portRudderInstance){
updateDouble(boatData->PRPOS, RudderPosition);
}
else{
return;
}
tNMEA0183Msg NMEA0183Msg;
if (!NMEA0183Msg.Init("RSA", talkerId))
return;
if (!NMEA0183Msg.AddDoubleField(formatCourse(RudderPosition)))
return;
if (!NMEA0183Msg.AddStrField("A"))
return;
if (!NMEA0183Msg.AddDoubleField(0.0))
return;
if (!NMEA0183Msg.AddStrField("A"))
return;
auto rpos=boatData->RPOS;
if (rpos->isValid()){
if (!NMEA0183Msg.AddDoubleField(formatWind(rpos->getData())))return;
if (!NMEA0183Msg.AddStrField("A"))return;
}
else{
if (!NMEA0183Msg.AddDoubleField(0.0))return;
if (!NMEA0183Msg.AddStrField("V"))return;
}
auto prpos=boatData->PRPOS;
if (prpos->isValid()){
if (!NMEA0183Msg.AddDoubleField(formatWind(prpos->getData())))return;
if (!NMEA0183Msg.AddStrField("A"))return;
}
else{
if (!NMEA0183Msg.AddDoubleField(0.0))return;
if (!NMEA0183Msg.AddStrField("V"))return;
}
SendMessage(NMEA0183Msg);
}
}
@ -1508,35 +1514,30 @@ private:
public:
N2kToNMEA0183Functions(GwLog *logger, GwBoatData *boatData,
SendNMEA0183MessageCallback callback,
String talkerId, GwXDRMappings *xdrMappings, int minXdrInterval)
String talkerId, GwXDRMappings *xdrMappings, const GwConverterConfig &cfg)
: N2kDataToNMEA0183(logger, boatData, callback,talkerId)
{
LastPosSend = 0;
lastLoopTime = 0;
NextRMCSend = millis() + RMCPeriod;
this->logger = logger;
this->boatData = boatData;
this->xdrMappings=xdrMappings;
this->minXdrInterval=minXdrInterval;
this->config=cfg;
registerConverters();
}
virtual void loop()
virtual void loop(unsigned long lastExtRmc) override
{
N2kDataToNMEA0183::loop();
N2kDataToNMEA0183::loop(lastExtRmc);
unsigned long now = millis();
if (now < (lastLoopTime + 100))
return;
lastLoopTime = now;
if (config.rmcInterval > 0 && (lastExtRmc + config.rmcCheckTime) <= now && (lastRmcSent + config.rmcInterval) <= now){
SendRMC();
}
}
};
N2kDataToNMEA0183* N2kDataToNMEA0183::create(GwLog *logger, GwBoatData *boatData,
SendNMEA0183MessageCallback callback, String talkerId, GwXDRMappings *xdrMappings,
int minXdrInterval){
const GwConverterConfig &cfg){
LOG_DEBUG(GwLog::LOG,"creating N2kToNMEA0183");
return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,minXdrInterval);
return new N2kToNMEA0183Functions(logger,boatData,callback, talkerId,xdrMappings,cfg);
}
//*****************************************************************************

View File

@ -26,9 +26,10 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <NMEA0183.h>
#include <NMEA2000.h>
#include <GwLog.h>
#include <GwBoatData.h>
#include <GwXDRMappings.h>
#include "GwLog.h"
#include "GwBoatData.h"
#include "GwXDRMappings.h"
#include "GwConverterConfig.h"
//------------------------------------------------------------------------------
class GwJsonDocument;
@ -36,8 +37,8 @@ class N2kDataToNMEA0183
{
public:
typedef std::function<void(const tNMEA0183Msg &NMEA0183Msg,int id)> SendNMEA0183MessageCallback;
protected:
GwConverterConfig config;
GwLog *logger;
GwBoatData *boatData;
int sourceId=0;
@ -49,9 +50,9 @@ protected:
public:
static N2kDataToNMEA0183* create(GwLog *logger, GwBoatData *boatData, SendNMEA0183MessageCallback callback,
String talkerId, GwXDRMappings *xdrMappings,int minXdrInterval=100);
String talkerId, GwXDRMappings *xdrMappings,const GwConverterConfig &cfg);
virtual void HandleMsg(const tN2kMsg &N2kMsg, int sourceId) = 0;
virtual void loop();
virtual void loop(unsigned long lastRmc);
virtual ~N2kDataToNMEA0183(){}
virtual unsigned long* handledPgns()=0;
virtual int numPgns()=0;

55
lib/sensors/GwSensor.h Normal file
View File

@ -0,0 +1,55 @@
/*
(C) Andreas Vogel andreas@wellenvogel.de
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _GWSENSORS_H
#define _GWSENSORS_H
#include "GwApi.h"
#include "GwLog.h"
template<typename BUS>
class SensorBase{
public:
int busId=0;
int iid=99; //N2K instanceId
int addr=-1;
const String prefix;
const String type;
long intv=0;
bool ok=false;
virtual void readConfig(GwConfigHandler *cfg)=0;
SensorBase(const String &tn,GwApi *api,const String &prfx):type(tn),prefix(prfx){
}
using Creator=std::function<SensorBase<BUS> *(GwApi *api,const String &prfx)>;
virtual bool isActive(){return false;};
virtual bool initDevice(GwApi *api,BUS *wire){return false;};
virtual bool preinit(GwApi * api){return false;}
virtual void measure(GwApi * api,BUS *wire, int counterId){};
virtual ~SensorBase(){}
};
template<typename BUS>
class SensorList : public std::vector<SensorBase<BUS>*>{
public:
void add(GwApi *api, SensorBase<BUS> *sensor){
sensor->readConfig(api->getConfig());
api->getLogger()->logDebug(GwLog::LOG,"configured sensor %s, status %d",sensor->prefix.c_str(),(int)sensor->ok);
this->push_back(sensor);
}
using std::vector<SensorBase<BUS>*>::vector;
};
#define CFG_GET(name,prefix) \
cfg->getValue(name, GwConfigDefinitions::prefix ## name)
#endif

View File

@ -115,11 +115,21 @@ void GwSerial::readMessages(GwMessageFetcher *writer){
writer->handleBuffer(readBuffer);
}
void GwSerial::flush(){
if (! isInitialized()) return;
while (write() == GwBuffer::AGAIN){
bool GwSerial::flush(long max){
if (! isInitialized()) return false;
if (! availableWrite) {
if ( serial->availableForWrite() < 1){
return false;
}
availableWrite=true;
}
auto start=millis();
while (millis() < (start+max)){
if (write() != GwBuffer::AGAIN) return true;
vTaskDelay(1);
}
availableWrite=(serial->availableForWrite() > 0);
return false;
}
Stream * GwSerial::getStream(bool partialWrite){
return new GwSerialStream(this,partialWrite);

View File

@ -17,6 +17,7 @@ class GwSerial : public GwChannelInterface{
int overflows=0;
size_t enqueue(const uint8_t *data, size_t len,bool partial=false);
Stream *serial;
bool availableWrite=false; //if this is false we will wait for availabkleWrite until we flush again
public:
static const int bufferSize=200;
GwSerial(GwLog *logger,Stream *stream,int id,bool allowRead=true);
@ -25,8 +26,9 @@ class GwSerial : public GwChannelInterface{
virtual size_t sendToClients(const char *buf,int sourceId,bool partial=false);
virtual void loop(bool handleRead=true,bool handleWrite=true);
virtual void readMessages(GwMessageFetcher *writer);
void flush();
bool flush(long millis=200);
virtual Stream *getStream(bool partialWrites);
bool getAvailableWrite(){return availableWrite;}
friend GwSerialStream;
};
#endif

139
lib/spitask/GWDMS22B.cpp Normal file
View File

@ -0,0 +1,139 @@
/*
(C) Andreas Vogel andreas@wellenvogel.de
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "GWDMS22B.h"
#include "GwApi.h"
#include "N2kMessages.h"
#include "GwHardware.h"
#define CHECK_BUS(BUS) \
checkDef("missing config for " #BUS,GW ## BUS ## _CLK ,GW ## BUS ## _MISO);
#define ADD22B(PRFX,BUS) \
{\
CHECK_BUS(BUS); \
GWDMS22B *dms=new GWDMS22B(api,#PRFX,GW ## BUS ## _HOST);\
sensors.add(api,dms); \
}
#ifdef GWDMS22B11
#define ADD22B11 ADD22B(DMS22B11,SPI1)
#ifndef GWDMS22B11_CS
#define GWDMS22B11_CS -1
#endif
#else
#define GWDMS22B11_CS -1
#define ADD22B11
#endif
#ifdef GWDMS22B12
#define ADD22B12 ADD22B(DMS22B12,SPI1)
#ifndef GWDMS22B12_CS
#error "you need to define GWDMS22B12_CS"
#endif
#if GWDMS22B11_CS == -1
#error "multiple devices on one SPI bus need chip select defines - GWDMS22B11_CS is unset"
#endif
#else
#define GWDMS22B12_CS -1
#define ADD22B12
#endif
#ifdef GWDMS22B21
#define ADD22B21 ADD22B(DMS22B21,SPI2)
#ifndef GWDMS22B21_CS
#define GWDMS22B21_CS -1
#endif
#else
#define GWDMS22B21_CS -1
#define ADD22B21
#endif
#ifdef GWDMS22B22
#define ADD22B22 ADD22B(DMS22B22,SPI2)
#ifndef GWDMS22B22_CS
#error "you need to define GWDMS22B22_CS"
#endif
#if GWDMS22B21_CS == -1
#error "multiple devices on one SPI bus need chip select defines - GWDMS22B21_CS is unset"
#endif
#else
#define GWDMS22B22_CS -1
#define ADD22B22
#endif
class GWDMS22B : public SSISensor{
int zero=2047;
bool invt=false;
String zeroConfigName;
public:
GWDMS22B(GwApi *api,const String &prfx, int host):SSISensor("DMS22B",api,prfx,host){}
virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"DMS22B configured, prefix=%s, intv=%f, active=%d",prefix.c_str(),fintv,(int)act);
api->addCapability(prefix,"true");
return act;
}
virtual void measure(GwApi * api,BusType *bus, int counterId){
GwLog *logger=api->getLogger();
uint32_t value=0;
esp_err_t res=readData(value);
if (res != ESP_OK){
LOG_DEBUG(GwLog::ERROR,"unable to measure %s: %d",prefix.c_str(),(int)res);
}
double resolved=(((int)value-zero)*360.0/mask);
if (invt) resolved=-resolved;
LOG_DEBUG(GwLog::DEBUG,"measure %s : %d, resolved: %f",prefix.c_str(),value,(float)resolved);
tN2kMsg msg;
SetN2kRudder(msg,DegToRad(resolved),iid);
api->sendN2kMessage(msg);
api->increment(counterId,prefix);
api->setCalibrationValue(zeroConfigName,(double)value);
}
#define DMS22B(PRFX,...) \
if (prefix == #PRFX) {\
CFG_GET(act,PRFX); \
CFG_GET(iid,PRFX); \
CFG_GET(fintv,PRFX); \
CFG_GET(zero,PRFX); \
zeroConfigName=GwConfigDefinitions::PRFX ## zero;\
CFG_GET(invt,PRFX); \
bits=12; \
clock=500000; \
cs=GW ## PRFX ## _CS; \
__VA_ARGS__ \
}
virtual void readConfig(GwConfigHandler *cfg){
DMS22B(DMS22B11);
DMS22B(DMS22B12);
DMS22B(DMS22B21);
DMS22B(DMS22B22);
intv=1000*fintv;
}
};
void registerDMS22B(GwApi *api,SpiSensorList &sensors){
ADD22B11
ADD22B12
ADD22B21
ADD22B22
}

22
lib/spitask/GWDMS22B.h Normal file
View File

@ -0,0 +1,22 @@
/*
(C) Andreas Vogel andreas@wellenvogel.de
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
SSI sensor DMS22B - https://www.mouser.de/datasheet/2/54/bour_s_a0011704065_1-2262614.pdf
*/
#ifndef _GWDMS22B_H
#define _GWDMS22B_H
#include "GwSpiSensor.h"
void registerDMS22B(GwApi *api,SpiSensorList &sensors);
#endif

141
lib/spitask/GwSpiSensor.h Normal file
View File

@ -0,0 +1,141 @@
/*
(C) Andreas Vogel andreas@wellenvogel.de
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
//for SSI refer to https://www.posital.com/media/posital_media/documents/AbsoluteEncoders_Context_Technology_SSI_AppNote.pdf
#ifndef _GWSPISENSOR_H
#define _GWSPISENSOR_H
#include <driver/spi_master.h>
#include "GwSensor.h"
#include <memory>
class SPIBus{
spi_host_device_t hd;
bool initialized=false;
public:
SPIBus(spi_host_device_t h):hd(h){}
bool init(GwLog*logger,int mosi=-1,int miso=-1,int clck=-1){
spi_bus_config_t buscfg = {
.mosi_io_num = mosi,
.miso_io_num = miso,
.sclk_io_num = clck,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 0
};
esp_err_t res=spi_bus_initialize(hd,&buscfg,0);
if (res == ESP_OK){
LOG_DEBUG(GwLog::LOG,"initialzed SPI bus %d,mosi=%d,miso=%d,clock=%d",
(int)hd,mosi,miso,clck);
initialized=true;
}
else{
LOG_DEBUG(GwLog::ERROR,"unable to initialize SPI bus %d,mosi=%d,miso=%d,clock=%d, error=%d",
(int)hd,mosi,miso,clck,(int)res);
}
return initialized;
}
spi_host_device_t host() const { return hd;}
};
using BusType=SPIBus;
class SSIDevice{
spi_device_handle_t spi;
spi_host_device_t host;
bool initialized=false;
int bits=12;
public:
SSIDevice(const SPIBus *bus):host(bus->host()){}
bool init(GwLog*logger,int clock,int cs=-1,int bits=12){
this->bits=bits;
spi_device_interface_config_t devcfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 2,
.duty_cycle_pos = 128,
.cs_ena_pretrans = 0,
.cs_ena_posttrans =0,
.clock_speed_hz = clock,
.input_delay_ns =0,
.spics_io_num = cs, //CS pin
.queue_size = 1 //see https://github.com/espressif/esp-idf/issues/9450
};
esp_err_t res=spi_bus_add_device(host,&devcfg,&spi);
if (res == ESP_OK){
LOG_DEBUG(GwLog::LOG,"added SSI device to bus %d, cs=%d, clock=%d",
(int)host,cs,clock);
initialized=true;
}
else{
LOG_DEBUG(GwLog::ERROR,"unable to add SSI device to bus %d, cs=%d, clock=%d, error=%d",
(int)host,cs,clock,(int) res);
}
return initialized;
}
bool isInitialized() const { return initialized;}
spi_device_handle_t & device(){return spi;}
};
class SSISensor : public SensorBase<BusType>{
std::unique_ptr<SSIDevice> device;
protected:
int bits=12;
int mask=0xffff;
int cs=-1;
int clock=0;
bool act=false;
float fintv=0;
virtual bool initSSI(GwLog*logger,const SPIBus *bus,
int clock,int cs, int bits){
mask= (1 << bits)-1;
device.reset(new SSIDevice(bus));
return device->init(logger,clock,cs,bits);
}
esp_err_t readData(uint32_t &res)
{
struct spi_transaction_t ta = {
.flags = SPI_TRANS_USE_RXDATA,
.cmd = 0,
.addr = 0,
.length = bits+1,
.rxlength = 0};
esp_err_t ret = spi_device_queue_trans(device->device(), &ta, portMAX_DELAY);
if (ret != ESP_OK) return ret;
struct spi_transaction_t *rs = NULL;
ret = spi_device_get_trans_result(device->device(), &rs, portMAX_DELAY);
if (ret != ESP_OK) return ret;
if (rs == NULL) return ESP_ERR_INVALID_RESPONSE;
res=SPI_SWAP_DATA_RX(*(uint32_t*)rs->rx_data,bits+1);
res&=mask;
return ESP_OK;
}
public:
SSISensor(const String &type,GwApi *api,const String &prfx, int host):SensorBase(type,api,prfx)
{
busId=host;
}
virtual bool isActive(){return act;};
virtual bool initDevice(GwApi *api,BusType *bus){
return initSSI(api->getLogger(),bus, clock,cs,bits);
};
};
using SpiSensorList=SensorList<BusType>;
#define GWSPI1_HOST SPI2_HOST
#define GWSPI2_HOST SPI3_HOST
#endif

136
lib/spitask/GwSpiTask.cpp Normal file
View File

@ -0,0 +1,136 @@
/*
(C) Andreas Vogel andreas@wellenvogel.de
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "GwSpiTask.h"
#include "GwSpiSensor.h"
#include "GWDMS22B.h"
#include "GwTimer.h"
#include "GwHardware.h"
static SPIBus bus1(GWSPI1_HOST);
static SPIBus bus2(GWSPI2_HOST);
static SpiSensorList sensors;
#ifdef GWSPI1_CLK
static const int spi1clk=GWSPI1_CLK;
#else
static const int spi1clk=-1;
#endif
#ifdef GWSPI1_MISO
static const int spi1miso=GWSPI1_MISO;
#else
static const int spi1miso=-1;
#endif
#ifdef GWSPI1_MOSI
static const int spi1mosi=GWSPI1_MOSI;
#else
static const int spi1mosi=-1;
#endif
#ifdef GWSPI2_CLK
static const int spi2clk=GWSPI2_CLK;
#else
static const int spi2clk=-1;
#endif
#ifdef GWSPI2_MISO
static const int spi2miso=GWSPI2_MISO;
#else
static const int spi2miso=-1;
#endif
#ifdef GWSPI2_MOSI
static const int spi2mosi=GWSPI2_MOSI;
#else
static const int spi2mosi=-1;
#endif
void runSpiTask(GwApi *api){
GwLog *logger=api->getLogger();
std::map<int,SPIBus *> buses;
for (auto && sensor:sensors){
int busId=sensor->busId;
auto bus=buses.find(busId);
if (bus == buses.end()){
switch (busId)
{
case GWSPI1_HOST:
if (spi1clk < 0){
LOG_DEBUG(GwLog::ERROR,"SPI bus 1 not configured, cannot create %s",sensor->prefix.c_str());
}
else{
if (bus1.init(logger,spi1mosi,spi1miso,spi1clk)){
buses[busId]=&bus1;
}
}
break;
case GWSPI2_HOST:
if (spi2clk < 0){
LOG_DEBUG(GwLog::ERROR,"SPI bus 2 not configured, cannot create %s",sensor->prefix.c_str());
}
else{
if (bus2.init(logger,spi2mosi,spi2miso,spi2clk)){
buses[busId]=&bus2;
}
}
break;
default:
LOG_DEBUG(GwLog::ERROR,"invalid busid %d for %s",busId,sensor->prefix.c_str());
}
}
}
GwConfigHandler *config=api->getConfig();
bool runLoop=false;
GwIntervalRunner timers;
int counterId=api->addCounter("spisensors");
for (auto && sensor:sensors){
if (!sensor->isActive()) continue;
auto bus=buses.find(sensor->busId);
if (bus == buses.end()){
continue;
}
bool rt=sensor->initDevice(api,bus->second);
if (rt){
runLoop=true;
timers.addAction(sensor->intv,[bus,api,sensor,counterId](){
sensor->measure(api,bus->second,counterId);
});
}
}
if (! runLoop){
LOG_DEBUG(GwLog::LOG,"nothing to do for SPI task, finish");
vTaskDelete(NULL);
return;
}
while(true){
delay(100);
timers.loop();
}
vTaskDelete(NULL);
}
void initSpiTask(GwApi *api){
GwLog *logger=api->getLogger();
registerDMS22B(api,sensors);
bool addTask=false;
for (auto && sensor:sensors){
if (sensor->preinit(api)) addTask=true;
}
if (addTask){
api->addUserTask(runSpiTask,"spiTask",3000);
}
else{
LOG_DEBUG(GwLog::LOG,"no SPI sensors defined/active");
}
}

20
lib/spitask/GwSpiTask.h Normal file
View File

@ -0,0 +1,20 @@
/*
(C) Andreas Vogel andreas@wellenvogel.de
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _GWSPITASK_H
#define _GWSPITASK_H
#include "GwApi.h"
void initSpiTask(GwApi *api);
DECLARE_INITFUNCTION(initSpiTask);
#endif

91
lib/spitask/config.json Normal file
View File

@ -0,0 +1,91 @@
[
{
"type": "array",
"name": "DMS22B",
"replace": [
{
"b": "1",
"i": "11",
"n": "0"
},
{
"b": "1",
"i": "12",
"n": "1"
},
{
"b": "2",
"i": "21",
"n": "1"
},
{
"b": "2",
"i": "22",
"n": "22"
}
],
"children": [
{
"name": "DMS22B$iact",
"label": "DMS22BX$i enable",
"type": "boolean",
"default": "true",
"description": "Enable the $i. SSI DMS22B rotary encoder (bus $b)",
"category": "spisensors$b",
"capabilities": {
"DMS22B$i": "true"
}
},
{
"name": "DMS22B$iiid",
"label": "DMS22B$i N2K iid",
"type": "number",
"default": "$n",
"description": "the N2K instance id for the $i. DMS22B Rotary Encoder ",
"category": "spisensors$b",
"min": 0,
"max": 253,
"check": "checkMinMax",
"capabilities": {
"DMS22B$i": "true"
}
},
{
"name": "DMS22B$ifintv",
"label": "DMS22B$i Interval",
"type": "number",
"default": 2,
"description": "Interval(s) to query DMS22B rotation (0.5...10)",
"category": "spisensors$b",
"min": 0.5,
"max": 10,
"check": "checkMinMax",
"capabilities": {
"DMS22B$i": "true"
}
},
{
"name": "DMS22B$izero",
"label": "DMS22B$i Zero",
"type": "calset",
"default": 2048,
"description": "Zero position (0...2^bits-1)\nuse the \"C\" button to open a calibrate dialog",
"category": "spisensors$b",
"capabilities": {
"DMS22B$i": "true"
}
},
{
"name": "DMS22B$iinvt",
"label": "DMS22BX$i invert",
"type": "boolean",
"default": "false",
"description": "Invert the direction of the $i. SSI DMS22B rotary encoder (bus $b)",
"category": "spisensors$b",
"capabilities": {
"DMS22B$i": "true"
}
}
]
}
]

View File

@ -0,0 +1,14 @@
[platformio]
#basically for testing purposes
[env:m5stack-atom-spidms22b]
extends = sensors
board = m5stack-atom
lib_deps =
${env.lib_deps}
${sensors.lib_deps}
build_flags=
-D GWSPI1_CLK=21
-D GWSPI1_MISO=25
-D GWDMS22B11
-D GWDMS22B11_CS=22
${env.build_flags}

View File

@ -332,6 +332,9 @@ public:
api->getLogger()->logDebug(GwLog::LOG,"adding user task %s",tname.c_str());
return true;
}
virtual void setCalibrationValue(const String &name, double value){
api->setCalibrationValue(name,value);
}
};

View File

@ -1,4 +1,5 @@
/*
(C) Andreas Vogel andreas@wellenvogel.de
This code is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
@ -19,6 +20,7 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
#include <Arduino.h>
#include "Preferences.h"
#include "GwApi.h"
#define GW_PINDEFS
#include "GwHardware.h"
#ifndef N2K_LOAD_LEVEL
@ -69,10 +71,6 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
#define MAX_NMEA2000_MESSAGE_SEASMART_SIZE 500
#define MAX_NMEA0183_MESSAGE_SIZE MAX_NMEA2000_MESSAGE_SEASMART_SIZE
//https://curiouser.cheshireeng.com/2014/08/19/c-compile-time-assert/
#define CASSERT(predicate, text) _impl_CASSERT_LINE(predicate,__LINE__)
#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line) typedef char _impl_PASTE(assertion_failed_CASSERT_,line)[(predicate)?1:-1];
//assert length of firmware name and version
CASSERT(strlen(FIRMWARE_TYPE) <= 32, "environment name (FIRMWARE_TYPE) must not exceed 32 chars");
CASSERT(strlen(VERSION) <= 32, "VERSION must not exceed 32 chars");
@ -234,15 +232,38 @@ void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId,bool conv
});
}
class CalibrationValues {
using Map=std::map<String,double>;
Map values;
SemaphoreHandle_t lock;
public:
CalibrationValues(){
lock=xSemaphoreCreateMutex();
}
void set(const String &name,double value){
GWSYNCHRONIZED(&lock);
values[name]=value;
}
bool get(const String &name, double &value){
GWSYNCHRONIZED(&lock);
auto it=values.find(name);
if (it==values.end()) return false;
value=it->second;
return true;
}
};
class ApiImpl : public GwApiInternal
{
private:
int sourceId = -1;
std::unique_ptr<CalibrationValues> calibrations;
public:
ApiImpl(int sourceId)
{
this->sourceId = sourceId;
calibrations.reset(new CalibrationValues());
}
virtual GwRequestQueue *getQueue()
{
@ -331,6 +352,13 @@ public:
virtual bool addUserTask(GwUserTaskFunction task,const String Name, int stackSize=2000){
return false;
}
virtual void setCalibrationValue(const String &name, double value){
calibrations->set(name,value);
}
bool getCalibrationValue(const String &name,double &value){
return calibrations->get(name,value);
}
};
bool delayedRestart(){
@ -382,7 +410,7 @@ public:
protected:
virtual void processRequest()
{
GwJsonDocument status(300 +
GwJsonDocument status(305 +
countNMEA2KIn.getJsonSize()+
countNMEA2KOut.getJsonSize() +
channels.getJsonSize()+
@ -390,6 +418,7 @@ protected:
);
status["version"] = VERSION;
status["wifiConnected"] = gwWifi.clientConnected();
status["wifiSSID"] = config.getString(GwConfigDefinitions::wifiSSID);
status["clientIP"] = WiFi.localIP().toString();
status["apIp"] = gwWifi.apIP();
size_t bsize=2*sizeof(unsigned long)+1;
@ -756,6 +785,7 @@ void setup() {
logger.setWriter(new DefaultLogWriter());
#endif
userCodeHandler.startInitTasks(MIN_USER_TASK);
channels.preinit();
config.stopChanges();
//maybe the user code changed the level
level=config.getInt(config.logLevel,LOGLEVEL);
@ -803,11 +833,24 @@ void setup() {
},
handleConfigRequestData);
webserver.registerHandler("/api/calibrate",[](AsyncWebServerRequest *request){
const String name=request->arg("name");
double value;
if (! apiImpl->getCalibrationValue(name,value)){
request->send(400, "text/plain", "name not found");
return;
}
char buffer[30];
snprintf(buffer,29,"%g",value);
buffer[29]=0;
request->send(200,"text/plain",buffer);
});
webserver.begin();
xdrMappings.begin();
logger.flush();
GwConverterConfig converterConfig;
converterConfig.init(&config);
nmea0183Converter= N2kDataToNMEA0183::create(&logger, &boatData,
[](const tNMEA0183Msg &msg, int sourceId){
SendNMEA0183Message(msg,sourceId,false);
@ -815,7 +858,7 @@ void setup() {
,
config.getString(config.talkerId,String("GP")),
&xdrMappings,
config.getInt(config.minXdrInterval,100)
converterConfig
);
toN2KConverter= NMEA0183DataToN2K::create(&logger,&boatData,[](const tN2kMsg &msg, int sourceId)->bool{
@ -824,7 +867,7 @@ void setup() {
return true;
},
&xdrMappings,
config.getInt(config.min2KInterval,50)
converterConfig
);
NMEA2000.SetN2kCANMsgBufSize(8);
@ -946,7 +989,8 @@ void loopRun() {
preferences.end();
logger.logDebug(GwLog::LOG,"Address Change: New Address=%d\n", SourceAddress);
}
nmea0183Converter->loop();
//potentially send out an own RMC if we did not receive one
nmea0183Converter->loop(toN2KConverter->getLastRmc());
monitor.setTime(8);
//read channels

View File

@ -51,7 +51,8 @@ EXCEPTIONS = [
PLATFORMS = {
"ESP8266": "lx106",
"ESP32": "esp32"
"ESP32": "esp32",
"ESP32S3": "esp32s3"
}
BACKTRACE_REGEX = re.compile(r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b")
@ -142,7 +143,7 @@ class ExceptionDataParser(object):
return None
def parse_file(self, file, platform, stack_only=False):
if platform == 'ESP32':
if platform == 'ESP32' or platform == 'ESP32S3':
func = self._parse_backtrace
else:
func = self._parse_exception

View File

@ -8,6 +8,86 @@
"description": "system name, used for the access point and for services",
"category": "system"
},
{
"name": "stopApTime",
"type": "number",
"default": "0",
"check": "checkMinMax",
"description": "stop the access point after that many minutes if not used",
"category": "system"
},
{
"name": "apPassword",
"type": "password",
"default": "esp32nmea2k",
"check": "checkApPass",
"description": "set the password for the Wifi access point",
"category": "system",
"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",
"type": "boolean",
"default": "true",
"description": "use a password for config modifications",
"category": "system"
},
{
"name": "adminPassword",
"type": "password",
"default": "esp32admin",
"check": "checkAdminPass",
"description": "set the password for config modifications",
"category": "system"
},
{
"name": "showInvalidData",
"label": "show all data",
"type": "boolean",
"default": "true",
"description": "show also not received items on data page",
"category": "system"
},
{
"name":"logLevel",
"label": "log level",
"type":"list",
"default":"0",
"list": [
{"l":"off","v":"-1"},
{"l":"error","v":"0"},
{"l":"log","v":"1"},
{"l":"debug","v":"3"}
],
"description": "log level at the USB port",
"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": "talkerId",
"label": "NMEA0183 ID",
@ -100,87 +180,7 @@
"ZV"
],
"description": "the talkerId used in generated NMEA0183 records",
"category": "system"
},
{
"name": "stopApTime",
"type": "number",
"default": "0",
"check": "checkMinMax",
"description": "stop the access point after that many minutes if not used",
"category": "system"
},
{
"name": "apPassword",
"type": "password",
"default": "esp32nmea2k",
"check": "checkApPass",
"description": "set the password for the Wifi access point",
"category": "system",
"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",
"type": "boolean",
"default": "true",
"description": "use a password for config modifications",
"category": "system"
},
{
"name": "adminPassword",
"type": "password",
"default": "esp32admin",
"check": "checkAdminPass",
"description": "set the password for config modifications",
"category": "system"
},
{
"name": "showInvalidData",
"label": "show all data",
"type": "boolean",
"default": "true",
"description": "show also not received items on data page",
"category": "system"
},
{
"name":"logLevel",
"label": "log level",
"type":"list",
"default":"0",
"list": [
{"l":"off","v":"-1"},
{"l":"error","v":"0"},
{"l":"log","v":"1"},
{"l":"debug","v":"3"}
],
"description": "log level at the USB port",
"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"
"category": "converter"
},
{
"name": "minXdrInterval",
@ -210,6 +210,46 @@
"description":"send out the converted data on the NMEA2000 bus\nIf set to off the converted data will still be shown at the data tab.",
"category":"converter"
},
{
"name":"sendRMCi",
"label":"send RMC interval",
"type": "number",
"description":"interval (ms) to automatically send an RMC if we have valid position data (min 100ms, set to 0 to disable)",
"default":"1000",
"category":"converter"
},
{
"name":"checkRMCt",
"label": "check RMC time",
"type": "number",
"description": "start sending RMC if we did not see an external RMC after this much ms",
"default":"4000",
"min": 1000,
"check":"checkMinMax",
"category":"converter"
},
{
"name": "stbRudderI",
"label":"stb rudder instance",
"type": "number",
"default": "0",
"check": "checkMinMax",
"min": 0,
"max": 253,
"description": "the n2k instance to be used as starboard(main) rudder 0...253",
"category": "converter"
},
{
"name": "portRudderI",
"label":"port rudder instance",
"type": "number",
"default": "-1",
"check": "checkMinMax",
"min": -1,
"max": 253,
"description": "the n2k instance to be used as port rudder 0...253, -1 to disable",
"category": "converter"
},
{
"name": "usbActisense",
"label": "USB mode",

View File

@ -114,6 +114,9 @@ body {
display: flex;
align-items: center;
}
.value button {
margin-left: 0.5em;
}
.hidden{
display: none !important;
}

View File

@ -37,7 +37,7 @@
</div>
<div class="row ">
<span class="label">wifi client connected</span>
<span class="value" id="wifiConnected">---</span>
<span class="value" id="wifiConnected">---</span>&nbsp;[<span class="value" id="wifiSSID">---</span>]
</div>
<div class="row even">
<span class="label">wifi client IP</span>
@ -102,7 +102,7 @@
</div>
<div class="row">
<span class="label">chip type</span>
<span class="value status-chiptype">---</span>
<span class="value status-chipid">---</span>
</div>
<div class="row">
<span class="label">currentVersion</span>
@ -131,7 +131,6 @@
AHA
</div>
<div class="overlayButtons">
<button id="hideOverlay">Close</button>
</div>
</div>
</div>
@ -190,6 +189,17 @@
<div class="hidden">
<a id="downloadXdr"></a>
</div>
<div class="hidden" id="calset">
<h2 class="heading"></h2>
<p class="val"></p>
</div>
<div class="hidden" id="calval">
<h2 class="heading"></h2>
<p class="val"></p>
<label>config
<input type="number"/>
</label>
</div>
</body>
</html>

View File

@ -346,11 +346,15 @@ function createCounterDisplay(parent,label,key,isEven){
}
});
}
function validKey(key){
if (! key) return;
return key.replace(/[^a-z_:A-Z0-9-]/g,'');
}
function updateMsgDetails(key, details) {
forEl('.msgDetails', function (frame) {
if (frame.getAttribute('id') !== key) return;
for (let k in details) {
k=validKey(k);
let el = frame.querySelector("[data-id=\"" + k + "\"] ");
if (!el) {
el = addEl('div', 'row', frame);
@ -371,8 +375,13 @@ function updateMsgDetails(key, details) {
});
}
function showOverlay(text, isHtml) {
function showOverlay(text, isHtml,buttons) {
let el = document.getElementById('overlayContent');
if (text instanceof Object){
el.textContent='';
el.appendChild(text);
}
else {
if (isHtml) {
el.innerHTML = text;
el.classList.remove("text");
@ -381,12 +390,22 @@ function showOverlay(text, isHtml) {
el.textContent = text;
el.classList.add("text");
}
}
buttons=(buttons?buttons:[]).concat([{label:"Close",click:hideOverlay}]);
let container = document.getElementById('overlayContainer');
let btframe=container.querySelector('.overlayButtons');
btframe.textContent='';
buttons.forEach((btconfig)=>{
let bt=addEl('button','',btframe,btconfig.label);
bt.addEventListener("click",btconfig.click);
});
container.classList.remove('hidden');
}
function hideOverlay() {
let container = document.getElementById('overlayContainer');
container.classList.add('hidden');
let el = document.getElementById('overlayContent');
el.textContent='';
}
function checkChange(el, row,name) {
let loaded = el.getAttribute('data-loaded');
@ -456,6 +475,94 @@ function checkCondition(element){
if (visible) row.classList.remove('hidden');
else row.classList.add('hidden');
}
let caliv=0;
function createCalSetInput(configItem,frame,clazz){
let el = addEl('input',clazz,frame);
let cb = addEl('button','',frame,'C');
//el.disabled=true;
cb.addEventListener('click',(ev)=>{
let cs=document.getElementById("calset").cloneNode(true);
cs.classList.remove("hidden");
cs.querySelector(".heading").textContent=configItem.label||configItem.name;
let vel=cs.querySelector(".val");
if (caliv != 0) window.clearInterval(caliv);
caliv=window.setInterval(()=>{
if (document.body.contains(cs)){
fetch("/api/calibrate?name="+encodeURIComponent(configItem.name))
.then((r)=>r.text())
.then((txt)=>{
if (txt != vel.textContent){
vel.textContent=txt;
}
})
.catch((e)=>{
alert(e);
hideOverlay();
window.clearInterval(caliv);
})
}
else{
window.clearInterval(caliv);
}
},200);
showOverlay(cs,false,[{label:'Set',click:()=>{
el.value=vel.textContent;
let cev=new Event('change');
el.dispatchEvent(cev);
}}]);
})
el.setAttribute('name', configItem.name)
return el;
}
function createCalValInput(configItem,frame,clazz){
let el = addEl('input',clazz,frame);
let cb = addEl('button','',frame,'C');
//el.disabled=true;
cb.addEventListener('click',(ev)=>{
const sv=function(val,cfg){
if (configItem.eval){
let v=parseFloat(val);
let c=parseFloat(cfg);
return(eval(configItem.eval));
}
return v;
};
let cs=document.getElementById("calval").cloneNode(true);
cs.classList.remove("hidden");
cs.querySelector(".heading").textContent=configItem.label||configItem.name;
let vel=cs.querySelector(".val");
let vinp=cs.querySelector("input");
vinp.value=el.value;
if (caliv != 0) window.clearInterval(caliv);
caliv=window.setInterval(()=>{
if (document.body.contains(cs)){
fetch("/api/calibrate?name="+encodeURIComponent(configItem.name))
.then((r)=>r.text())
.then((txt)=>{
txt=sv(txt,vinp.value);
if (txt != vel.textContent){
vel.textContent=txt;
}
})
.catch((e)=>{
alert(e);
hideOverlay();
window.clearInterval(caliv);
})
}
else{
window.clearInterval(caliv);
}
},200);
showOverlay(cs,false,[{label:'Set',click:()=>{
el.value=vinp.value;
let cev=new Event('change');
el.dispatchEvent(cev);
}}]);
})
el.setAttribute('name', configItem.name)
return el;
}
function createInput(configItem, frame,clazz) {
let el;
if (configItem.type === 'boolean' || configItem.type === 'list' || configItem.type == 'boatData') {
@ -492,6 +599,12 @@ function createInput(configItem, frame,clazz) {
if (configItem.type === 'xdr'){
return createXdrInput(configItem,frame,clazz);
}
if (configItem.type === "calset"){
return createCalSetInput(configItem,frame,clazz);
}
if (configItem.type === "calval"){
return createCalValInput(configItem,frame,clazz);
}
el = addEl('input',clazz,frame);
if (configItem.readOnly) el.setAttribute('disabled',true);
el.setAttribute('name', configItem.name)
@ -1506,7 +1619,9 @@ function createDashboard() {
frame.innerHTML = '';
}
function sourceName(v){
if (v == 0) return "N2K";
for (let n in channelList){
if (v == channelList[n].id) return n;
if (v >= channelList[n].id && v <= channelList[n].max){
return n;
}

View File

@ -36,86 +36,122 @@
#
#
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
key: m5groovei2c#grv#
label: "M5 I2C Groove Units"
children:
- label: "M5 ENV3"
type: checkbox
key: m5env3
key: m5env3#grv#
target: define
url: "https://docs.m5stack.com/en/unit/envIII"
description: "M5 sensor module temperature, humidity, pressure"
values:
- value: M5_ENV3
- value: M5_ENV3#grv#
key: true
resource: qmp69881#grv#1,sht3x#grv#1
- type: checkbox
label: SHT3X-1
description: "SHT30 temperature and humidity sensor 0x44"
key: sht3xg1
target: define
url: "https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/SHT3x_Datasheet_digital.pdf"
values:
- key: true
value: GWSHT3XG1#grv#
resource: sht3x#grv#1
- type: checkbox
label: SHT3X-2
description: "SHT30 temperature and humidity sensor 0x45"
key: sht3xg2
target: define
url: "https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/SHT3x_Datasheet_digital.pdf"
values:
- key: true
value: GWSHT3XG2#grv#
resource: sht3x#grv#2
- type: checkbox
label: QMP6988-1
description: "QMP6988 pressure sensor addr 86"
key: qmp69881g1
target: define
url: "https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/enviii/QMP6988%20Datasheet.pdf"
values:
- key: true
value: GWQMP6988G1#grv#
resource: qmp69881#grv#1
- type: checkbox
label: QMP6988-2
description: "QMP6988 pressure sensor addr 112"
key: qmp69882g2
target: define
url: "https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/unit/enviii/QMP6988%20Datasheet.pdf"
values:
- key: true
value: GWQMP6988G2#grv#
resource: qmp69881#grv#2
- type: checkbox
label: BME280-1
description: "BME280 temperature/humidity/pressure sensor 0x76"
key: bme2801g1
target: define
url: "https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf"
values:
- key: true
value: GWBME280G1#grv#
resource: bme280#grv#1
- type: checkbox
label: BME280-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: GWBME280G2#grv#
resource: bme280#grv#2
- &m5groovecan
type: select
key: m5groovecan
key: m5groovecan#grv#
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
value: M5_CANUNIT#grv#
resource: can
- &m5grooveserial
type: select
label: "M5 Groove Serial Unit"
target: define
key: m5grooveserial
key: m5grooveserial#grv#
values:
- label: "RS485"
key: unit485
value: SERIAL_GROOVE_485
value: SERIAL_GROOVE_485#grv#
description: "M5 RS485 unit"
url: "https://docs.m5stack.com/en/unit/rs485"
resource: serial
- label: "Tail485"
value: SERIAL_GROOVE_485
value: SERIAL_GROOVE_485#grv#
key: tail485
description: "M5 Tail 485"
url: "https://docs.m5stack.com/en/atom/tail485"
resource: serial
- label: "Gps Unit"
value: M5_GPS_UNIT
value: M5_GPS_UNIT#grv#
description: "M5 Gps Unit"
url: "https://docs.m5stack.com/en/unit/gps"
resource: serial
- &m5groove
type: select
key: m5groove
label: 'M5 groove type'
key: m5groove#grv#
label: 'M5 groove#grv# type '
help: 'Select the functionality that should be available at the M5 groove pins'
values:
- key: 'CAN'
@ -131,7 +167,9 @@ types:
type: dropdown
resource: "gpio:"
help: 'Select the number of the GPIO pin for this function'
values:
values: "#gpiopinv#"
- &gpiopinv
- {label: unset,value:}
- {label: "0: Low at boot!",value: 0}
- 1
@ -164,7 +202,9 @@ types:
type: dropdown
resource: "gpio:"
help: 'Select the number of the GPIO pin for this function'
values:
values: "#gpiopinv#"
- &gpioinputv
- {label: unset,value:}
- {label: "0: Low at boot!",value: 0}
- 1
@ -197,6 +237,15 @@ types:
- 38
- 39
- &protogpio
- {label: unset,value:}
- PPIN19
- PPIN21
- PPIN22
- PPIN23
- PPIN25
- PPIN33
- &serialRX
<<: *gpioinput
key: RX
@ -249,6 +298,7 @@ types:
type: checkbox
label: 'Serial 1'
key: serial1
resource: serial1
base:
serial: GWSERIAL_
values: *serialValues
@ -257,6 +307,7 @@ types:
type: checkbox
label: 'Serial 2'
key: serial2
resource: serial2
base:
serial: GWSERIAL2_
values: *serialValues
@ -374,6 +425,7 @@ types:
type: checkbox
label: "I2C #busname#"
key: "i2c#busname#"
resource: "i2c#busname#"
description: "I2C Bus #busname#"
values:
- key: true
@ -443,6 +495,136 @@ types:
- key: true
value: GWBME280#busname#2
- &spisensors
type: checkbox
label: "SPI/SSI #busname#"
key: "spi#busname#"
resource: "spi#busname#"
description: "SPI(SSI) Bus #busname#"
values:
- key: true
children:
- <<: *gpiopin
label: CLK
key: clk
mandatory: true
target: "define:GWSPI#bus#_CLK"
- <<: *gpiopin
label: MISO
key: miso
mandatory: false
target: "define:GWSPI#bus#_MISO"
- <<: *gpiopin
label: MOSI
key: mosi
mandatory: false
target: "define:GWSPI#bus#_MOSI"
description: "GPIO pin for MOSI, not necessary for SSI"
- type: checkbox
label: GWDMS22B-#busname#-1
description: "DMS22B rotatory encoder (SSI)"
key: dms22b#busname#1
target: define
url: "https://www.mouser.de/datasheet/2/54/bour_s_a0011704065_1-2262614.pdf"
values:
- key: true
value: GWDMS22B#busname#1
children:
- <<: *gpiopin
label: CS
key: dms22b#busname#1cs
description: "chip select pin, only necessary for multiple devices on this bus"
target: "define:GWDMS22B#busname#1_CS"
- type: checkbox
label: GWDMS22B-#busname#-2
description: "DMS22B rotatory encoder (SSI)"
key: dms22b#busname#2
target: define
url: "https://www.mouser.de/datasheet/2/54/bour_s_a0011704065_1-2262614.pdf"
values:
- key: true
value: GWDMS22B#busname#2
children:
- <<: *gpiopin
label: CS
key: dms22b#busname#2cs
description: "chip select pin, only necessary for multiple devices on this bus"
target: "define:GWDMS22B#busname#2_CS"
- &m5protochildren
- *serial1
- *serial2
- *can
- <<: *iicsensors
base:
busname: "1"
bus: ""
- <<: *iicsensors
base:
busname: "2"
bus: "2"
- <<: *spisensors
base:
busname: "1"
bus: "1"
- <<: *spisensors
base:
busname: "2"
bus: "2"
- &m5pabcchildren
- <<: *m5groove
base:
grv: _A
- <<: *m5groove
base:
grv: _B
- <<: *m5groove
base:
grv: _C
- &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
- value: M5_PROTO_HUB
description: "M5 Stack HUB PROTO"
url: "https://docs.m5stack.com/en/atom/atomhub"
label: "Hub Proto"
base:
gpioinputv: *protogpio
gpiopinv: *protogpio
children:
*m5protochildren
- value: M5_PORTABC
description: "M5 Stack Port ABC extension base"
url: "https://docs.m5stack.com/en/unit/AtomPortABC"
label: "ABC Ext"
children:
*m5pabcchildren
resources:
default: &esp32default
@ -457,6 +639,10 @@ config:
target: environment
label: 'Board'
key: board
base:
gpiopinv: *gpiopinv
gpioinputv: *gpioinputv
grv: ""
values:
- value: m5stack-atom-generic
label: m5stack-atom
@ -502,3 +688,11 @@ config:
base:
busname: "2"
bus: "2"
- <<: *spisensors
base:
busname: "1"
bus: "1"
- <<: *spisensors
base:
busname: "2"
bus: "2"

View File

@ -119,7 +119,7 @@ class PipelineInfo{
.then((st)=>{
if (queryPipeline !== currentPipeline.id) return;
let stid=st.pipeline_id||st.id;
if (currentPipeline.id !== stid) return;
if (stid !== undefined && currentPipeline.id !== stid) return;
if (st.status === undefined) st.status=st.state;
currentPipeline.update(st);
updateStatus();
@ -516,6 +516,11 @@ class PipelineInfo{
});
}
}
if (expandedValues.length > 0 && config.type === 'display'){
let cb=addEl('div','t'+config.type,inputFrame);
addDescription(config,inputFrame);
initialConfig=expandedValues[0];
}
let childFrame=addEl('div','childFrame',frame);
if (initialConfig !== undefined){
callback(initialConfig,true,childFrame);
@ -573,7 +578,7 @@ class PipelineInfo{
(child,initial,opt_frame)=>{
if(cfg.key !== undefined) removeSelectors(name,!initial);
if (! initial) isModified=true;
buildSelectors(name,child.children,initial,currentBase,opt_frame||childFrame);
buildSelectors(name,child.children,initial,Object.assign({},currentBase,child.base),opt_frame||childFrame);
if (cfg.key !== undefined) configStruct[name]={cfg:child,base:currentBase};
buildValues(initial);
})
@ -583,9 +588,18 @@ class PipelineInfo{
if (! base) return str;
if (typeof(str) === 'string'){
for (let k in base){
if (typeof(base[k]) !== 'string'){
//special replacement
//for complete parts
if (str === '#'+k+'#'){
return base[k];
}
}
else{
let r=new RegExp("#"+k+"#","g");
str=str.replace(r,base[k]);
}
}
return str;
}
if (str instanceof Array){
@ -598,7 +612,8 @@ class PipelineInfo{
if (str instanceof Object){
let rt={};
for (let k in str){
rt[k]=replaceValues(str[k],base);
if (k == 'children') rt[k]=str[k];
else rt[k]=replaceValues(str[k],base);
}
return rt;
}
@ -637,12 +652,15 @@ class PipelineInfo{
}
if (round < 1) continue;
if (struct.resource){
let resList=currentResources[struct.resource];
let splitted=struct.resource.split(",");
splitted.forEach((resource) => {
let resList = currentResources[resource];
if (!resList) {
resList = [];
currentResources[struct.resource]=resList;
currentResources[resource] = resList;
}
resList.push(struct);
});
}
if (target === 'define') {
flags += " -D" + struct.value;
@ -665,11 +683,12 @@ class PipelineInfo{
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";
}
let allowed=allowedResources[ak];
if (allowed === undefined) allowed=1;
if (resList.length > allowed){
errors+=" more than "+allowed+" "+k+" device(s) used";
}
}
if (errors){
setValue('configError',errors);