-
Notifications
You must be signed in to change notification settings - Fork 28
/
GwExampleTask.cpp
299 lines (289 loc) · 13 KB
/
GwExampleTask.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
//we only compile for some boards
#ifdef BOARD_TEST
#include "GwExampleTask.h"
#include "GwApi.h"
#include "GWConfig.h"
#include <vector>
#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<ExampleTaskIf>(taskName);
//not working interface
if (!api->taskInterfaces()->claim<ExampleNotWorkingIf>(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);
}
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<ExampleTaskIf>(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);
while(true){
delay(1000);
/*
* 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<ExampleTaskIf>(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<ExampleTaskIf>(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