//we only compile for some boards #ifdef BOARD_TEST #include "GwExampleTask.h" #include "GwApi.h" #include "GWConfig.h" #include #include "N2kMessages.h" #include "GwXdrTypeMappings.h" /** * INVALID!!! - the next interface declaration will not work * as it is not in the correct header file * it is just included here to show you how errors * could be created. * if you call the apiSetExampleNotWorkingIf method * it will always return false */ class ExampleNotWorkingIf: public GwApi::TaskInterfaces::Base{ public: int someValue=99; }; DECLARE_TASKIF(ExampleNotWorkingIf); void exampleTask(GwApi *param); /** * an init function that ist being called before other initializations from the core */ void exampleInit(GwApi *api){ api->getLogger()->logDebug(GwLog::LOG,"example init running"); // make the task known to the core // the task function should not return (unless you delete the task - see example code) const String taskName("exampleTask"); api->addUserTask(exampleTask, taskName, 4000); // this would create our task with a stack size of 4000 bytes // we declare some capabilities that we can // use in config.json to only show some // elements when this capability is set correctly api->addCapability("testboard", "true"); api->addCapability("testboard2", "true"); // hide some config value // and force it's default value auto current=api->getConfig()->getConfigItem(GwConfigDefinitions::minXdrInterval,false); String defaultXdrInt="50"; if (current){ defaultXdrInt=current->getDefault(); } //with the true parameter this config value will be hidden //if you would like the user to be able to see this item, omit the "true", the config value will be read only api->getConfig()->setValue(GwConfigDefinitions::minXdrInterval,defaultXdrInt,true); // example for a user defined help url that will be shown when clicking the help button api->addCapability("HELP_URL", "https://www.wellenvogel.de"); //we would like to store data with the interfaces that we defined //the name must match the one we used for addUserTask api->taskInterfaces()->claim(taskName); //not working interface if (!api->taskInterfaces()->claim(taskName)){ api->getLogger()->logDebug(GwLog::ERROR,"unable to claim ExampleNotWorkingIf"); } //check if we should simulate some voltage measurements //add an XDR mapping in this case String voltageTransducer=api->getConfig()->getString(GwConfigDefinitions::exTransducer); if (!voltageTransducer.isEmpty()){ int instance=api->getConfig()->getInt(GwConfigDefinitions::exInstanceId); GwXDRMappingDef xdr; //we send a battery message - so it is category battery xdr.category=GwXDRCategory::XDRBAT; //we only need a conversion from N2K to NMEA0183 xdr.direction=GwXDRMappingDef::Direction::M_FROM2K; //you can find the XDR field and selector definitions //in the generated GwXdrTypeMappings.h xdr.field=GWXDRFIELD_BATTERY_BATTERYVOLTAGE; //xdr.selector=-1; //refer to xdrconfig.json - there is no selector under Battery, so we can leave it empty //we just map exactly our instance xdr.instanceMode=GwXDRMappingDef::IS_SINGLE; //those are the user configured values //this instance is the one we use for the battery instance when we set up //the N2K message xdr.instanceId=instance; xdr.xdrName=voltageTransducer; if (!api->addXdrMapping(xdr)){ api->getLogger()->logDebug(GwLog::ERROR,"unable to set our xdr mapping %s",xdr.toString().c_str()); } } } #define INVALID_COORD -99999 class GetBoatDataRequest: public GwMessage{ private: GwApi *api; public: double latitude; double longitude; GetBoatDataRequest(GwApi *api):GwMessage(F("boat data")){ this->api=api; } virtual ~GetBoatDataRequest(){} protected: /** * this methos will be executed within the main thread * be sure not to make any time consuming or blocking operation */ virtual void processImpl(){ //api->getLogger()->logDebug(GwLog::DEBUG,"boatData request from example task"); /*access the values from boatData (see GwBoatData.h) by using getDataWithDefault it will return the given default value if there is no valid data available so be sure to use a value that never will be a valid one alternatively you can check using the isValid() method at each boatData item */ latitude=api->getBoatData()->LAT->getDataWithDefault(INVALID_COORD); longitude=api->getBoatData()->LON->getDataWithDefault(INVALID_COORD); }; }; String formatValue(GwApi::BoatValue *value){ if (! value->valid) return "----"; static const int bsize=30; char buffer[bsize+1]; buffer[0]=0; if (value->getFormat() == GwBoatItemBase::formatDate){ time_t tv=tNMEA0183Msg::daysToTime_t(value->value); tmElements_t parts; tNMEA0183Msg::breakTime(tv,parts); snprintf(buffer,bsize,"%04d/%02d/%02d",parts.tm_year+1900,parts.tm_mon+1,parts.tm_mday); } else if(value->getFormat() == GwBoatItemBase::formatTime){ double inthr; double intmin; double intsec; double val; val=modf(value->value/3600.0,&inthr); val=modf(val*3600.0/60.0,&intmin); modf(val*60.0,&intsec); snprintf(buffer,bsize,"%02.0f:%02.0f:%02.0f",inthr,intmin,intsec); } else if (value->getFormat() == GwBoatItemBase::formatFixed0){ snprintf(buffer,bsize,"%.0f",value->value); } else{ snprintf(buffer,bsize,"%.4f",value->value); } buffer[bsize]=0; return String(buffer); } class ExampleWebData{ SemaphoreHandle_t lock; int data=0; public: ExampleWebData(){ lock=xSemaphoreCreateMutex(); } ~ExampleWebData(){ vSemaphoreDelete(lock); } void set(int v){ GWSYNCHRONIZED(lock); data=v; } int get(){ GWSYNCHRONIZED(lock); return data; } }; void exampleTask(GwApi *api){ GwLog *logger=api->getLogger(); //get some configuration data bool exampleSwitch=api->getConfig()->getConfigItem( api->getConfig()->exampleConfig, true)->asBoolean(); String boatItemName=api->getConfig()->getString(api->getConfig()->exampleBDSel); //------ //initialization goes here //------ bool hasPosition=false; bool hasPosition2=false; LOG_DEBUG(GwLog::LOG,"example switch is %s",exampleSwitch?"true":"false"); LOG_DEBUG(GwLog::LOG,"minXdrInterval=%d",api->getConfig()->getInt(api->getConfig()->minXdrInterval)); GwApi::BoatValue *longitude=new GwApi::BoatValue(GwBoatData::_LON); GwApi::BoatValue *latitude=new GwApi::BoatValue(GwBoatData::_LAT); GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName); GwApi::BoatValue *valueList[]={longitude,latitude,testValue}; GwApi::Status status; int counter=api->addCounter("usertest"); int apiResult=0; ExampleTaskIf e1=api->taskInterfaces()->get(apiResult); LOG_DEBUG(GwLog::LOG,"exampleIf before rs=%d,v=%d,s=%s",apiResult,e1.count,e1.someValue.c_str()); ExampleNotWorkingIf nw1; bool nwrs=api->taskInterfaces()->set(nw1); LOG_DEBUG(GwLog::LOG,"exampleNotWorking update returned %d",(int)nwrs); String voltageTransducer=api->getConfig()->getString(GwConfigDefinitions::exTransducer); int voltageInstance=api->getConfig()->getInt(GwConfigDefinitions::exInstanceId); ExampleWebData webData; /** * an example web request handler * it uses a synchronized data structure as it gets called from a different thread * be aware that you must not block for longer times here! */ api->registerRequestHandler("data",[&webData](AsyncWebServerRequest *request){ int data=webData.get(); char buffer[30]; snprintf(buffer,29,"%d",data); buffer[29]=0; request->send(200,"text/plain",buffer); }); int loopcounter=0; while(true){ delay(1000); loopcounter++; webData.set(loopcounter); /* * getting values from the internal data store (boatData) requires some special handling * our tasks runs (potentially) at some time on a different core then the main code * and as the boatData has no synchronization (for performance reasons) * we must ensure to access it only from the main thread. * The pattern is to create a request object (a class that inherits from GwMessage - * GetBoatDataRequest above) * and to access the boatData in the processImpl method of this object. * Once the object is created we enqueue it into a request queue and wait for * the main thread to call the processImpl method (sendAndWait). * Afterwards we can use the data we have stored in the request. * As this request object can be accessed from different threads we must be careful * about it's lifetime. * The pattern below handles this correctly. We do not call delete on this object but * instead call the "unref" Method when we don't need it any more. */ GetBoatDataRequest *r=new GetBoatDataRequest(api); if (api->getQueue()->sendAndWait(r,10000) != GwRequestQueue::MSG_OK){ r->unref(); //delete the request api->getLogger()->logDebug(GwLog::ERROR,"request not handled"); continue; } if (r->latitude == INVALID_COORD || r->longitude == INVALID_COORD){ if (hasPosition){ if (exampleSwitch) logger->logDebug(GwLog::ERROR,"position lost..."); hasPosition=false; } } else{ //do something with the data we have from boatData if (! hasPosition){ if (exampleSwitch) logger->logDebug(GwLog::LOG,"postion now available lat=%f, lon=%f", r->latitude,r->longitude); hasPosition=true; } } r->unref(); //delete the request /** second example with string based functions to access boatData This does not need the request handling approach Finally it only makes sense to use one of the versions - either with the request or with the ValueMap approach. **/ //fetch the current values of the items that we have in itemNames api->getBoatDataValues(3,valueList); //check if the values are valid (i.e. the values we requested have been found in boatData) if (longitude->valid && latitude->valid){ //both values are there - so we have a valid position if (! hasPosition2){ //access to the values via iterator->second (iterator->first would be the name) if (exampleSwitch) LOG_DEBUG(GwLog::LOG,"(2)position availale lat=%s, lon=%s", formatValue(latitude).c_str(),formatValue(longitude).c_str()); hasPosition2=true; } } else{ if (hasPosition2){ if (exampleSwitch) LOG_DEBUG(GwLog::LOG,"(2)position lost"); hasPosition2=false; } } if (testValue->valid){ if (testValue->changed){ LOG_DEBUG(GwLog::LOG,"%s new value %s",testValue->getName().c_str(),formatValue(testValue).c_str()); } } else{ if (testValue->changed){ LOG_DEBUG(GwLog::LOG,"%s now invalid",testValue->getName().c_str()); } } api->getStatus(status); #define B(v) (v?"true":"false") LOG_DEBUG(GwLog::LOG,"ST1:ap=%s,wc=%s,cc=%s", B(status.wifiApOn), B(status.wifiClientOn), B(status.wifiClientConnected)); LOG_DEBUG(GwLog::LOG,"ST2:sn=%s,ai=%s,ap=%s,cs=%s,ci=%s", status.systemName.c_str(), status.wifiApIp.c_str(), status.wifiApPass.c_str(), status.wifiClientSSID.c_str(), status.wifiClientIp.c_str()); LOG_DEBUG(GwLog::LOG,"ST3:ur=%ld,ut=%ld,sr=%ld,st=%ld,tr=%ld,tt=%ld,cr=%ld,ct=%ld,2r=%ld,2t=%ld", status.usbRx, status.usbTx, status.serRx, status.serTx, status.tcpSerRx, status.tcpSerTx, status.tcpClRx, status.tcpClTx, status.n2kRx, status.n2kTx); //increment some counter api->increment(counter,"Test"); ExampleTaskIf e2=api->taskInterfaces()->get(apiResult); LOG_DEBUG(GwLog::LOG,"exampleIf before update rs=%d,v=%d,s=%s",apiResult,e2.count,e2.someValue.c_str()); e1.count+=1; e1.someValue="running"; bool rs=api->taskInterfaces()->set(e1); LOG_DEBUG(GwLog::LOG,"exampleIf update rs=%d,v=%d,s=%s",(int)rs,e1.count,e1.someValue.c_str()); ExampleTaskIf e3=api->taskInterfaces()->get(apiResult); LOG_DEBUG(GwLog::LOG,"exampleIf after update rs=%d,v=%d,s=%s",apiResult,e3.count,e3.someValue.c_str()); if (!voltageTransducer.isEmpty()){ //simulate some voltage measurements... double offset=100.0*(double)std::rand()/RAND_MAX - 50.0; double simVoltage=(1200.0+offset)/100; LOG_DEBUG(GwLog::LOG,"simulated voltage %f",(float)simVoltage); tN2kMsg msg; SetN2kDCBatStatus(msg,voltageInstance,simVoltage); //we send out an N2K message //and as we added an XDR mapping, we will see this in the data dashboard //and on the NMEA0183 stream api->sendN2kMessage(msg); } } vTaskDelete(NULL); } #endif