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 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) [20241114](../../releases/tag/20241114)
********** **********
* UDP writer and reader - [#79](../../issues/79) * UDP writer and reader - [#79](../../issues/79)

View File

@ -1,6 +1,6 @@
Sensors 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. This includes some I2C Sensors and SSI rotary encoders.
To connect sensors the following steps are necessary: 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). 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 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] * [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] * [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] * [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 "GwSynchronized.h"
#include <map> #include <map>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "GwSensor.h"
#include <functional>
#include <memory>
class GwApi; class GwApi;
typedef void (*GwUserTaskFunction)(GwApi *); typedef void (*GwUserTaskFunction)(GwApi *);
//API to be used for additional tasks //API to be used for additional tasks
@ -43,11 +46,7 @@ class GwApi{
* the core part will not handle this data at all but * the core part will not handle this data at all but
* this interface ensures that there is a correct locking of the * this interface ensures that there is a correct locking of the
* data to correctly handle multi threading * data to correctly handle multi threading
* The user code should not use this intterface directly * there is no protection - i.e. every task can get and set the data
* but instead it should use the static functions
* apiGetXXX(GwApi *,...)
* apiSetXXX(GwApi *,...)
* that will be created by the macro DECLARE_TASK_INTERFACE
*/ */
class TaskInterfaces class TaskInterfaces
{ {
@ -61,9 +60,9 @@ class GwApi{
}; };
using Ptr = std::shared_ptr<Base>; using Ptr = std::shared_ptr<Base>;
protected: 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 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: public:
template <typename T> template <typename T>
bool set(const T &v){ bool set(const T &v){
@ -76,6 +75,10 @@ class GwApi{
} }
template <typename T> template <typename T>
bool claim(const String &task){ bool claim(const String &task){
return true;
}
template <typename T>
bool update(std::function<bool(T *)>){
return false; return false;
} }
}; };
@ -206,7 +209,13 @@ class GwApi{
* @param name: the config name this value is used for * @param name: the config name this value is used for
* @param value: the current value * @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 * not thread safe methods
@ -231,7 +240,10 @@ static void checkDef(T... args){};
#define DECLARE_USERTASK_PARAM(task,...) #define DECLARE_USERTASK_PARAM(task,...)
#endif #endif
#ifndef DECLARE_INITFUNCTION #ifndef DECLARE_INITFUNCTION
#define DECLARE_INITFUNCTION(task) #define DECLARE_INITFUNCTION(task,...)
#endif
#ifndef DECLARE_INITFUNCTION_ORDER
#define DECLARE_INITFUNCTION_ORDER(task,...)
#endif #endif
#ifndef DECLARE_CAPABILITY #ifndef DECLARE_CAPABILITY
#define DECLARE_CAPABILITY(name,value) #define DECLARE_CAPABILITY(name,value)
@ -255,27 +267,20 @@ static void checkDef(T... args){};
* int ival2=99; * int ival2=99;
* String sval="unset"; * String sval="unset";
* }; * };
* DECLARE_TASKIF(testTask,TestTaskApi); * DECLARE_TASKIF(TestTaskApi);
* The macro will generate 2 static funtions:
* *
* bool apiSetTestTaskApi(GwApi *api, const TestTaskApi &v);
* TestTaskApi apiGetTestTaskApi(GwApi *api, int &result);
*
* The setter will return true on success.
* It is intended to be used by the task that did declare the api.
* The getter will set the result to -1 if no data is available, otherwise
* it will return the update count (so you can check if there was a change
* compared to the last call).
* It is intended to be used by any task. * It is intended to be used by any task.
* Be aware that all the apis share a common namespace - so be sure to somehow * Be aware that all the apis share a common namespace - so be sure to somehow
* make your API names unique. * 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) \ #define DECLARE_TASKIF_IMPL(type) \
template<> \ template<> \
inline bool GwApi::TaskInterfaces::set(const type & v) {\ 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<> \ template<> \
inline type GwApi::TaskInterfaces::get<type>(int &result) {\ inline type GwApi::TaskInterfaces::get<type>(int &result) {\
@ -286,13 +291,42 @@ static void checkDef(T... args){};
}\ }\
type *tp=(type*)ptr.get(); \ type *tp=(type*)ptr.get(); \
return type(*tp); \ return type(*tp); \
}\ } \
template<> \ template<> \
inline bool GwApi::TaskInterfaces::claim<type>(const String &task) {\ inline bool GwApi::TaskInterfaces::update(std::function<bool(type *)> f) { \
return iclaim(#type,task);\ 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 #ifndef DECLARE_TASKIF
#define DECLARE_TASKIF(type) DECLARE_TASKIF_IMPL(type) #define DECLARE_TASKIF(type) DECLARE_TASKIF_IMPL(type)
#endif #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 #endif

View File

@ -427,6 +427,7 @@ void GwChannelList::begin(bool fallbackSerial){
if (! fallbackSerial){ if (! fallbackSerial){
GwSerial *usbSerial=createSerialImpl(config, logger,USB_CHANNEL_ID,GWUSB_RX,GWUSB_TX,true); GwSerial *usbSerial=createSerialImpl(config, logger,USB_CHANNEL_ID,GWUSB_RX,GWUSB_TX,true);
if (usbSerial != nullptr){ 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); GwChannel *usbChannel=createChannel(logger,config,USB_CHANNEL_ID,usbSerial,GWSERIAL_TYPE_BI);
if (usbChannel != nullptr){ if (usbChannel != nullptr){
addChannel(usbChannel); addChannel(usbChannel);

View File

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

View File

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

View File

@ -108,7 +108,7 @@ void GwWifi::loop(){
} }
else{ else{
if (! clientIsConnected){ 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; clientIsConnected=true;
} }
} }

View File

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

View File

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

View File

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

View File

@ -15,11 +15,16 @@
#include <Adafruit_BMP280.h> #include <Adafruit_BMP280.h>
#endif #endif
#ifdef _GWBMP280 #ifdef _GWBMP280
#define TYPE "BMP280"
#define PRFX1 TYPE "11"
#define PRFX2 TYPE "12" /**
#define PRFX3 TYPE "21" * we need a forward declaration here as the config list has to go before the
#define PRFX4 TYPE "22" * class implementation
*/
class BMP280Config;
static GwSensorConfigInitializerList<BMP280Config> configs;
class BMP280Config : public IICSensorBase{ class BMP280Config : public IICSensorBase{
public: public:
bool prAct=true; bool prAct=true;
@ -33,14 +38,13 @@ class BMP280Config : public IICSensorBase{
float prOff=0; float prOff=0;
Adafruit_BMP280 *device=nullptr; Adafruit_BMP280 *device=nullptr;
uint32_t sensorId=-1; 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 isActive(){return prAct||tmAct;}
virtual bool initDevice(GwApi *api,TwoWire *wire){ virtual bool initDevice(GwApi *api,TwoWire *wire){
GwLog *logger=api->getLogger(); GwLog *logger=api->getLogger();
device= new Adafruit_BMP280(wire); device= new Adafruit_BMP280(wire);
if (! device->begin(addr)){ 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; delete device;
device=nullptr; device=nullptr;
return false; return false;
@ -52,7 +56,6 @@ class BMP280Config : public IICSensorBase{
virtual bool preinit(GwApi * api){ virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger(); GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str()); LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
api->addCapability(prefix,"true");
addPressureXdr(api,*this); addPressureXdr(api,*this);
addTempXdr(api,*this); addTempXdr(api,*this);
return isActive(); return isActive();
@ -85,96 +88,90 @@ class BMP280Config : public IICSensorBase{
sendN2kEnvironmentalParameters(api, *this, temperature, humidity, computed,counterId); 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 virtual void readConfig(GwConfigHandler *cfg) override
{ {
if (prefix == PRFX1) if (ok) return;
{ configs.readConfig(this,cfg);
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;
} }
}; };
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); return new BMP280Config(api,prfx);
}); });
IICSensorBase::Creator registerBMP280(GwApi *api,IICSensorList &sensors){ SensorBase::Creator registerBMP280(GwApi *api){
#if defined(GWBMP280) || defined(GWBMP28011) #if defined(GWBMP280) || defined(GWBMP28011)
{ {
auto *cfg=creator(api,PRFX1); api->addSensor(creator(api,"BMP28011"));
//BMP280Config *cfg=new BMP280Config(api,PRFX1);
sensors.add(api,cfg);
CHECK_IIC1(); CHECK_IIC1();
#pragma message "GWBMP28011 defined" #pragma message "GWBMP28011 defined"
} }
#endif #endif
#if defined(GWBMP28012) #if defined(GWBMP28012)
{ {
auto *cfg=creator(api,PRFX2); api->addSensor(creator(api,"BMP28012"));
//BMP280Config *cfg=new BMP280Config(api,PRFX2);
sensors.add(api,cfg);
CHECK_IIC1(); CHECK_IIC1();
#pragma message "GWBMP28012 defined" #pragma message "GWBMP28012 defined"
} }
#endif #endif
#if defined(GWBMP28021) #if defined(GWBMP28021)
{ {
auto *cfg=creator(api,PRFX3); api->addSensor(creator(api,"BMP28021"));
//BMP280Config *cfg=new BMP280Config(api,PRFX3);
sensors.add(api,cfg);
CHECK_IIC2(); CHECK_IIC2();
#pragma message "GWBMP28021 defined" #pragma message "GWBMP28021 defined"
} }
#endif #endif
#if defined(GWBMP28022) #if defined(GWBMP28022)
{ {
auto *cfg=creator(api,PRFX4); api->addSensor(creator(api,"BMP28022"));
//BMP280Config *cfg=new BMP280Config(api,PRFX4);
sensors.add(api,cfg);
CHECK_IIC1(); CHECK_IIC1();
#pragma message "GWBMP28022 defined" #pragma message "GWBMP28022 defined"
} }
#endif #endif
return creator; 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 #else
IICSensorBase::Creator registerBMP280(GwApi *api,IICSensorList &sensors){ SensorBase::Creator registerBMP280(GwApi *api){
return IICSensorBase::Creator(); return SensorBase::Creator();
} }
#endif #endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,7 @@
#include "GwSHT3X.h" #include "GwSHT3X.h"
#ifdef _GWSHT3X #ifdef _GWSHT3X
#define TYPE "SHT3X" class SHT3XConfig;
#define PRFX1 TYPE "11" static GwSensorConfigInitializerList<SHT3XConfig> configs;
#define PRFX2 TYPE "12"
#define PRFX3 TYPE "21"
#define PRFX4 TYPE "22"
class SHT3XConfig : public IICSensorBase{ class SHT3XConfig : public IICSensorBase{
public: public:
String tmNam; String tmNam;
@ -15,8 +11,7 @@ class SHT3XConfig : public IICSensorBase{
tN2kHumiditySource huSrc; tN2kHumiditySource huSrc;
tN2kTempSource tmSrc; tN2kTempSource tmSrc;
SHT3X *device=nullptr; SHT3X *device=nullptr;
SHT3XConfig(GwApi *api,const String &prefix): using IICSensorBase::IICSensorBase;
SensorBase(TYPE,api,prefix){}
virtual bool isActive(){ virtual bool isActive(){
return tmAct || huAct; return tmAct || huAct;
} }
@ -31,7 +26,6 @@ class SHT3XConfig : public IICSensorBase{
virtual bool preinit(GwApi * api){ virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger(); GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str()); LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
api->addCapability(prefix,"true");
addHumidXdr(api,*this); addHumidXdr(api,*this);
addTempXdr(api,*this); addTempXdr(api,*this);
return isActive(); return isActive();
@ -62,83 +56,43 @@ class SHT3XConfig : public IICSensorBase{
LOG_DEBUG(GwLog::DEBUG, "unable to query %s: %d",prefix.c_str(), rt); 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){ virtual void readConfig(GwConfigHandler *cfg){
if (ok) return; if (ok) return;
if (prefix == PRFX1){ configs.readConfig(this,cfg);
busId=1; return;
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;
} }
}; };
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); return new SHT3XConfig(api,prfx);
}; };
IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors){ SensorBase::Creator registerSHT3X(GwApi *api){
GwLog *logger=api->getLogger(); GwLog *logger=api->getLogger();
#if defined(GWSHT3X) || defined (GWSHT3X11) #if defined(GWSHT3X) || defined (GWSHT3X11)
{ {
auto *scfg=creator(api,PRFX1); api->addSensor(creator(api,"SHT3X11"));
sensors.add(api,scfg);
CHECK_IIC1(); CHECK_IIC1();
#pragma message "GWSHT3X11 defined" #pragma message "GWSHT3X11 defined"
} }
#endif #endif
#if defined(GWSHT3X12) #if defined(GWSHT3X12)
{ {
auto *scfg=creator(api,PRFX2); api->addSensor(creator(api,"SHT3X12"));
sensors.add(api,scfg);
CHECK_IIC1(); CHECK_IIC1();
#pragma message "GWSHT3X12 defined" #pragma message "GWSHT3X12 defined"
} }
#endif #endif
#if defined(GWSHT3X21) #if defined(GWSHT3X21)
{ {
auto *scfg=creator(api,PRFX3); api->addSensor(creator(api,"SHT3X21"));
sensors.add(api,scfg);
CHECK_IIC2(); CHECK_IIC2();
#pragma message "GWSHT3X21 defined" #pragma message "GWSHT3X21 defined"
} }
#endif #endif
#if defined(GWSHT3X22) #if defined(GWSHT3X22)
{ {
auto *scfg=creator(api,PRFX4); api->addSensor(creator(api,"SHT3X22"));
sensors.add(api,scfg);
CHECK_IIC2(); CHECK_IIC2();
#pragma message "GWSHT3X22 defined" #pragma message "GWSHT3X22 defined"
} }
@ -146,9 +100,36 @@ IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors){
return creator; 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 #else
IICSensorBase::Creator registerSHT3X(GwApi *api,IICSensorList &sensors){ SensorBase::Creator registerSHT3X(GwApi *api){
return IICSensorBase::Creator(); return SensorBase::Creator();
} }
#endif #endif

View File

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

View File

@ -11,16 +11,6 @@ build_flags=
-D M5_CAN_KIT -D M5_CAN_KIT
${env.build_flags} ${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] [env:m5stack-atom-bme280]
extends = sensors extends = sensors
@ -55,6 +45,7 @@ lib_deps =
${sensors.lib_deps} ${sensors.lib_deps}
build_flags= build_flags=
-D GWBMP280G1 -D GWBMP280G1
-D GWSHT3X11
-D M5_GROOVEIIC -D M5_GROOVEIIC
-D M5_CAN_KIT -D M5_CAN_KIT
${env.build_flags} ${env.build_flags}

View File

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

View File

@ -3,14 +3,26 @@
class GwSynchronized{ class GwSynchronized{
private: private:
SemaphoreHandle_t *locker; SemaphoreHandle_t locker=nullptr;
void lock(){
if (locker != nullptr) xSemaphoreTake(locker, portMAX_DELAY);
}
public: public:
/**
* deprecated
* as SemaphoreHandle_t is already a pointer just use this directly
*/
GwSynchronized(SemaphoreHandle_t *locker){ GwSynchronized(SemaphoreHandle_t *locker){
if (locker == nullptr) return;
this->locker=*locker;
lock();
}
GwSynchronized(SemaphoreHandle_t locker){
this->locker=locker; this->locker=locker;
if (locker != nullptr) xSemaphoreTake(*locker, portMAX_DELAY); lock();
} }
~GwSynchronized(){ ~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 #ifndef _GWSENSORS_H
#define _GWSENSORS_H #define _GWSENSORS_H
#include "GwApi.h" #include "GwConfigItem.h"
#include "GwLog.h" #include <Arduino.h>
template<typename BUS> #include <memory>
#include <vector>
class GwApi;
class GwConfigHandler;
class SensorBase{ class SensorBase{
public: public:
using BusType=enum{
IIC=0,
SPI=1,
UNKNOWN=-1
};
using Ptr=std::shared_ptr<SensorBase>;
BusType busType=BusType::UNKNOWN;
int busId=0; int busId=0;
int iid=99; //N2K instanceId int iid=99; //N2K instanceId
int addr=-1; int addr=-1;
const String prefix; const String prefix;
const String type;
long intv=0; long intv=0;
bool ok=false; bool ok=false;
virtual void readConfig(GwConfigHandler *cfg)=0; SensorBase(BusType bt,GwApi *api,const String &prfx)
SensorBase(const String &tn,GwApi *api,const String &prfx):type(tn),prefix(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 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 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 ~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: public:
void add(GwApi *api, SensorBase<BUS> *sensor){ void add(SensorBase::Ptr sensor);
sensor->readConfig(api->getConfig()); using std::vector<SensorBase::Ptr>::vector;
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;
}; };
/**
* 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) \ #define CFG_GET(name,prefix) \
cfg->getValue(name, GwConfigDefinitions::prefix ## name) cfg->getValue(name, GwConfigDefinitions::prefix ## name)
#endif #endif

View File

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

View File

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

View File

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

View File

@ -21,76 +21,23 @@
#define CHECK_BUS(BUS) \ #define CHECK_BUS(BUS) \
checkDef("missing config for " #BUS,GW ## BUS ## _CLK ,GW ## BUS ## _MISO); 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{ class GWDMS22B : public SSISensor{
public:
int zero=2047; int zero=2047;
bool invt=false; bool invt=false;
String zeroConfigName; String zeroConfigName;
public: GWDMS22B(GwApi *api,const String &prfx):SSISensor(api,prfx){}
GWDMS22B(GwApi *api,const String &prfx, int host):SSISensor("DMS22B",api,prfx,host){}
virtual bool preinit(GwApi * api){ virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger(); GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"DMS22B configured, prefix=%s, intv=%f, active=%d",prefix.c_str(),fintv,(int)act); LOG_DEBUG(GwLog::LOG,"DMS22B configured, prefix=%s, intv=%f, active=%d",prefix.c_str(),fintv,(int)act);
api->addCapability(prefix,"true");
return act; return act;
} }
virtual void measure(GwApi * api,BusType *bus, int counterId){ virtual void measure(GwApi * api,BUSTYPE *bus, int counterId){
GwLog *logger=api->getLogger(); GwLog *logger=api->getLogger();
uint32_t value=0; uint32_t value=0;
esp_err_t res=readData(value); esp_err_t res=readData(value);
@ -106,34 +53,84 @@ class GWDMS22B : public SSISensor{
api->increment(counterId,prefix); api->increment(counterId,prefix);
api->setCalibrationValue(zeroConfigName,(double)value); 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){ virtual void readConfig(GwConfigHandler *cfg){
DMS22B(DMS22B11); if (ok) return;
DMS22B(DMS22B12); configs.readConfig(this,cfg);
DMS22B(DMS22B21);
DMS22B(DMS22B22);
intv=1000*fintv;
} }
}; };
void registerDMS22B(GwApi *api,SpiSensorList &sensors){ #define ADD22B(PRFX,BUS) \
ADD22B11 {\
ADD22B12 CHECK_BUS(BUS); \
ADD22B21 GWDMS22B *dms=new GWDMS22B(api,#PRFX);\
ADD22B22 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 #ifndef _GWDMS22B_H
#define _GWDMS22B_H #define _GWDMS22B_H
#include "GwSpiSensor.h" #include "GwSpiSensor.h"
void registerDMS22B(GwApi *api,SpiSensorList &sensors); void registerDMS22B(GwApi *api);
#endif #endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -58,6 +58,7 @@ GwXDRType *types[] = {
new GwXDRType(GwXDRType::DISPLACEMENTD, "A", "D",DegToRad,RadToDeg,"rd"), new GwXDRType(GwXDRType::DISPLACEMENTD, "A", "D",DegToRad,RadToDeg,"rd"),
new GwXDRType(GwXDRType::RPM,"T","R") new GwXDRType(GwXDRType::RPM,"T","R")
}; };
static GwXDRType genericType(GwXDRType::GENERIC, "G", "");
template<typename T, int size> template<typename T, int size>
int GetArrLength(T(&)[size]){return size;} int GetArrLength(T(&)[size]){return size;}
static GwXDRType *findType(GwXDRType::TypeCode type, int *start = NULL) static GwXDRType *findType(GwXDRType::TypeCode type, int *start = NULL)
@ -82,6 +83,19 @@ static GwXDRType *findType(GwXDRType::TypeCode type, int *start = NULL)
return 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" #include "GwXdrTypeMappings.h"
static GwXDRType::TypeCode findTypeMapping(GwXDRCategory category, int field) static GwXDRType::TypeCode findTypeMapping(GwXDRCategory category, int field)
@ -199,7 +213,7 @@ GwXDRMappingDef *GwXDRMappingDef::fromString(String s)
} }
return rt; return rt;
} }
String GwXDRMappingDef::getTransducerName(int instance) String GwXDRMappingDef::getTransducerName(int instance) const
{ {
String name = xdrName; String name = xdrName;
if (instanceMode == GwXDRMappingDef::IS_AUTO) 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()); LOG_DEBUG(GwLog::ERROR, "no type mapping for %s", def->toString().c_str());
return false; return false;
} }
GwXDRType *type = findType(code, &typeIndex); GwXDRType *type = ::findType(code, &typeIndex);
if (!type) if (!type)
{ {
LOG_DEBUG(GwLog::ERROR, "no type definition for %s", def->toString().c_str()); 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()); LOG_DEBUG(GwLog::LOG, "append mapping with n183key %s", n183key.c_str());
it->second.push_back(mapping); it->second.push_back(mapping);
} }
type = findType(code, &typeIndex); type = ::findType(code, &typeIndex);
if (!type) if (!type)
break; break;
mapping = new GwXDRMapping(def, type); mapping = new GwXDRMapping(def, type);
@ -471,7 +485,7 @@ String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){
{ {
return rt; return rt;
} }
GwXDRType *type = findType(code, &typeIndex); GwXDRType *type = ::findType(code, &typeIndex);
bool first=true; bool first=true;
unsigned long invalidTime=config->getInt(GwConfigDefinitions::timoSensor); unsigned long invalidTime=config->getInt(GwConfigDefinitions::timoSensor);
while (type){ while (type){
@ -480,8 +494,12 @@ String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){
if (first) first=false; if (first) first=false;
else rt+=","; else rt+=",";
rt+=found.buildXdrEntry(value).entry; rt+=found.buildXdrEntry(value).entry;
type = findType(code, &typeIndex); type = ::findType(code, &typeIndex);
} }
delete def; delete def;
return rt; 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; rt += xdrUnit;
return rt; return rt;
} }
String getTransducerName(int instance); String getTransducerName(int instance) const;
private: private:
static bool handleToken(String tok,int index,GwXDRMappingDef *def); static bool handleToken(String tok,int index,GwXDRMappingDef *def);
}; };
@ -163,12 +163,12 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
String entry; String entry;
String transducer; String transducer;
}; };
GwXDRMappingDef *definition=NULL; const GwXDRMappingDef *definition=NULL;
GwXDRType *type=NULL; const GwXDRType *type=NULL;
int instanceId=-1; int instanceId=-1;
bool empty=true; bool empty=true;
unsigned long timeout=0; 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->definition=definition;
this->type=type; this->type=type;
this->timeout=timeout; this->timeout=timeout;
@ -182,7 +182,7 @@ class GwXDRFoundMapping : public GwBoatItemNameProvider{
empty=false; empty=false;
} }
GwXDRFoundMapping(){} GwXDRFoundMapping(){}
String getTransducerName(){ virtual String getTransducerName(){
return definition->getTransducerName(instanceId); return definition->getTransducerName(instanceId);
} }
double valueFromXdr(double value){ 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 class GwXDRMappings is not intended to be deleted
//the deletion will leave memory leaks! //the deletion will leave memory leaks!
class GwConfigHandler; class GwConfigHandler;
@ -229,6 +247,7 @@ class GwXDRMappings{
GwXDRFoundMapping getMapping(GwXDRCategory category,int selector,int field=0,int instance=-1); GwXDRFoundMapping getMapping(GwXDRCategory category,int selector,int field=0,int instance=-1);
String getXdrEntry(String mapping, double value,int instance=0); String getXdrEntry(String mapping, double value,int instance=0);
const char * getUnMapped(); 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 { class CalibrationValues {
using Map=std::map<String,double>; using Map=std::map<String,double>;
Map values; Map values;
SemaphoreHandle_t lock; SemaphoreHandle_t lock=nullptr;
public: public:
CalibrationValues(){ CalibrationValues(){
lock=xSemaphoreCreateMutex(); lock=xSemaphoreCreateMutex();
} }
void set(const String &name,double value){ void set(const String &name,double value){
GWSYNCHRONIZED(&lock); GWSYNCHRONIZED(lock);
values[name]=value; values[name]=value;
} }
bool get(const String &name, double &value){ bool get(const String &name, double &value){
GWSYNCHRONIZED(&lock); GWSYNCHRONIZED(lock);
auto it=values.find(name); auto it=values.find(name);
if (it==values.end()) return false; if (it==values.end()) return false;
value=it->second; value=it->second;
@ -373,7 +373,7 @@ bool delayedRestart(){
},"reset",2000,&logger,0,NULL) == pdPASS; },"reset",2000,&logger,0,NULL) == pdPASS;
} }
ApiImpl *apiImpl=new ApiImpl(MIN_USER_TASK); ApiImpl *apiImpl=new ApiImpl(MIN_USER_TASK);
GwUserCode userCodeHandler(apiImpl,&mainLock); GwUserCode userCodeHandler(apiImpl);
#define JSON_OK "{\"status\":\"OK\"}" #define JSON_OK "{\"status\":\"OK\"}"
#define JSON_INVALID_PASS F("{\"status\":\"invalid password\"}") #define JSON_INVALID_PASS F("{\"status\":\"invalid password\"}")
@ -788,6 +788,7 @@ void setup() {
logger.setWriter(new DefaultLogWriter()); logger.setWriter(new DefaultLogWriter());
#endif #endif
boatData.begin(); boatData.begin();
userCodeHandler.begin(mainLock);
userCodeHandler.startInitTasks(MIN_USER_TASK); userCodeHandler.startInitTasks(MIN_USER_TASK);
channels.preinit(); channels.preinit();
config.stopChanges(); config.stopChanges();
@ -849,7 +850,7 @@ void setup() {
buffer[29]=0; buffer[29]=0;
request->send(200,"text/plain",buffer); 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()); String turl=req->url().substring(USERPREFIX.length());
logger.logDebug(GwLog::DEBUG,"user web request for %s",turl.c_str()); logger.logDebug(GwLog::DEBUG,"user web request for %s",turl.c_str());
userCodeHandler.handleWebRequest(turl,req); userCodeHandler.handleWebRequest(turl,req);
@ -937,7 +938,7 @@ void setup() {
logger.logDebug(GwLog::LOG,"starting addon tasks"); logger.logDebug(GwLog::LOG,"starting addon tasks");
logger.flush(); logger.flush();
{ {
GWSYNCHRONIZED(&mainLock); GWSYNCHRONIZED(mainLock);
userCodeHandler.startUserTasks(MIN_USER_TASK); userCodeHandler.startUserTasks(MIN_USER_TASK);
} }
timers.addAction(HEAP_REPORT_TIME,[](){ timers.addAction(HEAP_REPORT_TIME,[](){
@ -967,7 +968,7 @@ void handleSendAndRead(bool handleRead){
void loopRun() { void loopRun() {
//logger.logDebug(GwLog::DEBUG,"main loop start"); //logger.logDebug(GwLog::DEBUG,"main loop start");
monitor.reset(); monitor.reset();
GWSYNCHRONIZED(&mainLock); GWSYNCHRONIZED(mainLock);
logger.flush(); logger.flush();
monitor.setTime(1); monitor.setTime(1);
gwWifi.loop(); 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.", "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" "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", "name":"sendRMCi",
"label":"send RMC interval", "label":"send RMC interval",

View File

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

View File

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

View File

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