diff --git a/extra_script.py b/extra_script.py index 6650b29..95ccbb2 100644 --- a/extra_script.py +++ b/extra_script.py @@ -17,6 +17,7 @@ CFG_INCLUDE='GwConfigDefinitions.h' CFG_INCLUDE_IMPL='GwConfigDefImpl.h' XDR_INCLUDE='GwXdrTypeMappings.h' TASK_INCLUDE='GwUserTasks.h' +TASK_INCLUDE_IF='GwUserTasksIf.h' EMBEDDED_INCLUDE="GwEmbeddedFiles.h" def getEmbeddedFiles(env): @@ -219,23 +220,28 @@ def getUserTaskDirs(): for task in taskdirs: rt.append(task) return rt -def genereateUserTasks(outfile): + +def checkAndAdd(file,names,ilist): + if not file.endswith('.h'): + return + match=False + for cmp in names: + #print("##check %s<->%s"%(f.lower(),cmp)) + if file.lower() == cmp: + match=True + if not match: + return + ilist.append(file) +def genereateUserTasks(outfile,forIf=False): includes=[] for task in userTaskDirs: #print("##taskdir=%s"%task) base=os.path.basename(task) includeNames=[base.lower()+".h",'gw'+base.lower()+'.h'] + if forIf: + includeNames=["i"+base.lower()+".h",'gwi'+base.lower()+'.h'] for f in os.listdir(task): - if not f.endswith('.h'): - continue - match=False - for cmp in includeNames: - #print("##check %s<->%s"%(f.lower(),cmp)) - if f.lower() == cmp: - match=True - if not match: - continue - includes.append(f) + checkAndAdd(f,includeNames,includes) includeData="" for i in includes: print("#task include %s"%i) @@ -291,6 +297,7 @@ def prebuild(env): print("#WARNING: infile %s for %s not found"%(inFile,ef)) generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE)) genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE)) + genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE_IF),forIf=True) generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings) version="dev"+datetime.now().strftime("%Y%m%d") env.Append(CPPDEFINES=[('GWDEVVERSION',version)]) diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h index 7fdf8cd..6fbad61 100644 --- a/lib/api/GwApi.h +++ b/lib/api/GwApi.h @@ -5,6 +5,7 @@ #include "NMEA0183Msg.h" #include "GWConfig.h" #include "GwBoatData.h" +#include //API to be used for additional tasks class GwApi{ public: @@ -33,29 +34,31 @@ class GwApi{ } }; /** - * a simple value container - * to exchange data between tasks - */ - class Value{ - long lvalue=0; - String svalue; - bool isString=false; - bool isValid=false; + * an interface for the data exchange between tasks + * the core part will not handle this data at all but + * this interface ensures that there is a correct locking of the + * data to correctly handle multi threading + * The user code should not use this intterface directly + * but instead it should use the static functions + * apiGetXXX(GwApi *,...) + * apiSetXXX(GwApi *,...) + * that will be created by the macro DECLARE_TASK_INTERFACE + */ + class TaskInterfaces + { + public: + class Base + { public: - Value(const String &v){isString=true;svalue=v;isValid=true;} - Value(long l){lvalue=l;isValid=true;} - Value(){} - long getLValue() const{ - if(!isString) return lvalue; - return atol(svalue.c_str()); - } - String getSValue() const{ - if(isString) return svalue; - return String(lvalue); - } - bool valid() const{return isValid;} + virtual ~Base() + { + } + }; + using Ptr = std::shared_ptr; + public: + virtual bool iset(const String &file, const String &name, Ptr v) = 0; + virtual Ptr iget(const String &name, int &result) = 0; }; - class Status{ public: typedef enum{ @@ -156,14 +159,7 @@ class GwApi{ virtual void increment(int idx,const String &name,bool failed=false){} virtual void reset(int idx){} virtual void remove(int idx){} - - /** - * exchange data between different user tasks - * each task can set arbitrary items - * that can be accessed by other tasks - */ - virtual void setTaskValue(const String &name,const Value &v){} - virtual Value getTaskValue(const String &taskName,const String &name){return Value();} + virtual TaskInterfaces * taskInterfaces()=0; /** * not thread safe methods @@ -188,4 +184,58 @@ class GwApi{ #ifndef DECLARE_STRING_CAPABILITY #define DECLARE_STRING_CAPABILITY(name,value) #endif +/** + * macro to declare an interface that a task provides to other tasks + * this macro must be used in an File I.h or GwI.h + * the first parameter must be the name of the task that should be able + * to write this data (the same name as being used in DECLARE_TASK). + * The second parameter must be a type (class) that inherits from + * GwApi::TaskInterfaces::Base. + * This class must provide copy constructors and empty constructors. + * The attributes should only be simple values or structs but no pointers. + * example: + * class TestTaskApi: public GwApi::TaskInterfaces::Base{ + * public: + * int ival1=0; + * int ival2=99; + * String sval="unset"; + * }; + * DECLARE_TASKIF(testTask,TestTaskApi); + * The macro will generate 2 static funtions: + * + * bool apiSetTestTaskApi(GwApi *api, const TestTaskApi &v); + * TestTaskApi apiGetTestTaskApi(GwApi *api, int &result); + * + * The setter will return true on success. + * It is intended to be used by the task that did declare the api. + * The getter will set the result to -1 if no data is available, otherwise + * it will return the update count (so you can check if there was a change + * compared to the last call). + * It is intended to be used by any task. + * Be aware that all the apis share a common namespace - so be sure to somehow + * make your API names unique. + * + * +*/ +#define DECLARE_TASKIF_IMPL(task,type) \ + static bool apiSet##type(GwApi *a,const type &v){ \ + if (! a || ! a->taskInterfaces()) return false; \ + return a->taskInterfaces()->iset(__FILE__,#type,GwApi::TaskInterfaces::Ptr(new type(v)));\ + }\ + static const type apiGet##type(GwApi *a, int &result) {\ + if (! a || ! a->taskInterfaces()) {\ + result=-1; \ + return type(); \ + }\ + auto ptr=a->taskInterfaces()->iget(#type,result); \ + if (!ptr) {\ + result=-1; \ + return type(); \ + }\ + type *tp=(type*)ptr.get(); \ + return type(*tp); \ + } +#ifndef DECLARE_TASKIF + #define DECLARE_TASKIF(task,type) DECLARE_TASKIF_IMPL(task,type) +#endif #endif diff --git a/lib/exampletask/GwIExampleTask.h b/lib/exampletask/GwIExampleTask.h new file mode 100644 index 0000000..b09a2f4 --- /dev/null +++ b/lib/exampletask/GwIExampleTask.h @@ -0,0 +1,14 @@ +#ifndef GWIEXAMPLETASK_H +#define GWIEXAMPLETASK_H +#include "GwApi.h" +/** + * an interface for the example task +*/ +class ExampleIf : public GwApi::TaskInterfaces::Base{ + public: + long count=0; + String someValue; +}; +DECLARE_TASKIF(exampleTask,ExampleIf); + +#endif \ No newline at end of file diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp index f5e9f97..4f12b03 100644 --- a/lib/usercode/GwUserCode.cpp +++ b/lib/usercode/GwUserCode.cpp @@ -17,7 +17,46 @@ std::vector userTasks; std::vector initTasks; GwUserCode::Capabilities userCapabilities; +class RegEntry{ + public: + String file; + String task; + RegEntry(const String &t, const String &f):file(f),task(t){} + RegEntry(){} +}; +using RegMap=std::map; +static RegMap ®istrations(){ + static RegMap *regMap=new RegMap(); + return *regMap; +} +static void registerInterface(const String &task,const String &file, const String &name){ + auto it=registrations().find(name); + if (it != registrations().end()){ + if (it->second.file != file){ + ESP_LOGE("Assert","type %s redefined in %s original in %s",name,file,it->second.file); + std::abort(); + }; + if (it->second.task != task){ + ESP_LOGE("Assert","type %s registered for multiple tasks %s and %s",name,task,it->second.task); + std::abort(); + }; + } + else{ + registrations()[name]=RegEntry(task,file); + } +} + +class GwIreg{ + public: + GwIreg(const String &task,const String &file, const String &name){ + registerInterface(task,file,name); + } + }; + +#define DECLARE_TASKIF(task,type) \ + DECLARE_TASKIF_IMPL(task,type) \ + GwIreg __register##type(#task,__FILE__,#type) class GwUserTaskDef{ @@ -46,44 +85,79 @@ class GwUserCapability{ } }; #include "GwUserTasks.h" -class TaskData{ - SemaphoreHandle_t lock; - std::map data; - GwLog *logger; - String computeKey(const String &taskName,const String &name) const{ - return taskName+":#:"+name; - } - public: - TaskData(GwLog *l){ - lock=xSemaphoreCreateMutex(); - logger=l; - } - ~TaskData(){ - vSemaphoreDelete(lock); - } - void setTaskValue(const String &taskName,const String &name,const GwApi::Value &v){ - String key=computeKey(taskName,name); - LOG_DEBUG(GwLog::DEBUG,"set task data %s=%s",key.c_str(),v.getSValue().c_str()); - GWSYNCHRONIZED(&lock); - data[key]=v; - } - GwApi::Value getTaskValue(const String &taskName,const String &name){ - GwApi::Value rt; - String key=computeKey(taskName,name); - { - GWSYNCHRONIZED(&lock); - auto it=data.find(key); - if (it != data.end()) rt=it->second; - } - LOG_DEBUG(GwLog::DEBUG,"get task data %s:%s (valid=%d)",key.c_str(),rt.getSValue().c_str(),(int)rt.valid()); - return rt; - } +#include "GwUserTasksIf.h" +class TaskDataEntry{ + public: + GwApi::TaskInterfaces::Ptr ptr; + int updates=0; + TaskDataEntry(GwApi::TaskInterfaces::Ptr p):ptr(p){} + TaskDataEntry(){} + }; +class TaskInterfacesStorage{ + GwLog *logger; + SemaphoreHandle_t lock; + std::map values; + public: + TaskInterfacesStorage(GwLog* l): + logger(l){ + lock=xSemaphoreCreateMutex(); + } + bool set(const String &file, const String &name, const String &task,GwApi::TaskInterfaces::Ptr v){ + GWSYNCHRONIZED(&lock); + auto it=registrations().find(name); + if (it == registrations().end()){ + LOG_DEBUG(GwLog::ERROR,"invalid set %s not known",name.c_str()); + return false; + } + if (it->second.file != file){ + LOG_DEBUG(GwLog::ERROR,"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,"invalid set %s wrong task, expected %s , got %s",name.c_str(),it->second.task.c_str(),task.c_str()); + return false; + } + auto vit=values.find(name); + if (vit != values.end()){ + vit->second.updates++; + vit->second.ptr=v; + } + else{ + values[name]=TaskDataEntry(v); + } + return true; + } + GwApi::TaskInterfaces::Ptr get(const String &name, int &result){ + GWSYNCHRONIZED(&lock); + auto it = values.find(name); + if (it == values.end()) + { + result = -1; + return GwApi::TaskInterfaces::Ptr(); + } + result = it->second.updates; + return it->second.ptr; + } +}; +class TaskInterfacesImpl : public GwApi::TaskInterfaces{ + String task; + TaskInterfacesStorage *storage; + public: + TaskInterfacesImpl(const String &n,TaskInterfacesStorage *s): + task(n),storage(s){} + virtual bool iset(const String &file, const String &name, Ptr v){ + return storage->set(file,name,task,v); + } + virtual Ptr iget(const String &name, int &result){ + return storage->get(name,result); + } }; + + class TaskApi : public GwApiInternal { GwApiInternal *api=nullptr; - TaskData *taskData=nullptr; int sourceId; SemaphoreHandle_t *mainLock; SemaphoreHandle_t localLock; @@ -91,20 +165,20 @@ class TaskApi : public GwApiInternal String name; bool counterUsed=false; int counterIdx=0; - + TaskInterfacesImpl *interfaces; public: TaskApi(GwApiInternal *api, int sourceId, SemaphoreHandle_t *mainLock, const String &name, - TaskData *d) + TaskInterfacesStorage *s) { this->sourceId = sourceId; this->api = api; this->mainLock=mainLock; this->name=name; localLock=xSemaphoreCreateMutex(); - taskData=d; + interfaces=new TaskInterfacesImpl(name,s); } virtual GwRequestQueue *getQueue() { @@ -153,6 +227,7 @@ public: api->getStatus(status); } virtual ~TaskApi(){ + delete interfaces; vSemaphoreDelete(localLock); }; virtual void fillStatus(GwJsonDocument &status){ @@ -202,11 +277,8 @@ public: else it->second=GwCounter("count"+name); return counterIdx; } - virtual void setTaskValue(const String &name,const Value &v){ - taskData->setTaskValue(this->name,name,v); - } - virtual Value getTaskValue(const String &taskName,const String &name){ - return taskData->getTaskValue(taskName,name); + virtual TaskInterfaces * taskInterfaces(){ + return interfaces; } }; @@ -215,7 +287,10 @@ GwUserCode::GwUserCode(GwApiInternal *api,SemaphoreHandle_t *mainLock){ this->logger=api->getLogger(); this->api=api; this->mainLock=mainLock; - this->taskData=new TaskData(this->logger); + this->taskData=new TaskInterfacesStorage(this->logger); +} +GwUserCode::~GwUserCode(){ + delete taskData; } void userTaskStart(void *p){ GwUserTask *task=(GwUserTask*)p; diff --git a/lib/usercode/GwUserCode.h b/lib/usercode/GwUserCode.h index a0d1019..548d0d1 100644 --- a/lib/usercode/GwUserCode.h +++ b/lib/usercode/GwUserCode.h @@ -34,14 +34,15 @@ class GwUserTask{ } }; -class TaskData; +class TaskInterfacesStorage; class GwUserCode{ GwLog *logger; GwApiInternal *api; SemaphoreHandle_t *mainLock; - TaskData *taskData; + TaskInterfacesStorage *taskData; void startAddOnTask(GwApiInternal *api,GwUserTask *task,int sourceId,String name); public: + ~GwUserCode(); typedef std::map Capabilities; GwUserCode(GwApiInternal *api, SemaphoreHandle_t *mainLock); void startUserTasks(int baseId); diff --git a/src/main.cpp b/src/main.cpp index aeea06c..94825ac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -320,6 +320,7 @@ public: return config.getString(config.talkerId,String("GP")).c_str(); } virtual ~ApiImpl(){} + virtual TaskInterfaces *taskInterfaces(){ return nullptr;} }; bool delayedRestart(){