Merge branch 'wellenvogel:master' into master

This commit is contained in:
Norbert Walter 2024-11-30 17:03:11 +01:00 committed by GitHub
commit 4f0fd9721e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 804 additions and 578 deletions

View File

@ -170,6 +170,17 @@ For details refer to the [example description](lib/exampletask/Readme.md).
Changelog
---------
[20241128](../../releases/tag/20241128)
*********
* additional correction for: USB connection on S3 stops [#81](../../issues/81)
* [#71](../../pull/71): add BMP280 to [IIC Sensors](doc/Sensors.md), send 130311 for BMP380 and BME380
* add an api function to add [own Sensors](doc/Sensors.md)
* use a lock on the USB connection write site to avoid problems with NMEA and logs at the same time
* allow to show unmapped XDR values in the data display
* fix a bug that made the dashboard page disappear after a restart of the device
* correctly handle empty fields in RMB messages
* call the newly introduced web request handler for user tasks outside of an API lock
[20241114](../../releases/tag/20241114)
**********
* UDP writer and reader - [#79](../../issues/79)

View File

@ -1,6 +1,6 @@
Sensors
=======
The software contains support for a couple of sensors (starting from [20231228](../../releases/tag/20231228) and extend in consecutive releases).
The software contains support for a couple of sensors (starting from [20231228](../../releases/tag/20231228) and extended in consecutive releases).
This includes some I2C Sensors and SSI rotary encoders.
To connect sensors the following steps are necessary:
@ -29,9 +29,57 @@ 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).
Implementing Own Sensors
---------------------
To add an own sensor implementation you typically need to handle the following parts:
* (opt) add a library that supports your sensor
* add some [XDR Mapping](./XdrMappings.md) that will convert your generated NMEA2000 message into an NMEA0183 XDR record and ensure the display on the data page
* implement the sensor initialization
* implement the measurement and generating the NMEA2000 message
You typically would do this in a [user task](../lib/exampletask/Readme.md).<br>
You can either just implement everything by your own or reuse the existing infrastructure for sensors.
OwnImplementation
__________________
To implement everything by your own just create a config.json for the parameters you need, add an XDR mapping in a task init function (see e.g. [PressureXdr](../lib/iictask/GwIicSensors.h#L27)).
In your user taks just initialize the sensor using your config values and add a loop that periodically measures the sensor value and sends out an nmea2000 message (using the [api->sendN2KMessage](../lib/api/GwApi.h#L137)).
To display some information on the status page just add a countergroup in your task init function ([api->addCounter](../lib/api/GwApi.h#L170)) and increment a counter of this group on every measure ([api->increment](../lib/api/GwApi.h#L171)).
To utilize a bus you typically would need to add the related library to your environment and add some bus initialization to define the pins that are used for this particular bus.
Be carefull if you additionally would like to use sensors from the core as the core potentially would already initialize some bus - depending on the compile flags you provide.
If you need additional libraries for your sensor just add a platformio.ini to your usertask and define an environment that contains the additional libraries.
If you would like to compile also other environments (i.e. without the additional libraries) you should wrap all the code that references the additional libraries with some #ifdef and add a define to your environment (see the implementations of the [sensors in the core](../lib/iictask/)).
Using the core infrastructure
_____________________________
For sensors of bus types that are already supported by the core (mainly I2C) you can simplify your implementation.
Just also start with a [usertask](../lib/exampletask/Readme.md). But you only need the task init function, a config.json and potentially a platformio.ini.
In your task code just implement a class that handles the sensor - it should inherit from [SensorBase](../lib/sensors/GwSensor.h#L20) or from [IICSensorBase](../lib/iictask/GwIicSensors.h#L16).<br>
You need to implement:
* _readConfig_ - just read your configuration and fill attributes of your class. Especially set the "ok" to true and fill the interval field to define your measure interval.
* _preinit_ - check if your snesor is configured ,add necessary XDR mappings and return true if your sensor is active. Do __not__ yet initialize the sensor hardware.
* _isActive_ - return true if your sensor is active
* _initDevice_ - init your sensor hardware and return true if this was ok
* _measure_ - read the sensor data, send NMEA2000 messages and increment
counters
The busType and busId fields of your imnplementation have to be set correctly.<br>
In your task init function add the sensors you would like to be handled using [api->addSensor](../lib/api/GwApi.h#L218).
All the internal sensors are implemented using this approach - e.g. [BME280](../lib/iictask/GwBME280.cpp#L23).<br>
Do not get confused by all the different defines and the special config handling - this is only there to be as generic as possible - typically not necessary for your own sensor implementation.
To use an IIC bus you need to compile with flags that define the pins to be used for the IIC bus:
* busId 1 (IIC bus 1): GWIIC_SDA, GWIIC_SCL
* busId 2 (IIC bus 2): GWIIC_SDA2, GWIIC_SCL2
So you would need to add such definitions to your environment in your platformio.ini.
Implemented Sensors
-------------------
* [BME280](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf): temperature/humidity/pressure [PGNs: 130314,130312, 130313]
* [BME280](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf): temperature/humidity/pressure [PGNs: 130314,130312, 130313, 130311 since 20241128 ]
* [BMP280](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) [since 20241128]: temperature/pressure [PGNs: 130314,130312, 130311]
* [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]

View File

@ -9,6 +9,9 @@
#include "GwSynchronized.h"
#include <map>
#include <ESPAsyncWebServer.h>
#include "GwSensor.h"
#include <functional>
#include <memory>
class GwApi;
typedef void (*GwUserTaskFunction)(GwApi *);
//API to be used for additional tasks
@ -43,11 +46,7 @@ class GwApi{
* the core part will not handle this data at all but
* this interface ensures that there is a correct locking of the
* data to correctly handle multi threading
* The user code should not use this intterface directly
* but instead it should use the static functions
* apiGetXXX(GwApi *,...)
* apiSetXXX(GwApi *,...)
* that will be created by the macro DECLARE_TASK_INTERFACE
* there is no protection - i.e. every task can get and set the data
*/
class TaskInterfaces
{
@ -61,9 +60,9 @@ class GwApi{
};
using Ptr = std::shared_ptr<Base>;
protected:
virtual bool iset(const String &file, const String &name, Ptr v) = 0;
virtual bool iset(const String &name, Ptr v) = 0;
virtual Ptr iget(const String &name, int &result) = 0;
virtual bool iclaim(const String &name, const String &task)=0;
virtual bool iupdate(const String &name,std::function<Ptr(Ptr v)>)=0;
public:
template <typename T>
bool set(const T &v){
@ -76,6 +75,10 @@ class GwApi{
}
template <typename T>
bool claim(const String &task){
return true;
}
template <typename T>
bool update(std::function<bool(T *)>){
return false;
}
};
@ -206,7 +209,13 @@ class GwApi{
* @param name: the config name this value is used for
* @param value: the current value
*/
virtual void setCalibrationValue(const String &name, double value);
virtual void setCalibrationValue(const String &name, double value)=0;
/**
* add a sensor
* depending on the type it will be added to the appropriate task
* @param sensor: created sensor config
*/
virtual void addSensor(SensorBase* sensor,bool readConfig=true){};
/**
* not thread safe methods
@ -231,7 +240,10 @@ static void checkDef(T... args){};
#define DECLARE_USERTASK_PARAM(task,...)
#endif
#ifndef DECLARE_INITFUNCTION
#define DECLARE_INITFUNCTION(task)
#define DECLARE_INITFUNCTION(task,...)
#endif
#ifndef DECLARE_INITFUNCTION_ORDER
#define DECLARE_INITFUNCTION_ORDER(task,...)
#endif
#ifndef DECLARE_CAPABILITY
#define DECLARE_CAPABILITY(name,value)
@ -255,27 +267,20 @@ static void checkDef(T... args){};
* int ival2=99;
* String sval="unset";
* };
* DECLARE_TASKIF(testTask,TestTaskApi);
* The macro will generate 2 static funtions:
* DECLARE_TASKIF(TestTaskApi);
*
* bool apiSetTestTaskApi(GwApi *api, const TestTaskApi &v);
* TestTaskApi apiGetTestTaskApi(GwApi *api, int &result);
*
* The setter will return true on success.
* It is intended to be used by the task that did declare the api.
* The getter will set the result to -1 if no data is available, otherwise
* it will return the update count (so you can check if there was a change
* compared to the last call).
* It is intended to be used by any task.
* Be aware that all the apis share a common namespace - so be sure to somehow
* make your API names unique.
*
* To utilize this interface a task can call:
* api->taskInterfaces()->get<TestTaskApi>(res) //and check the result in res
* api->taskInterfaces()->set<TestTaskApi>(value)
*
*/
#define DECLARE_TASKIF_IMPL(type) \
template<> \
inline bool GwApi::TaskInterfaces::set(const type & v) {\
return iset(__FILE__,#type,GwApi::TaskInterfaces::Ptr(new type(v))); \
return iset(#type,GwApi::TaskInterfaces::Ptr(new type(v))); \
}\
template<> \
inline type GwApi::TaskInterfaces::get<type>(int &result) {\
@ -286,13 +291,42 @@ static void checkDef(T... args){};
}\
type *tp=(type*)ptr.get(); \
return type(*tp); \
}\
} \
template<> \
inline bool GwApi::TaskInterfaces::claim<type>(const String &task) {\
return iclaim(#type,task);\
}\
inline bool GwApi::TaskInterfaces::update(std::function<bool(type *)> f) { \
return iupdate(#type,[f](GwApi::TaskInterfaces::Ptr cp)->GwApi::TaskInterfaces::Ptr{ \
if (cp) { \
f((type *)cp.get()); \
return cp; \
} \
type * et=new type(); \
bool res=f(et); \
if (! res){ \
delete et; \
return GwApi::TaskInterfaces::Ptr(); \
} \
return GwApi::TaskInterfaces::Ptr(et); \
}); \
} \
#ifndef DECLARE_TASKIF
#define DECLARE_TASKIF(type) DECLARE_TASKIF_IMPL(type)
#endif
/**
* do not use this interface directly
* instead use the API function addSensor
*/
class ConfiguredSensors : public GwApi::TaskInterfaces::Base{
public:
SensorList sensors;
};
DECLARE_TASKIF(ConfiguredSensors);
//order for late init functions
//all user tasks should have lower orders (default: 0)
#define GWLATEORDER 9999
#endif

View File

@ -427,6 +427,7 @@ void GwChannelList::begin(bool fallbackSerial){
if (! fallbackSerial){
GwSerial *usbSerial=createSerialImpl(config, logger,USB_CHANNEL_ID,GWUSB_RX,GWUSB_TX,true);
if (usbSerial != nullptr){
usbSerial->enableWriteLock(); //as it is used for logging we need this additionally
GwChannel *usbChannel=createChannel(logger,config,USB_CHANNEL_ID,usbSerial,GWSERIAL_TYPE_BI);
if (usbChannel != nullptr){
addChannel(usbChannel);

View File

@ -71,9 +71,12 @@ class GwConverterConfig{
int rmcInterval=1000;
int rmcCheckTime=4000;
int winst312=256;
bool unmappedXdr=false;
unsigned long xdrTimeout=60000;
std::vector<WindMapping> windMappings;
void init(GwConfigHandler *config, GwLog*logger){
minXdrInterval=config->getInt(GwConfigDefinitions::minXdrInterval,100);
xdrTimeout=config->getInt(GwConfigDefinitions::timoSensor);
starboardRudderInstance=config->getInt(GwConfigDefinitions::stbRudderI,0);
portRudderInstance=config->getInt(GwConfigDefinitions::portRudderI,-1);
min2KInterval=config->getInt(GwConfigDefinitions::min2KInterval,50);
@ -83,6 +86,7 @@ class GwConverterConfig{
rmcInterval=config->getInt(GwConfigDefinitions::sendRMCi,1000);
if (rmcInterval < 0) rmcInterval=0;
if (rmcInterval > 0 && rmcInterval <100) rmcInterval=100;
unmappedXdr=config->getBool(GwConfigDefinitions::unknownXdr);
winst312=config->getInt(GwConfigDefinitions::winst312,256);
for (auto && it:windConfigs){
String cfg=config->getString(it.second);

View File

@ -156,11 +156,11 @@ class ExampleWebData{
vSemaphoreDelete(lock);
}
void set(int v){
GWSYNCHRONIZED(&lock);
GWSYNCHRONIZED(lock);
data=v;
}
int get(){
GWSYNCHRONIZED(&lock);
GWSYNCHRONIZED(lock);
return data;
}
};

View File

@ -108,7 +108,7 @@ void GwWifi::loop(){
}
else{
if (! clientIsConnected){
LOG_DEBUG(GwLog::LOG,"wifiClient %s now connected to",wifiSSID->asCString());
LOG_DEBUG(GwLog::LOG,"wifiClient now connected to %s at %s",wifiSSID->asCString(),WiFi.localIP().toString().c_str());
clientIsConnected=true;
}
}

View File

@ -64,15 +64,15 @@
#endif
#GROVE
#ifdef M5_ENV4$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(SHT3X,$Z$,1)
GROOVE_IIC(BMP280,$Z$,1)
#define _GWSHT3X
#define _GWBMP280
#endif
//#ifdef M5_ENV4$GS$
// #ifndef M5_GROOVEIIC$GS$
// #define M5_GROOVEIIC$GS$
// #endif
// GROOVE_IIC(SHT3X,$Z$,1)
// GROOVE_IIC(BMP280,$Z$,1)
// #define _GWSHT3X
// #define _GWBMP280
//#endif
#GROVE
//example: -DSHT3XG1_A : defines STH3Xn1 on grove A - x depends on the other devices

View File

@ -15,11 +15,9 @@
#include <Adafruit_BME280.h>
#endif
#ifdef _GWBME280
#define TYPE "BME280"
#define PRFX1 TYPE "11"
#define PRFX2 TYPE "12"
#define PRFX3 TYPE "21"
#define PRFX4 TYPE "22"
class BME280Config;
static GwSensorConfigInitializerList<BME280Config> configs;
class BME280Config : public IICSensorBase{
public:
bool prAct=true;
@ -35,7 +33,7 @@ class BME280Config : public IICSensorBase{
float prOff=0;
Adafruit_BME280 *device=nullptr;
uint32_t sensorId=-1;
BME280Config(GwApi * api, const String &prfx):SensorBase(TYPE,api,prfx){
BME280Config(GwApi * api, const String &prfx):IICSensorBase(api,prfx){
}
virtual bool isActive(){return prAct||huAct||tmAct;}
virtual bool initDevice(GwApi *api,TwoWire *wire){
@ -57,7 +55,6 @@ class BME280Config : public IICSensorBase{
virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
api->addCapability(prefix,"true");
addPressureXdr(api,*this);
addTempXdr(api,*this);
addHumidXdr(api,*this);
@ -97,96 +94,80 @@ class BME280Config : public IICSensorBase{
sendN2kEnvironmentalParameters(api, *this, temperature, humidity, computed,counterId);
}
}
#define CFG280(prefix) \
CFG_GET(prAct,prefix); \
CFG_GET(tmAct,prefix);\
CFG_GET(huAct,prefix);\
CFG_GET(tmSrc,prefix);\
CFG_GET(huSrc,prefix);\
CFG_GET(iid,prefix);\
CFG_GET(intv,prefix);\
CFG_GET(tmNam,prefix);\
CFG_GET(huNam,prefix);\
CFG_GET(prNam,prefix);\
CFG_GET(tmOff,prefix);\
CFG_GET(prOff,prefix);
virtual void readConfig(GwConfigHandler *cfg) override
{
if (ok) return;
if (prefix == PRFX1)
{
busId = 1;
addr = 0x76;
CFG280(BME28011);
ok=true;
}
if (prefix == PRFX2)
{
busId = 1;
addr = 0x77;
CFG280(BME28012);
ok=true;
}
if (prefix == PRFX3)
{
busId = 2;
addr = 0x76;
CFG280(BME28021);
ok=true;
}
if (prefix == PRFX4)
{
busId = 2;
addr = 0x77;
CFG280(BME28022);
ok=true;
}
intv *= 1000;
configs.readConfig(this,cfg);
}
};
static IICSensorBase::Creator creator([](GwApi *api, const String &prfx){
static SensorBase::Creator creator([](GwApi *api, const String &prfx){
return new BME280Config(api,prfx);
});
IICSensorBase::Creator registerBME280(GwApi *api,IICSensorList &sensors){
#if defined(GWBME280) || defined(GWBME28011)
SensorBase::Creator registerBME280(GwApi *api){
#if defined(GWBME280) || defined(GWBME28011)
{
auto *cfg=creator(api,PRFX1);
sensors.add(api,cfg);
api->addSensor(creator(api,"BME28011"));
CHECK_IIC1();
#pragma message "GWBME28011 defined"
}
#endif
#if defined(GWBME28012)
#endif
#if defined(GWBME28012)
{
auto *cfg=creator(api,PRFX2);
sensors.add(api,cfg);
api->addSensor(creator(api,"BME28012"));
CHECK_IIC1();
#pragma message "GWBME28012 defined"
}
#endif
#if defined(GWBME28021)
#endif
#if defined(GWBME28021)
{
auto *cfg=creator(api,PRFX3);
sensors.add(api,cfg);
api->addSensor(creator(api,"BME28021"));
CHECK_IIC2();
#pragma message "GWBME28021 defined"
}
#endif
#if defined(GWBME28022)
#endif
#if defined(GWBME28022)
{
auto *cfg=creator(api,PRFX4);
sensors.add(api,cfg);
api->addSensor(creator(api,"BME28022"));
CHECK_IIC1();
#pragma message "GWBME28022 defined"
}
#endif
#endif
return creator;
}
#define CFG280(s, prefix, bus, baddr) \
CFG_SGET(s, prAct, prefix); \
CFG_SGET(s, tmAct, prefix); \
CFG_SGET(s, huAct, prefix); \
CFG_SGET(s, tmSrc, prefix); \
CFG_SGET(s, huSrc, prefix); \
CFG_SGET(s, iid, prefix); \
CFG_SGET(s, intv, prefix); \
CFG_SGET(s, tmNam, prefix); \
CFG_SGET(s, huNam, prefix); \
CFG_SGET(s, prNam, prefix); \
CFG_SGET(s, tmOff, prefix); \
CFG_SGET(s, prOff, prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \
s->intv *= 1000;
#define SCBME280(list, prefix, bus, addr) \
GWSENSORCONFIG(list, BME280Config, prefix, [](BME280Config *s, GwConfigHandler *cfg) { CFG280(s, prefix, bus, addr); });
SCBME280(configs,BME28011,1,0x76);
SCBME280(configs,BME28012,1,0x77);
SCBME280(configs,BME28021,2,0x76);
SCBME280(configs,BME28022,2,0x77);
#else
IICSensorBase::Creator registerBME280(GwApi *api,IICSensorList &sensors){
return IICSensorBase::Creator();
SensorBase::Creator registerBME280(GwApi *api){
return SensorBase::Creator();
}
#endif

View File

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

View File

@ -15,11 +15,16 @@
#include <Adafruit_BMP280.h>
#endif
#ifdef _GWBMP280
#define TYPE "BMP280"
#define PRFX1 TYPE "11"
#define PRFX2 TYPE "12"
#define PRFX3 TYPE "21"
#define PRFX4 TYPE "22"
/**
* we need a forward declaration here as the config list has to go before the
* class implementation
*/
class BMP280Config;
static GwSensorConfigInitializerList<BMP280Config> configs;
class BMP280Config : public IICSensorBase{
public:
bool prAct=true;
@ -33,14 +38,13 @@ class BMP280Config : public IICSensorBase{
float prOff=0;
Adafruit_BMP280 *device=nullptr;
uint32_t sensorId=-1;
BMP280Config(GwApi * api, const String &prfx):SensorBase(TYPE,api,prfx){
}
using IICSensorBase::IICSensorBase;
virtual bool isActive(){return prAct||tmAct;}
virtual bool initDevice(GwApi *api,TwoWire *wire){
GwLog *logger=api->getLogger();
device= new Adafruit_BMP280(wire);
if (! device->begin(addr)){
LOG_DEBUG(GwLog::ERROR,"unable to initialize %s at %d",prefix.c_str(),addr);
LOG_DEBUG(GwLog::ERROR,"unable to initialize %s at 0x%x",prefix.c_str(),addr);
delete device;
device=nullptr;
return false;
@ -52,7 +56,6 @@ class BMP280Config : public IICSensorBase{
virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
api->addCapability(prefix,"true");
addPressureXdr(api,*this);
addTempXdr(api,*this);
return isActive();
@ -85,96 +88,90 @@ class BMP280Config : public IICSensorBase{
sendN2kEnvironmentalParameters(api, *this, temperature, humidity, computed,counterId);
}
}
#define CFGBMP280(prefix) \
CFG_GET(prAct,prefix); \
CFG_GET(tmAct,prefix);\
CFG_GET(tmSrc,prefix);\
CFG_GET(iid,prefix);\
CFG_GET(intv,prefix);\
CFG_GET(tmNam,prefix);\
CFG_GET(prNam,prefix);\
CFG_GET(tmOff,prefix);\
CFG_GET(prOff,prefix);
virtual void readConfig(GwConfigHandler *cfg) override
{
if (prefix == PRFX1)
{
busId = 1;
addr = 0x76;
CFGBMP280(BMP28011);
ok=true;
}
if (prefix == PRFX2)
{
busId = 1;
addr = 0x77;
CFGBMP280(BMP28012);
ok=true;
}
if (prefix == PRFX3)
{
busId = 2;
addr = 0x76;
CFGBMP280(BMP28021);
ok=true;
}
if (prefix == PRFX4)
{
busId = 2;
addr = 0x77;
CFGBMP280(BMP28022);
ok=true;
}
intv *= 1000;
if (ok) return;
configs.readConfig(this,cfg);
}
};
static IICSensorBase::Creator creator([](GwApi *api, const String &prfx){
static SensorBase::Creator creator([](GwApi *api, const String &prfx)->BMP280Config*{
if (! configs.knowsPrefix(prfx)){
return nullptr;
}
return new BMP280Config(api,prfx);
});
IICSensorBase::Creator registerBMP280(GwApi *api,IICSensorList &sensors){
SensorBase::Creator registerBMP280(GwApi *api){
#if defined(GWBMP280) || defined(GWBMP28011)
{
auto *cfg=creator(api,PRFX1);
//BMP280Config *cfg=new BMP280Config(api,PRFX1);
sensors.add(api,cfg);
api->addSensor(creator(api,"BMP28011"));
CHECK_IIC1();
#pragma message "GWBMP28011 defined"
}
#endif
#if defined(GWBMP28012)
{
auto *cfg=creator(api,PRFX2);
//BMP280Config *cfg=new BMP280Config(api,PRFX2);
sensors.add(api,cfg);
api->addSensor(creator(api,"BMP28012"));
CHECK_IIC1();
#pragma message "GWBMP28012 defined"
}
#endif
#if defined(GWBMP28021)
{
auto *cfg=creator(api,PRFX3);
//BMP280Config *cfg=new BMP280Config(api,PRFX3);
sensors.add(api,cfg);
api->addSensor(creator(api,"BMP28021"));
CHECK_IIC2();
#pragma message "GWBMP28021 defined"
}
#endif
#if defined(GWBMP28022)
{
auto *cfg=creator(api,PRFX4);
//BMP280Config *cfg=new BMP280Config(api,PRFX4);
sensors.add(api,cfg);
api->addSensor(creator(api,"BMP28022"));
CHECK_IIC1();
#pragma message "GWBMP28022 defined"
}
#endif
return creator;
}
/**
* a define for the readConfig function
* we use a define here as we want to be able to check the config
* definitions at compile time
*/
#define CFGBMP280P(s, prefix, bus, baddr) \
CFG_SGET(s, prAct, prefix); \
CFG_SGET(s, tmAct, prefix); \
CFG_SGET(s, tmSrc, prefix); \
CFG_SGET(s, iid, prefix); \
CFG_SGET(s, intv, prefix); \
CFG_SGET(s, tmNam, prefix); \
CFG_SGET(s, prNam, prefix); \
CFG_SGET(s, tmOff, prefix); \
CFG_SGET(s, prOff, prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \
s->intv*=1000;
/**
* a config initializer for our sensor
*/
#define SCBMP280(list, prefix, bus, addr) \
GWSENSORCONFIG(list, BMP280Config, prefix, [](BMP280Config *s, GwConfigHandler *cfg) { CFGBMP280P(s, prefix, bus, addr); });
/**
* four possible sensor configs
*/
SCBMP280(configs, BMP28011, 1, 0x76);
SCBMP280(configs, BMP28012, 1, 0x77);
SCBMP280(configs, BMP28021, 2, 0x76);
SCBMP280(configs, BMP28022, 2, 0x77);
#else
IICSensorBase::Creator registerBMP280(GwApi *api,IICSensorList &sensors){
return IICSensorBase::Creator();
SensorBase::Creator registerBMP280(GwApi *api){
return SensorBase::Creator();
}
#endif

View File

@ -1,6 +1,6 @@
#ifndef _GWBMP280_H
#define _GWBMP280_H
#include "GwIicSensors.h"
IICSensorBase::Creator registerBMP280(GwApi *api,IICSensorList &sensors);
SensorBase::Creator registerBMP280(GwApi *api);
#endif

View File

@ -11,9 +11,9 @@
class TwoWire;
#endif
using BusType=TwoWire;
using IICSensorList=SensorList<BusType>;
using IICSensorBase=SensorBase<BusType>;
using BUSTYPE=TwoWire;
using IICSensorList=SensorList;
using IICSensorBase=SensorTemplate<BUSTYPE,SensorBase::IIC>;
template <class CFG>

View File

@ -43,8 +43,7 @@ static std::vector<IICGrove> iicGroveList;
void runIicTask(GwApi *api);
static IICSensorList sensors;
static void addGroveItems(std::vector<IICSensorBase::Creator> &creators,GwApi *api, IICSensorList &sensors, const String &bus,const String &grove, int, int)
static void addGroveItems(std::vector<SensorBase::Creator> &creators,GwApi *api, const String &bus,const String &grove, int, int)
{
GwLog *logger=api->getLogger();
for (auto &&init : iicGroveList)
@ -61,17 +60,18 @@ static void addGroveItems(std::vector<IICSensorBase::Creator> &creators,GwApi *a
{
if (! creator) continue;
auto *scfg = creator(api, prfx);
if (scfg == nullptr) continue;
scfg->readConfig(api->getConfig());
if (scfg->ok)
{
LOG_DEBUG(GwLog::LOG, "adding %s from grove config", prfx.c_str());
sensors.add(api, scfg);
api->addSensor(scfg,false);
found=true;
break;
}
else
{
LOG_DEBUG(GwLog::DEBUG, "unmatched grove sensor config %s for %s", prfx.c_str(), scfg->type.c_str());
LOG_DEBUG(GwLog::DEBUG, "unmatched grove sensor config %s", prfx.c_str());
delete scfg;
}
}
@ -89,19 +89,26 @@ void initIicTask(GwApi *api){
#else
bool addTask=false;
GwConfigHandler *config=api->getConfig();
std::vector<IICSensorBase::Creator> creators;
creators.push_back(registerSHT3X(api,sensors));
creators.push_back(registerQMP6988(api,sensors));
creators.push_back(registerBME280(api,sensors));
creators.push_back(registerBMP280(api,sensors));
std::vector<SensorBase::Creator> creators;
creators.push_back(registerSHT3X(api));
creators.push_back(registerQMP6988(api));
creators.push_back(registerBME280(api));
creators.push_back(registerBMP280(api));
#ifdef _GWI_IIC1
addGroveItems(creators,api,sensors,"1",_GWI_IIC1);
addGroveItems(creators,api,"1",_GWI_IIC1);
#endif
#ifdef _GWI_IIC2
addGroveItems(creators,api,sensors,"2",_GWI_IIC2);
addGroveItems(creators,api,"2",_GWI_IIC2);
#endif
for (auto it=sensors.begin();it != sensors.end();it++){
if ((*it)->preinit(api)) addTask=true;
//TODO: ensure that we run after other init tasks...
int res=-1;
ConfiguredSensors sensorList=api->taskInterfaces()->get<ConfiguredSensors>(res);
for (auto &&it: sensorList.sensors){
if (it->busType != SensorBase::IIC) continue;
if (it->preinit(api)) {
addTask=true;
api->addCapability(it->prefix,"true");
}
}
if (addTask){
api->addUserTask(runIicTask,"iicTask",4000);
@ -154,8 +161,11 @@ void runIicTask(GwApi *api){
GwLog *logger=api->getLogger();
std::map<int,TwoWire *> buses;
LOG_DEBUG(GwLog::LOG,"iic task started");
for (auto it=sensors.begin();it != sensors.end();it++){
int busId=(*it)->busId;
int res=-1;
ConfiguredSensors sensorList=api->taskInterfaces()->get<ConfiguredSensors>(res);
for (auto &&it : sensorList.sensors){
if (it->busType != SensorBase::IIC) continue;
int busId=it->busId;
auto bus=buses.find(busId);
if (bus == buses.end()){
switch (busId)
@ -175,7 +185,7 @@ void runIicTask(GwApi *api){
}
break;
default:
LOG_DEBUG(GwLog::ERROR, "invalid bus id %d at config %s", busId, (*it)->prefix.c_str());
LOG_DEBUG(GwLog::ERROR, "invalid bus id %d at config %s", busId, it->prefix.c_str());
break;
}
}
@ -184,8 +194,8 @@ void runIicTask(GwApi *api){
bool runLoop=false;
GwIntervalRunner timers;
int counterId=api->addCounter("iicsensors");
for (auto it=sensors.begin();it != sensors.end();it++){
IICSensorBase *cfg=*it;
for (auto && cfg: sensorList.sensors){
if (cfg->busType != SensorBase::IIC) continue;
auto bus=buses.find(cfg->busId);
if (! cfg->isActive()) continue;
if (bus == buses.end()){

View File

@ -1,6 +1,7 @@
#ifndef _GWIICTASK_H
#define _GWIICTASK_H
#include "GwApi.h"
#include "GwSensor.h"
void initIicTask(GwApi *api);
DECLARE_INITFUNCTION(initIicTask);
DECLARE_INITFUNCTION_ORDER(initIicTask,GWLATEORDER);
#endif

View File

@ -1,11 +1,10 @@
#define _IIC_GROOVE_LIST
#include "GwQMP6988.h"
#ifdef _GWQMP6988
#define TYPE "QMP6988"
#define PRFX1 TYPE "11"
#define PRFX2 TYPE "12"
#define PRFX3 TYPE "21"
#define PRFX4 TYPE "22"
class QMP6988Config;
static GwSensorConfigInitializerList<QMP6988Config> configs;
class QMP6988Config : public IICSensorBase{
public:
String prNam="Pressure";
@ -13,7 +12,7 @@ class QMP6988Config : public IICSensorBase{
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
float prOff=0;
QMP6988 *device=nullptr;
QMP6988Config(GwApi* api,const String &prefix):SensorBase(TYPE,api,prefix){}
QMP6988Config(GwApi* api,const String &prefix):IICSensorBase(api,prefix){}
virtual bool isActive(){return prAct;};
virtual bool initDevice(GwApi *api,TwoWire *wire){
if (!isActive()) return false;
@ -31,7 +30,6 @@ class QMP6988Config : public IICSensorBase{
virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"QMP6988 configured");
api->addCapability(prefix,"true");
addPressureXdr(api,*this);
return isActive();
}
@ -42,76 +40,43 @@ class QMP6988Config : public IICSensorBase{
LOG_DEBUG(GwLog::DEBUG,"%s measure %2.0fPa, computed %2.0fPa",prefix.c_str(), pressure,computed);
sendN2kPressure(api,*this,computed,counterId);
}
#define CFG6988(prefix)\
CFG_GET(prNam,prefix); \
CFG_GET(iid,prefix); \
CFG_GET(prAct,prefix); \
CFG_GET(intv,prefix); \
CFG_GET(prOff,prefix);
virtual void readConfig(GwConfigHandler *cfg){
if (ok) return;
if (prefix == PRFX1){
busId=1;
addr=86;
CFG6988(QMP698811);
ok=true;
}
if (prefix == PRFX2){
busId=1;
addr=112;
CFG6988(QMP698812);
ok=true;
}
if (prefix == PRFX3){
busId=2;
addr=86;
CFG6988(QMP698821);
ok=true;
}
if (prefix == PRFX4){
busId=2;
addr=112;
CFG6988(QMP698822);
ok=true;
}
intv*=1000;
configs.readConfig(this,cfg);
}
};
static IICSensorBase::Creator creator=[](GwApi *api,const String &prfx){
static SensorBase::Creator creator=[](GwApi *api,const String &prfx)-> SensorBase*{
if (! configs.knowsPrefix(prfx)) return nullptr;
return new QMP6988Config(api,prfx);
};
IICSensorBase::Creator registerQMP6988(GwApi *api,IICSensorList &sensors){
SensorBase::Creator registerQMP6988(GwApi *api){
GwLog *logger=api->getLogger();
#if defined(GWQMP6988) || defined(GWQMP698811)
{
QMP6988Config *scfg=new QMP6988Config(api,PRFX1);
sensors.add(api,scfg);
api->addSensor(new QMP6988Config(api,"QMP698811"));
CHECK_IIC1();
#pragma message "GWQMP698811 defined"
}
#endif
#if defined(GWQMP698812)
{
QMP6988Config *scfg=new QMP6988Config(api,PRFX2);
sensors.add(api,scfg);
api->addSensor(new QMP6988Config(api,"QMP698812"));
CHECK_IIC1();
#pragma message "GWQMP698812 defined"
}
#endif
#if defined(GWQMP698821)
{
QMP6988Config *scfg=new QMP6988Config(api,PRFX3);
sensors.add(api,scfg);
api->addSensor(new QMP6988Config(api,"QMP698821"));
CHECK_IIC2();
#pragma message "GWQMP698821 defined"
}
#endif
#if defined(GWQMP698822)
{
QMP6988Config *scfg=new QMP6988Config(api,PRFX4);
sensors.add(api,scfg);
api->addSensor(new QMP6988Config(api,"QMP698822"));
CHECK_IIC2();
#pragma message "GWQMP698822 defined"
}
@ -119,8 +84,28 @@ IICSensorBase::Creator registerQMP6988(GwApi *api,IICSensorList &sensors){
return creator;
}
#define CFG6988(s,prefix,bus,baddr)\
CFG_SGET(s,prNam,prefix); \
CFG_SGET(s,iid,prefix); \
CFG_SGET(s,prAct,prefix); \
CFG_SGET(s,intv,prefix); \
CFG_SGET(s,prOff,prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \
s->intv*=1000;
#define SC6988(prefix,bus,addr) \
GWSENSORDEF(configs,QMP6988Config,CFG6988,prefix,bus,addr)
SC6988(QMP698811,1,86);
SC6988(QMP698812,1,112);
SC6988(QMP698821,2,86);
SC6988(QMP698822,2,112);
#else
IICSensorBase::Creator registerQMP6988(GwApi *api,IICSensorList &sensors){
return IICSensorBase::Creator();
SensorBase::Creator registerQMP6988(GwApi *api){
return SensorBase::Creator();
}
#endif

View File

@ -16,5 +16,5 @@
#ifdef _GWQMP6988
#include "QMP6988.h"
#endif
IICSensorBase::Creator registerQMP6988(GwApi *api,IICSensorList &sensors);
SensorBase::Creator registerQMP6988(GwApi *api);
#endif

View File

@ -1,11 +1,7 @@
#include "GwSHT3X.h"
#ifdef _GWSHT3X
#define TYPE "SHT3X"
#define PRFX1 TYPE "11"
#define PRFX2 TYPE "12"
#define PRFX3 TYPE "21"
#define PRFX4 TYPE "22"
class SHT3XConfig;
static GwSensorConfigInitializerList<SHT3XConfig> configs;
class SHT3XConfig : public IICSensorBase{
public:
String tmNam;
@ -15,8 +11,7 @@ class SHT3XConfig : public IICSensorBase{
tN2kHumiditySource huSrc;
tN2kTempSource tmSrc;
SHT3X *device=nullptr;
SHT3XConfig(GwApi *api,const String &prefix):
SensorBase(TYPE,api,prefix){}
using IICSensorBase::IICSensorBase;
virtual bool isActive(){
return tmAct || huAct;
}
@ -31,7 +26,6 @@ class SHT3XConfig : public IICSensorBase{
virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
api->addCapability(prefix,"true");
addHumidXdr(api,*this);
addTempXdr(api,*this);
return isActive();
@ -62,83 +56,43 @@ class SHT3XConfig : public IICSensorBase{
LOG_DEBUG(GwLog::DEBUG, "unable to query %s: %d",prefix.c_str(), rt);
}
}
/**
* we do not dynamically compute the config names
* just to get compile time errors if something does not fit
* correctly
*/
#define CFG3X(prefix) \
CFG_GET(tmNam,prefix); \
CFG_GET(huNam,prefix); \
CFG_GET(iid,prefix); \
CFG_GET(tmAct,prefix); \
CFG_GET(huAct,prefix); \
CFG_GET(intv,prefix); \
CFG_GET(huSrc,prefix); \
CFG_GET(tmSrc,prefix);
virtual void readConfig(GwConfigHandler *cfg){
if (ok) return;
if (prefix == PRFX1){
busId=1;
addr=0x44;
CFG3X(SHT3X11);
ok=true;
}
if (prefix == PRFX2){
busId=1;
addr=0x45;
CFG3X(SHT3X12);
ok=true;
}
if (prefix == PRFX3){
busId=2;
addr=0x44;
CFG3X(SHT3X21);
ok=true;
}
if (prefix == PRFX4){
busId=2;
addr=0x45;
CFG3X(SHT3X22);
ok=true;
}
intv*=1000;
configs.readConfig(this,cfg);
return;
}
};
IICSensorBase::Creator creator=[](GwApi *api,const String &prfx){
SensorBase::Creator creator=[](GwApi *api,const String &prfx)-> SensorBase*{
if (! configs.knowsPrefix(prfx)) return nullptr;
return new SHT3XConfig(api,prfx);
};
IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors){
SensorBase::Creator registerSHT3X(GwApi *api){
GwLog *logger=api->getLogger();
#if defined(GWSHT3X) || defined (GWSHT3X11)
{
auto *scfg=creator(api,PRFX1);
sensors.add(api,scfg);
api->addSensor(creator(api,"SHT3X11"));
CHECK_IIC1();
#pragma message "GWSHT3X11 defined"
}
#endif
#if defined(GWSHT3X12)
{
auto *scfg=creator(api,PRFX2);
sensors.add(api,scfg);
api->addSensor(creator(api,"SHT3X12"));
CHECK_IIC1();
#pragma message "GWSHT3X12 defined"
}
#endif
#if defined(GWSHT3X21)
{
auto *scfg=creator(api,PRFX3);
sensors.add(api,scfg);
api->addSensor(creator(api,"SHT3X21"));
CHECK_IIC2();
#pragma message "GWSHT3X21 defined"
}
#endif
#if defined(GWSHT3X22)
{
auto *scfg=creator(api,PRFX4);
sensors.add(api,scfg);
api->addSensor(creator(api,"SHT3X22"));
CHECK_IIC2();
#pragma message "GWSHT3X22 defined"
}
@ -146,9 +100,36 @@ IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors){
return creator;
};
/**
* we do not dynamically compute the config names
* just to get compile time errors if something does not fit
* correctly
*/
#define CFGSHT3X(s, prefix, bus, baddr) \
CFG_SGET(s, tmNam, prefix); \
CFG_SGET(s, huNam, prefix); \
CFG_SGET(s, iid, prefix); \
CFG_SGET(s, tmAct, prefix); \
CFG_SGET(s, huAct, prefix); \
CFG_SGET(s, intv, prefix); \
CFG_SGET(s, huSrc, prefix); \
CFG_SGET(s, tmSrc, prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \
s->intv *= 1000;
#define SCSHT3X(prefix, bus, addr) \
GWSENSORDEF(configs, SHT3XConfig, CFGSHT3X, prefix, bus, addr)
SCSHT3X(SHT3X11, 1, 0x44);
SCSHT3X(SHT3X12, 1, 0x45);
SCSHT3X(SHT3X21, 2, 0x44);
SCSHT3X(SHT3X22, 2, 0x45);
#else
IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors){
return IICSensorBase::Creator();
SensorBase::Creator registerSHT3X(GwApi *api){
return SensorBase::Creator();
}
#endif

View File

@ -16,5 +16,5 @@
#ifdef _GWSHT3X
#include "SHT3X.h"
#endif
IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors);
SensorBase::Creator registerSHT3X(GwApi *api);
#endif

View File

@ -11,16 +11,6 @@ build_flags=
-D M5_CAN_KIT
${env.build_flags}
[env:m5stack-atom-env4]
extends = sensors
board = m5stack-atom
lib_deps =
${env.lib_deps}
${sensors.lib_deps}
build_flags=
-D M5_ENV4
-D M5_CAN_KIT
${env.build_flags}
[env:m5stack-atom-bme280]
extends = sensors
@ -55,6 +45,7 @@ lib_deps =
${sensors.lib_deps}
build_flags=
-D GWBMP280G1
-D GWSHT3X11
-D M5_GROOVEIIC
-D M5_CAN_KIT
${env.build_flags}

View File

@ -189,6 +189,7 @@ private:
if (N2kIsNA(v)) return N2kInt8NA;
return v;
}
void convertXDR(const SNMEA0183Msg &msg){
XdrMappingList foundMappings;
for (int offset=0;offset <= (msg.FieldCount()-4);offset+=4){
@ -199,7 +200,19 @@ private:
String unit=msg.Field(offset+2);
String transducerName=msg.Field(offset+3);
GwXDRFoundMapping found=xdrMappings->getMapping(transducerName,type,unit);
if (found.empty) continue;
if (found.empty) {
if (config.unmappedXdr){
const GwXDRType *typeDef=xdrMappings->findType(type,unit);
GwXdrUnknownMapping mapping(transducerName,unit,typeDef,config.xdrTimeout);
value=mapping.valueFromXdr(value);
if (boatData->update(value,msg.sourceId,&mapping)){
//TODO: potentially update the format
LOG_DEBUG(GwLog::DEBUG+1,"found unmapped XDR %s:%s, value %f",
transducerName.c_str(),mapping.getBoatItemFormat().c_str(),value);
}
}
continue;
}
value=found.valueFromXdr(value);
if (!boatData->update(value,msg.sourceId,&found)) continue;
LOG_DEBUG(GwLog::DEBUG+1,"found mapped XDR %s:%s, value %f",
@ -307,7 +320,7 @@ private:
return;
}
tN2kMsg n2kMsg;
if (boatData->XTE->update(rmb.xte,msg.sourceId)){
if (updateDouble(boatData->XTE,rmb.xte,msg.sourceId)){
tN2kXTEMode mode=N2kxtem_Autonomous;
if (msg.FieldCount() > 13){
const char *modeChar=msg.Field(13);
@ -318,10 +331,10 @@ private:
}
uint8_t destinationId=getWaypointId(rmb.destID);
uint8_t sourceId=getWaypointId(rmb.originID);
if (boatData->DTW->update(rmb.dtw,msg.sourceId)
&& boatData->BTW->update(rmb.btw,msg.sourceId)
&& boatData->WPLat->update(rmb.latitude,msg.sourceId)
&& boatData->WPLon->update(rmb.longitude,msg.sourceId)
if (updateDouble(boatData->DTW,rmb.dtw,msg.sourceId)
&& updateDouble(boatData->BTW,rmb.btw,msg.sourceId)
&& updateDouble(boatData->WPLat,rmb.latitude,msg.sourceId)
&& updateDouble(boatData->WPLon,rmb.longitude,msg.sourceId)
){
SetN2kNavigationInfo(n2kMsg,1,rmb.dtw,N2khr_true,
false,

View File

@ -3,14 +3,26 @@
class GwSynchronized{
private:
SemaphoreHandle_t *locker;
SemaphoreHandle_t locker=nullptr;
void lock(){
if (locker != nullptr) xSemaphoreTake(locker, portMAX_DELAY);
}
public:
/**
* deprecated
* as SemaphoreHandle_t is already a pointer just use this directly
*/
GwSynchronized(SemaphoreHandle_t *locker){
if (locker == nullptr) return;
this->locker=*locker;
lock();
}
GwSynchronized(SemaphoreHandle_t locker){
this->locker=locker;
if (locker != nullptr) xSemaphoreTake(*locker, portMAX_DELAY);
lock();
}
~GwSynchronized(){
if (locker != nullptr) xSemaphoreGive(*locker);
if (locker != nullptr) xSemaphoreGive(locker);
}
};

20
lib/sensors/GwSensor.cpp 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
*/
#include "GwSensor.h"
#include "GwApi.h"
void SensorList::add(SensorBase::Ptr sensor){
this->push_back(sensor);
}

View File

@ -14,42 +14,124 @@
*/
#ifndef _GWSENSORS_H
#define _GWSENSORS_H
#include "GwApi.h"
#include "GwLog.h"
template<typename BUS>
#include "GwConfigItem.h"
#include <Arduino.h>
#include <memory>
#include <vector>
class GwApi;
class GwConfigHandler;
class SensorBase{
public:
using BusType=enum{
IIC=0,
SPI=1,
UNKNOWN=-1
};
using Ptr=std::shared_ptr<SensorBase>;
BusType busType=BusType::UNKNOWN;
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){
SensorBase(BusType bt,GwApi *api,const String &prfx)
:busType(bt),prefix(prfx){
}
using Creator=std::function<SensorBase<BUS> *(GwApi *api,const String &prfx)>;
using Creator=std::function<SensorBase *(GwApi *api,const String &prfx)>;
virtual bool isActive(){return false;};
virtual bool initDevice(GwApi *api,BUS *wire){return false;};
virtual bool initDevice(GwApi *api,void *bus){return false;};
virtual bool preinit(GwApi * api){return false;}
virtual void measure(GwApi * api,BUS *wire, int counterId){};
virtual void measure(GwApi * api,void *bus, int counterId){};
virtual ~SensorBase(){}
virtual void readConfig(GwConfigHandler *cfg)=0;
};
template<typename BUS,SensorBase::BusType bt>
class SensorTemplate : public SensorBase{
public:
SensorTemplate(GwApi *api,const String &prfx)
:SensorBase(bt,api,prfx){}
virtual bool initDevice(GwApi *api,BUS *bus){return false;};
virtual bool initDevice(GwApi *api,void *bus){
if (busType != bt) return false;
return initDevice(api,(BUS*)bus);
}
virtual void measure(GwApi * api,void *bus, int counterId){
if (busType != bt) return;
measure(api,(BUS*)bus,counterId);
};
protected:
virtual void measure(GwApi *api,BUS *bus, int counterId)=0;
};
template<typename BUS>
class SensorList : public std::vector<SensorBase<BUS>*>{
class SensorList : public std::vector<SensorBase::Ptr>{
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;
void add(SensorBase::Ptr sensor);
using std::vector<SensorBase::Ptr>::vector;
};
/**
* helper classes for a simplified sensor configuration
* by creating a list of type GwSensorConfigInitializerList<SensorClass> we can populate
* it by static instances of GwSensorConfigInitializer (using GWSENSORCONFIG ) that each has
* a function that populates the sensor config from the config data.
* For using sensors this is not really necessary - but it can simplify the code for a sensor
* if you want to support a couple of instances on different buses.
* By using this approach you still can write functions using the CFG_SGET macros that will check
* your config definitions at compile time.
*
*/
template <typename T>
class GwSensorConfig{
public:
using ReadConfig=std::function<void(T*,GwConfigHandler*)>;
ReadConfig configReader;
String prefix;
GwSensorConfig(const String &prfx,ReadConfig f):prefix(prfx),configReader(f){
}
bool readConfig(T* s,GwConfigHandler *cfg){
if (s == nullptr) return false;
configReader(s,cfg);
return s->ok;
}
};
template<typename T>
class GwSensorConfigInitializer : public GwInitializer<GwSensorConfig<T>>{
public:
using GwInitializer<GwSensorConfig<T>>::GwInitializer;
};
template<typename T>
class GwSensorConfigInitializerList : public GwInitializer<GwSensorConfig<T>>::List{
public:
using GwInitializer<GwSensorConfig<T>>::List::List;
bool readConfig(T *s,GwConfigHandler *cfg){
for (auto &&scfg:*this){
if (scfg.readConfig(s,cfg)) return true;
}
return false;
}
bool knowsPrefix(const String &prefix){
for (auto &&scfg:*this){
if (scfg.prefix == prefix) return true;
}
return false;
}
};
#define CFG_SGET(s, name, prefix) \
cfg->getValue(s->name, GwConfigDefinitions::prefix##name)
#define GWSENSORCONFIG(list,type,prefix,initFunction) \
GwSensorConfigInitializer<type> __init ## type ## prefix(list,GwSensorConfig<type>(#prefix,initFunction));
#define GWSENSORDEF(list,type,init,prefix,bus,baddr) \
GWSENSORCONFIG(list, type, prefix, [](type *s, GwConfigHandler *cfg) { init(s, prefix, bus, baddr); });
#define CFG_GET(name,prefix) \
cfg->getValue(name, GwConfigDefinitions::prefix ## name)
#endif

View File

@ -63,6 +63,7 @@ GwSerial::~GwSerial()
{
delete buffer;
if (readBuffer) delete readBuffer;
if (lock != nullptr) vSemaphoreDelete(lock);
}
String GwSerial::getMode(){
@ -87,10 +88,14 @@ size_t GwSerial::enqueue(const uint8_t *data, size_t len, bool partial)
}
GwBuffer::WriteStatus GwSerial::write(){
if (! isInitialized()) return GwBuffer::ERROR;
size_t numWrite=availableForWrite();
size_t rt=buffer->fetchData(numWrite,[](uint8_t *buffer,size_t len, void *p){
return ((GwSerial *)p)->stream->write(buffer,len);
},this);
size_t rt=0;
{
GWSYNCHRONIZED(lock);
size_t numWrite=availableForWrite();
rt=buffer->fetchData(numWrite,[](uint8_t *buffer,size_t len, void *p){
return ((GwSerial *)p)->stream->write(buffer,len);
},this);
}
if (rt != 0){
LOG_DEBUG(GwLog::DEBUG+1,"Serial %d write %d",id,rt);
}

View File

@ -4,6 +4,7 @@
#include "GwLog.h"
#include "GwBuffer.h"
#include "GwChannelInterface.h"
#include "GwSynchronized.h"
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
#include "hal/usb_serial_jtag_ll.h"
#endif
@ -26,8 +27,12 @@ class GwSerial : public GwChannelInterface{
virtual long getFlushTimeout(){return 2000;}
virtual int availableForWrite()=0;
int type=0;
SemaphoreHandle_t lock=nullptr;
public:
GwSerial(GwLog *logger,Stream *stream,int id,int type,bool allowRead=true);
void enableWriteLock(){
lock=xSemaphoreCreateMutex();
}
virtual ~GwSerial();
bool isInitialized();
virtual size_t sendToClients(const char *buf,int sourceId,bool partial=false);
@ -94,6 +99,7 @@ template<typename T>
if (c->isConnected()){
//this retriggers the ISR
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
usb_serial_jtag_ll_txfifo_flush();
}
}
return rt;

View File

@ -246,7 +246,7 @@ void GwTcpClient::resolveHost(String host)
{
LOG_DEBUG(GwLog::LOG,"start resolving %s",host.c_str());
{
GWSYNCHRONIZED(&locker);
GWSYNCHRONIZED(locker);
resolvedAddress.resolved = false;
}
state = C_RESOLVING;
@ -283,12 +283,12 @@ void GwTcpClient::resolveHost(String host)
void GwTcpClient::setResolved(IPAddress addr, bool valid){
LOG_DEBUG(GwLog::LOG,"setResolved %s, valid=%s",
addr.toString().c_str(),(valid?"true":"false"));
GWSYNCHRONIZED(&locker);
GWSYNCHRONIZED(locker);
resolvedAddress.address=addr;
resolvedAddress.resolved=valid;
state=C_RESOLVED;
}
GwTcpClient::ResolvedAddress GwTcpClient::getResolved(){
GWSYNCHRONIZED(&locker);
GWSYNCHRONIZED(locker);
return resolvedAddress;
}

View File

@ -21,76 +21,23 @@
#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;
static GwSensorConfigInitializerList<GWDMS22B> configs;
class GWDMS22B : public SSISensor{
public:
int zero=2047;
bool invt=false;
String zeroConfigName;
public:
GWDMS22B(GwApi *api,const String &prfx, int host):SSISensor("DMS22B",api,prfx,host){}
GWDMS22B(GwApi *api,const String &prfx):SSISensor(api,prfx){}
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){
virtual void measure(GwApi * api,BUSTYPE *bus, int counterId){
GwLog *logger=api->getLogger();
uint32_t value=0;
esp_err_t res=readData(value);
@ -106,34 +53,84 @@ class GWDMS22B : public SSISensor{
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;
if (ok) return;
configs.readConfig(this,cfg);
}
};
void registerDMS22B(GwApi *api,SpiSensorList &sensors){
ADD22B11
ADD22B12
ADD22B21
ADD22B22
#define ADD22B(PRFX,BUS) \
{\
CHECK_BUS(BUS); \
GWDMS22B *dms=new GWDMS22B(api,#PRFX);\
api->addSensor(dms,true); \
}
void registerDMS22B(GwApi *api){
#ifdef GWDMS22B11
ADD22B(DMS22B11,SPI1)
#ifndef GWDMS22B11_CS
#define GWDMS22B11_CS -1
#endif
#else
#define GWDMS22B11_CS -1
#endif
#ifdef GWDMS22B12
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
#endif
#ifdef GWDMS22B21
ADD22B(DMS22B21,SPI2)
#ifndef GWDMS22B21_CS
#define GWDMS22B21_CS -1
#endif
#else
#define GWDMS22B21_CS -1
#endif
#ifdef GWDMS22B22
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
#endif
}
#define CFGDMS22B(s,PRFX,bus,csv) \
CFG_SGET(s,act,PRFX); \
CFG_SGET(s,iid,PRFX); \
CFG_SGET(s,fintv,PRFX); \
CFG_SGET(s,zero,PRFX); \
s->zeroConfigName=GwConfigDefinitions::PRFX ## zero;\
CFG_SGET(s,invt,PRFX); \
s->bits=12; \
s->clock=500000; \
s->cs=csv; \
s->busId=bus; \
s->intv=1000*s->fintv; \
s->ok=true;
#define SCDMS22B(prefix,bus) \
GWSENSORDEF(configs,GWDMS22B,CFGDMS22B,prefix,GW ## bus ## _HOST,GW ## prefix ## _CS)
SCDMS22B(DMS22B11,SPI1);
SCDMS22B(DMS22B12,SPI1);
SCDMS22B(DMS22B21,SPI2);
SCDMS22B(DMS22B22,SPI2);

View File

@ -18,5 +18,5 @@ SSI sensor DMS22B - https://www.mouser.de/datasheet/2/54/bour_s_a0011704065_1-22
#ifndef _GWDMS22B_H
#define _GWDMS22B_H
#include "GwSpiSensor.h"
void registerDMS22B(GwApi *api,SpiSensorList &sensors);
void registerDMS22B(GwApi *api);
#endif

View File

@ -16,7 +16,7 @@
#ifndef _GWSPISENSOR_H
#define _GWSPISENSOR_H
#include <driver/spi_master.h>
#include "GwSensor.h"
#include "GwApi.h"
#include <memory>
class SPIBus{
@ -48,7 +48,7 @@ class SPIBus{
spi_host_device_t host() const { return hd;}
};
using BusType=SPIBus;
using BUSTYPE=SPIBus;
class SSIDevice{
spi_device_handle_t spi;
@ -90,15 +90,16 @@ class SSIDevice{
};
class SSISensor : public SensorBase<BusType>{
class SSISensor : public SensorTemplate<BUSTYPE,SensorBase::SPI>{
std::unique_ptr<SSIDevice> device;
protected:
public:
int bits=12;
int mask=0xffff;
int cs=-1;
int clock=0;
bool act=false;
float fintv=0;
protected:
virtual bool initSSI(GwLog*logger,const SPIBus *bus,
int clock,int cs, int bits){
mask= (1 << bits)-1;
@ -125,17 +126,16 @@ class SSISensor : public SensorBase<BusType>{
}
public:
SSISensor(const String &type,GwApi *api,const String &prfx, int host):SensorBase(type,api,prfx)
SSISensor(GwApi *api,const String &prfx):SensorTemplate<BUSTYPE,SensorBase::SPI>(api,prfx)
{
busId=host;
}
virtual bool isActive(){return act;};
virtual bool initDevice(GwApi *api,BusType *bus){
virtual bool initDevice(GwApi *api,BUSTYPE *bus){
return initSSI(api->getLogger(),bus, clock,cs,bits);
};
};
using SpiSensorList=SensorList<BusType>;
using SpiSensorList=SensorList;
#define GWSPI1_HOST SPI2_HOST
#define GWSPI2_HOST SPI3_HOST
#endif

View File

@ -21,8 +21,6 @@
static SPIBus bus1(GWSPI1_HOST);
static SPIBus bus2(GWSPI2_HOST);
static SpiSensorList sensors;
#ifdef GWSPI1_CLK
static const int spi1clk=GWSPI1_CLK;
#else
@ -57,8 +55,11 @@ static const int spi2mosi=-1;
void runSpiTask(GwApi *api){
GwLog *logger=api->getLogger();
int res=-1;
ConfiguredSensors sensorList=api->taskInterfaces()->get<ConfiguredSensors>(res);
std::map<int,SPIBus *> buses;
for (auto && sensor:sensors){
for (auto && sensor: sensorList.sensors){
if (sensor->busType != SensorBase::BusType::SPI) continue;
int busId=sensor->busId;
auto bus=buses.find(busId);
if (bus == buses.end()){
@ -93,7 +94,7 @@ void runSpiTask(GwApi *api){
bool runLoop=false;
GwIntervalRunner timers;
int counterId=api->addCounter("spisensors");
for (auto && sensor:sensors){
for (auto && sensor: sensorList.sensors){
if (!sensor->isActive()) continue;
auto bus=buses.find(sensor->busId);
if (bus == buses.end()){
@ -122,10 +123,16 @@ void runSpiTask(GwApi *api){
void initSpiTask(GwApi *api){
GwLog *logger=api->getLogger();
registerDMS22B(api,sensors);
int res=-1;
registerDMS22B(api);
ConfiguredSensors sensorList=api->taskInterfaces()->get<ConfiguredSensors>(res);
bool addTask=false;
for (auto && sensor:sensors){
if (sensor->preinit(api)) addTask=true;
for (auto && sensor:sensorList.sensors){
if (sensor->busType != SensorBase::BusType::SPI) continue;
if (sensor->preinit(api)) {
api->addCapability(sensor->prefix,"true");
addTask=true;
}
}
if (addTask){
api->addUserTask(runSpiTask,"spiTask",3000);

View File

@ -16,5 +16,5 @@
#define _GWSPITASK_H
#include "GwApi.h"
void initSpiTask(GwApi *api);
DECLARE_INITFUNCTION(initSpiTask);
DECLARE_INITFUNCTION_ORDER(initSpiTask,GWLATEORDER);
#endif

View File

@ -1,11 +1,12 @@
#define DECLARE_USERTASK(task) GwUserTaskDef __##task##__(task,#task);
#define DECLARE_USERTASK_PARAM(task,...) GwUserTaskDef __##task##__(task,#task,__VA_ARGS__);
#define DECLARE_INITFUNCTION(task) GwInitTask __Init##task##__(task,#task);
#define DECLARE_INITFUNCTION_ORDER(task,order) GwInitTask __Init##task##__(task,#task,order);
#define DECLARE_CAPABILITY(name,value) GwUserCapability __CAP##name##__(#name,#value);
#define DECLARE_STRING_CAPABILITY(name,value) GwUserCapability __CAP##name##__(#name,value);
#define DECLARE_TASKIF(type) \
DECLARE_TASKIF_IMPL(type) \
GwIreg __register##type(__FILE__,#type)
static int __taskInterface##type=0; //avoid duplicate declarations
#include "GwUserCode.h"
#include "GwSynchronized.h"
@ -28,45 +29,6 @@ bool taskExists(V &list, const String &name){
}
return false;
}
class RegEntry{
public:
String file;
String task;
RegEntry(const String &t, const String &f):file(f),task(t){}
RegEntry(){}
};
using RegMap=std::map<String,RegEntry>;
static RegMap &registrations(){
static RegMap *regMap=new RegMap();
return *regMap;
}
static void registerInterface(const String &task,const String &file, const String &name){
auto it=registrations().find(name);
if (it != registrations().end()){
if (it->second.file != file){
ESP_LOGE("Assert","type %s redefined in %s original in %s",name,file,it->second.file);
std::abort();
};
if (it->second.task != task){
ESP_LOGE("Assert","type %s registered for multiple tasks %s and %s",name,task,it->second.task);
std::abort();
};
}
else{
registrations()[name]=RegEntry(task,file);
}
}
class GwIreg{
public:
GwIreg(const String &file, const String &name){
registerInterface("",file,name);
}
};
class GwUserTaskDef{
public:
GwUserTaskDef(TaskFunction_t task,String name, int stackSize=2000){
@ -82,8 +44,8 @@ class GwInitTask{
GwInitTask(TaskFunction_t task, String name){
initTasks.push_back(GwUserTask(name,task));
}
GwInitTask(GwUserTaskFunction task, String name){
initTasks.push_back(GwUserTask(name,task));
GwInitTask(GwUserTaskFunction task, String name,int order=0){
initTasks.push_back(GwUserTask(name,task,GwUserTask::DEFAULT_STACKSIZE,order));
}
};
class GwUserCapability{
@ -112,21 +74,8 @@ class TaskInterfacesStorage{
logger(l){
lock=xSemaphoreCreateMutex();
}
bool set(const String &file, const String &name, const String &task,GwApi::TaskInterfaces::Ptr v){
GWSYNCHRONIZED(&lock);
auto it=registrations().find(name);
if (it == registrations().end()){
LOG_DEBUG(GwLog::ERROR,"TaskInterfaces: invalid set %s not known",name.c_str());
return false;
}
if (it->second.file != file){
LOG_DEBUG(GwLog::ERROR,"TaskInterfaces: invalid set %s wrong file, expected %s , got %s",name.c_str(),it->second.file.c_str(),file.c_str());
return false;
}
if (it->second.task != task){
LOG_DEBUG(GwLog::ERROR,"TaskInterfaces: invalid set %s wrong task, expected %s , got %s",name.c_str(),it->second.task.c_str(),task.c_str());
return false;
}
bool set(const String &name, GwApi::TaskInterfaces::Ptr v){
GWSYNCHRONIZED(lock);
auto vit=values.find(name);
if (vit != values.end()){
vit->second.updates++;
@ -141,7 +90,7 @@ class TaskInterfacesStorage{
return true;
}
GwApi::TaskInterfaces::Ptr get(const String &name, int &result){
GWSYNCHRONIZED(&lock);
GWSYNCHRONIZED(lock);
auto it = values.find(name);
if (it == values.end())
{
@ -150,36 +99,59 @@ class TaskInterfacesStorage{
}
result = it->second.updates;
return it->second.ptr;
}
bool update(const String &name, std::function<GwApi::TaskInterfaces::Ptr(GwApi::TaskInterfaces::Ptr)>f){
GWSYNCHRONIZED(lock);
auto vit=values.find(name);
bool rt=false;
int mode=0;
if (vit == values.end()){
mode=1;
auto np=f(GwApi::TaskInterfaces::Ptr());
if (np){
mode=11;
values[name]=TaskDataEntry(np);
rt=true;
}
}
else
{
auto np = f(vit->second.ptr);
mode=2;
if (np)
{
mode=22;
vit->second = np;
vit->second.updates++;
if (vit->second.updates < 0)
{
vit->second.updates = 0;
}
rt=true;
}
}
LOG_DEBUG(GwLog::DEBUG,"TaskApi::update %s (mode %d)returns %d",name.c_str(),mode,(int)rt);
return rt;
}
};
class TaskInterfacesImpl : public GwApi::TaskInterfaces{
String task;
TaskInterfacesStorage *storage;
GwLog *logger;
bool isInit=false;
public:
TaskInterfacesImpl(const String &n,TaskInterfacesStorage *s, GwLog *l,bool i):
task(n),storage(s),isInit(i),logger(l){}
virtual bool iset(const String &file, const String &name, Ptr v){
return storage->set(file,name,task,v);
TaskInterfacesImpl(TaskInterfacesStorage *s, GwLog *l,bool i):
storage(s),isInit(i),logger(l){}
protected:
virtual bool iset(const String &name, Ptr v){
return storage->set(name,v);
}
virtual Ptr iget(const String &name, int &result){
return storage->get(name,result);
}
virtual bool iclaim(const String &name, const String &task){
if (! isInit) return false;
auto it=registrations().find(name);
if (it == registrations().end()){
LOG_DEBUG(GwLog::ERROR,"unable to claim interface %s for task %s, not registered",name.c_str(),task.c_str());
return false;
}
if (!it->second.task.isEmpty()){
LOG_DEBUG(GwLog::ERROR,"unable to claim interface %s for task %s, already claimed by %s",name.c_str(),task.c_str(),it->second.task.c_str());
return false;
}
it->second.task=task;
LOG_DEBUG(GwLog::LOG,"claimed interface %s for task %s",name.c_str(),task.c_str());
return true;
virtual bool iupdate(const String &name,std::function<Ptr(Ptr v)> f){
return storage->update(name,f);
}
};
@ -188,7 +160,7 @@ class TaskApi : public GwApiInternal
{
GwApiInternal *api=nullptr;
int sourceId;
SemaphoreHandle_t *mainLock;
SemaphoreHandle_t mainLock;
SemaphoreHandle_t localLock;
std::map<int,GwCounter<String>> counter;
std::map<String,GwApi::HandlerFunction> webHandlers;
@ -200,7 +172,7 @@ class TaskApi : public GwApiInternal
public:
TaskApi(GwApiInternal *api,
int sourceId,
SemaphoreHandle_t *mainLock,
SemaphoreHandle_t mainLock,
const String &name,
TaskInterfacesStorage *s,
bool init=false)
@ -210,7 +182,7 @@ public:
this->mainLock=mainLock;
this->name=name;
localLock=xSemaphoreCreateMutex();
interfaces=new TaskInterfacesImpl(name,s,api->getLogger(),init);
interfaces=new TaskInterfacesImpl(s,api->getLogger(),init);
isInit=init;
}
virtual GwRequestQueue *getQueue()
@ -264,14 +236,14 @@ public:
vSemaphoreDelete(localLock);
};
virtual void fillStatus(GwJsonDocument &status){
GWSYNCHRONIZED(&localLock);
GWSYNCHRONIZED(localLock);
if (! counterUsed) return;
for (auto it=counter.begin();it != counter.end();it++){
it->second.toJson(status);
}
};
virtual int getJsonSize(){
GWSYNCHRONIZED(&localLock);
GWSYNCHRONIZED(localLock);
if (! counterUsed) return 0;
int rt=0;
for (auto it=counter.begin();it != counter.end();it++){
@ -280,7 +252,7 @@ public:
return rt;
};
virtual void increment(int idx,const String &name,bool failed=false){
GWSYNCHRONIZED(&localLock);
GWSYNCHRONIZED(localLock);
counterUsed=true;
auto it=counter.find(idx);
if (it == counter.end()) return;
@ -288,18 +260,18 @@ public:
else (it->second.add(name));
};
virtual void reset(int idx){
GWSYNCHRONIZED(&localLock);
GWSYNCHRONIZED(localLock);
counterUsed=true;
auto it=counter.find(idx);
if (it == counter.end()) return;
it->second.reset();
};
virtual void remove(int idx){
GWSYNCHRONIZED(&localLock);
GWSYNCHRONIZED(localLock);
counter.erase(idx);
}
virtual int addCounter(const String &name){
GWSYNCHRONIZED(&localLock);
GWSYNCHRONIZED(localLock);
counterUsed=true;
counterIdx++;
//avoid the need for an empty counter constructor
@ -317,7 +289,7 @@ public:
return api->addXdrMapping(def);
}
virtual void registerRequestHandler(const String &url,HandlerFunction handler){
GWSYNCHRONIZED(&localLock);
GWSYNCHRONIZED(localLock);
webHandlers[url]=handler;
}
virtual void addCapability(const String &name, const String &value){
@ -344,7 +316,7 @@ public:
{
GwApi::HandlerFunction handler;
{
GWSYNCHRONIZED(&localLock);
GWSYNCHRONIZED(localLock);
auto it = webHandlers.find(url);
if (it == webHandlers.end())
{
@ -357,12 +329,25 @@ public:
handler(req);
return true;
}
virtual void addSensor(SensorBase *sb,bool readConfig=true) override{
if (sb == nullptr) return;
SensorBase::Ptr sensor(sb);
if (readConfig) sb->readConfig(this->getConfig());
if (! sensor->ok){
api->getLogger()->logDebug(GwLog::ERROR,"sensor %s nok , bustype=%d",sensor->prefix.c_str(),(int)sensor->busType);
return;
}
bool rt=taskInterfaces()->update<ConfiguredSensors>( [sensor,this](ConfiguredSensors *sensors)->bool{
api->getLogger()->logDebug(GwLog::LOG,"adding sensor %s, type=%d",sensor->prefix,(int)sensor->busType);
sensors->sensors.add(sensor);
return true;
});
}
};
GwUserCode::GwUserCode(GwApiInternal *api,SemaphoreHandle_t *mainLock){
GwUserCode::GwUserCode(GwApiInternal *api){
this->logger=api->getLogger();
this->api=api;
this->mainLock=mainLock;
this->taskData=new TaskInterfacesStorage(this->logger);
}
GwUserCode::~GwUserCode(){
@ -392,6 +377,9 @@ void GwUserCode::startUserTasks(int baseId){
}
}
void GwUserCode::startInitTasks(int baseId){
std::sort(initTasks.begin(),initTasks.end(),[](const GwUserTask &a, const GwUserTask &b){
return a.order < b.order;
});
LOG_DEBUG(GwLog::DEBUG,"starting %d user init tasks",initTasks.size());
for (auto it=initTasks.begin();it != initTasks.end();it++){
LOG_DEBUG(GwLog::LOG,"starting user init task %s with id %d",it->name.c_str(),baseId);

View File

@ -15,22 +15,25 @@ class GwApiInternal : public GwApi{
};
class GwUserTask{
public:
static const int DEFAULT_STACKSIZE=2000;
String name;
TaskFunction_t task=NULL;
GwUserTaskFunction usertask=NULL;
bool isUserTask=false;
GwApiInternal *api=NULL;
int stackSize=2000;
GwUserTask(String name,TaskFunction_t task,int stackSize=2000){
int order=0;
GwUserTask(String name,TaskFunction_t task,int stackSize=DEFAULT_STACKSIZE){
this->name=name;
this->task=task;
this->stackSize=stackSize;
}
GwUserTask(String name, GwUserTaskFunction task,int stackSize=2000){
GwUserTask(String name, GwUserTaskFunction task,int stackSize=DEFAULT_STACKSIZE, int order=0){
this->name=name;
this->usertask=task;
this->isUserTask=true;
this->stackSize=stackSize;
this->order=order;
}
};
@ -38,13 +41,14 @@ class TaskInterfacesStorage;
class GwUserCode{
GwLog *logger;
GwApiInternal *api;
SemaphoreHandle_t *mainLock;
SemaphoreHandle_t mainLock=nullptr;
TaskInterfacesStorage *taskData;
void startAddOnTask(GwApiInternal *api,GwUserTask *task,int sourceId,String name);
public:
~GwUserCode();
typedef std::map<String,String> Capabilities;
GwUserCode(GwApiInternal *api, SemaphoreHandle_t *mainLock);
GwUserCode(GwApiInternal *api);
void begin(SemaphoreHandle_t mainLock){this->mainLock=mainLock;}
void startUserTasks(int baseId);
void startInitTasks(int baseId);
void startAddonTask(String name,TaskFunction_t task, int id);

View File

@ -58,6 +58,7 @@ GwXDRType *types[] = {
new GwXDRType(GwXDRType::DISPLACEMENTD, "A", "D",DegToRad,RadToDeg,"rd"),
new GwXDRType(GwXDRType::RPM,"T","R")
};
static GwXDRType genericType(GwXDRType::GENERIC, "G", "");
template<typename T, int size>
int GetArrLength(T(&)[size]){return size;}
static GwXDRType *findType(GwXDRType::TypeCode type, int *start = NULL)
@ -82,6 +83,19 @@ static GwXDRType *findType(GwXDRType::TypeCode type, int *start = NULL)
return NULL;
}
static GwXDRType *findType(const String &typeString, const String &unitString)
{
int len=GetArrLength(types);
for (int i=0; i< len; i++)
{
if (types[i]->xdrtype == typeString && types[i]->xdrunit == unitString)
{
return types[i];
}
}
return &genericType;
}
#include "GwXdrTypeMappings.h"
static GwXDRType::TypeCode findTypeMapping(GwXDRCategory category, int field)
@ -199,7 +213,7 @@ GwXDRMappingDef *GwXDRMappingDef::fromString(String s)
}
return rt;
}
String GwXDRMappingDef::getTransducerName(int instance)
String GwXDRMappingDef::getTransducerName(int instance) const
{
String name = xdrName;
if (instanceMode == GwXDRMappingDef::IS_AUTO)
@ -257,7 +271,7 @@ bool GwXDRMappings::addMapping(GwXDRMappingDef *def)
LOG_DEBUG(GwLog::ERROR, "no type mapping for %s", def->toString().c_str());
return false;
}
GwXDRType *type = findType(code, &typeIndex);
GwXDRType *type = ::findType(code, &typeIndex);
if (!type)
{
LOG_DEBUG(GwLog::ERROR, "no type definition for %s", def->toString().c_str());
@ -298,7 +312,7 @@ bool GwXDRMappings::addMapping(GwXDRMappingDef *def)
LOG_DEBUG(GwLog::LOG, "append mapping with n183key %s", n183key.c_str());
it->second.push_back(mapping);
}
type = findType(code, &typeIndex);
type = ::findType(code, &typeIndex);
if (!type)
break;
mapping = new GwXDRMapping(def, type);
@ -471,7 +485,7 @@ String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){
{
return rt;
}
GwXDRType *type = findType(code, &typeIndex);
GwXDRType *type = ::findType(code, &typeIndex);
bool first=true;
unsigned long invalidTime=config->getInt(GwConfigDefinitions::timoSensor);
while (type){
@ -480,8 +494,12 @@ String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){
if (first) first=false;
else rt+=",";
rt+=found.buildXdrEntry(value).entry;
type = findType(code, &typeIndex);
type = ::findType(code, &typeIndex);
}
delete def;
return rt;
}
const GwXDRType * GwXDRMappings::findType(const String &typeString, const String &unitString) const{
return ::findType(typeString,unitString);
}

View File

@ -140,7 +140,7 @@ class GwXDRMappingDef{
rt += xdrUnit;
return rt;
}
String getTransducerName(int instance);
String getTransducerName(int instance) const;
private:
static bool handleToken(String tok,int index,GwXDRMappingDef *def);
};
@ -163,12 +163,12 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
String entry;
String transducer;
};
GwXDRMappingDef *definition=NULL;
GwXDRType *type=NULL;
const GwXDRMappingDef *definition=NULL;
const GwXDRType *type=NULL;
int instanceId=-1;
bool empty=true;
unsigned long timeout=0;
GwXDRFoundMapping(GwXDRMappingDef *definition,GwXDRType *type, unsigned long timeout){
GwXDRFoundMapping(const GwXDRMappingDef *definition,const GwXDRType *type, unsigned long timeout){
this->definition=definition;
this->type=type;
this->timeout=timeout;
@ -182,7 +182,7 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
empty=false;
}
GwXDRFoundMapping(){}
String getTransducerName(){
virtual String getTransducerName(){
return definition->getTransducerName(instanceId);
}
double valueFromXdr(double value){
@ -203,6 +203,24 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
}
};
class GwXdrUnknownMapping : public GwXDRFoundMapping{
String name;
String unit;
public:
GwXdrUnknownMapping(const String &xdrName, const String &xdrUnit,const GwXDRType *type,unsigned long timeout):
name(xdrName),unit(xdrUnit), GwXDRFoundMapping(nullptr,type,timeout){
}
virtual String getTransducerName(){
return name;
}
virtual String getBoatItemFormat(){
String rt=GwXDRFoundMapping::getBoatItemFormat();
if (type->xdrunit.isEmpty()) rt+=unit;
return rt;
}
};
//the class GwXDRMappings is not intended to be deleted
//the deletion will leave memory leaks!
class GwConfigHandler;
@ -229,6 +247,7 @@ class GwXDRMappings{
GwXDRFoundMapping getMapping(GwXDRCategory category,int selector,int field=0,int instance=-1);
String getXdrEntry(String mapping, double value,int instance=0);
const char * getUnMapped();
const GwXDRType * findType(const String &typeString, const String &unitString) const;
};

View File

@ -235,17 +235,17 @@ void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg, int sourceId,bool conv
class CalibrationValues {
using Map=std::map<String,double>;
Map values;
SemaphoreHandle_t lock;
SemaphoreHandle_t lock=nullptr;
public:
CalibrationValues(){
lock=xSemaphoreCreateMutex();
}
void set(const String &name,double value){
GWSYNCHRONIZED(&lock);
GWSYNCHRONIZED(lock);
values[name]=value;
}
bool get(const String &name, double &value){
GWSYNCHRONIZED(&lock);
GWSYNCHRONIZED(lock);
auto it=values.find(name);
if (it==values.end()) return false;
value=it->second;
@ -373,7 +373,7 @@ bool delayedRestart(){
},"reset",2000,&logger,0,NULL) == pdPASS;
}
ApiImpl *apiImpl=new ApiImpl(MIN_USER_TASK);
GwUserCode userCodeHandler(apiImpl,&mainLock);
GwUserCode userCodeHandler(apiImpl);
#define JSON_OK "{\"status\":\"OK\"}"
#define JSON_INVALID_PASS F("{\"status\":\"invalid password\"}")
@ -788,6 +788,7 @@ void setup() {
logger.setWriter(new DefaultLogWriter());
#endif
boatData.begin();
userCodeHandler.begin(mainLock);
userCodeHandler.startInitTasks(MIN_USER_TASK);
channels.preinit();
config.stopChanges();
@ -849,7 +850,7 @@ void setup() {
buffer[29]=0;
request->send(200,"text/plain",buffer);
});
webserver.registerHandler((USERPREFIX+"*").c_str(),[&USERPREFIX](AsyncWebServerRequest *req){
webserver.registerHandler((USERPREFIX+"*").c_str(),[](AsyncWebServerRequest *req){
String turl=req->url().substring(USERPREFIX.length());
logger.logDebug(GwLog::DEBUG,"user web request for %s",turl.c_str());
userCodeHandler.handleWebRequest(turl,req);
@ -937,7 +938,7 @@ void setup() {
logger.logDebug(GwLog::LOG,"starting addon tasks");
logger.flush();
{
GWSYNCHRONIZED(&mainLock);
GWSYNCHRONIZED(mainLock);
userCodeHandler.startUserTasks(MIN_USER_TASK);
}
timers.addAction(HEAP_REPORT_TIME,[](){
@ -967,7 +968,7 @@ void handleSendAndRead(bool handleRead){
void loopRun() {
//logger.logDebug(GwLog::DEBUG,"main loop start");
monitor.reset();
GWSYNCHRONIZED(&mainLock);
GWSYNCHRONIZED(mainLock);
logger.flush();
monitor.setTime(1);
gwWifi.loop();

View File

@ -210,6 +210,14 @@
"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":"unknownXdr",
"label":"show unknown XDR",
"type":"boolean",
"default":"false",
"description":"show received XDR transducer values in data display if there is no XDR mapping for them",
"category":"converter"
},
{
"name":"sendRMCi",
"label":"send RMC interval",

View File

@ -1578,7 +1578,7 @@
if (isNaN(x)) return '-----';
return formatLonLatsDecimal(x, 'lat');
},
u: '°'
u: ''
},
formatLongitude: {
f: function (v) {
@ -1798,7 +1798,7 @@
let id = el.getAttribute('id');
if (id) {
if (!names[id.replace(/^frame_/, '')]) {
el.parentElement.remove();
el.remove();
}
}
});

View File

@ -40,6 +40,8 @@ types:
type: frame
key: m5groovei2c#grv#
label: "M5 I2C Groove Units"
target: resource
resource: i2cbus
children:
- label: "M5 ENV3"
type: checkbox
@ -51,16 +53,16 @@ types:
- value: M5_ENV3#grv#
key: true
resource: qmp69881#grv#1,sht3x#grv#1
- label: "M5 ENV4"
type: checkbox
key: m5env4#grv#
target: define
url: "https://docs.m5stack.com/en/unit/ENV%E2%85%A3%20Unit"
description: "M5 sensor module temperature, humidity, pressure"
values:
- value: M5_ENV4#grv#
key: true
resource: bmp280#grv#1,sht3x#grv#1
# - label: "M5 ENV4"
# type: checkbox
# key: m5env4#grv#
# target: define
# url: "https://docs.m5stack.com/en/unit/ENV%E2%85%A3%20Unit"
# description: "M5 sensor module temperature, humidity, pressure"
# values:
# - value: M5_ENV4#grv#
# key: true
# resource: bmp280#grv#1,sht3x#grv#1
- type: checkbox
label: SHT3X-1
description: "SHT30 temperature and humidity sensor 0x44"
@ -682,7 +684,7 @@ resources:
default: &esp32default
serial: 2
can: 1
i2c: 1
i2cbus: 2
gpio: 1
config:

View File

@ -686,7 +686,7 @@ class PipelineInfo{
let allowed=allowedResources[ak];
if (allowed === undefined) allowed=1;
if (resList.length > allowed){
errors+=" more than "+allowed+" "+k+" device(s) used";
errors+=" more than "+allowed+" device(s) of type "+k+" used";
}
}