add registerRequestHandler to the API with examples
This commit is contained in:
parent
506dd7ea9f
commit
538f643fbf
|
@ -6,7 +6,9 @@
|
||||||
#include "GWConfig.h"
|
#include "GWConfig.h"
|
||||||
#include "GwBoatData.h"
|
#include "GwBoatData.h"
|
||||||
#include "GwXDRMappings.h"
|
#include "GwXDRMappings.h"
|
||||||
|
#include "GwSynchronized.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
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
|
||||||
|
@ -171,6 +173,20 @@ class GwApi{
|
||||||
virtual void remove(int idx){}
|
virtual void remove(int idx){}
|
||||||
virtual TaskInterfaces * taskInterfaces()=0;
|
virtual TaskInterfaces * taskInterfaces()=0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* register handler for web URLs
|
||||||
|
* Please be aware that this handler function will always be called from a separate
|
||||||
|
* task. So you must ensure proper synchronization!
|
||||||
|
*/
|
||||||
|
using HandlerFunction=std::function<void(AsyncWebServerRequest *)>;
|
||||||
|
/**
|
||||||
|
* @param url: the url of that will trigger the handler.
|
||||||
|
* it will be prefixed with /api/user/<taskname>
|
||||||
|
* taskname is the name that you used in addUserTask
|
||||||
|
* @param handler: the handler function (see remark above about thread synchronization)
|
||||||
|
*/
|
||||||
|
virtual void registerRequestHandler(const String &url,HandlerFunction handler)=0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* only allowed during init methods
|
* only allowed during init methods
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "N2kMessages.h"
|
#include "N2kMessages.h"
|
||||||
#include "GwXdrTypeMappings.h"
|
#include "GwXdrTypeMappings.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* INVALID!!! - the next interface declaration will not work
|
* INVALID!!! - the next interface declaration will not work
|
||||||
* as it is not in the correct header file
|
* as it is not in the correct header file
|
||||||
|
@ -144,6 +145,26 @@ String formatValue(GwApi::BoatValue *value){
|
||||||
return String(buffer);
|
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){
|
void exampleTask(GwApi *api){
|
||||||
GwLog *logger=api->getLogger();
|
GwLog *logger=api->getLogger();
|
||||||
//get some configuration data
|
//get some configuration data
|
||||||
|
@ -172,8 +193,24 @@ void exampleTask(GwApi *api){
|
||||||
LOG_DEBUG(GwLog::LOG,"exampleNotWorking update returned %d",(int)nwrs);
|
LOG_DEBUG(GwLog::LOG,"exampleNotWorking update returned %d",(int)nwrs);
|
||||||
String voltageTransducer=api->getConfig()->getString(GwConfigDefinitions::exTransducer);
|
String voltageTransducer=api->getConfig()->getString(GwConfigDefinitions::exTransducer);
|
||||||
int voltageInstance=api->getConfig()->getInt(GwConfigDefinitions::exInstanceId);
|
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){
|
while(true){
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
loopcounter++;
|
||||||
|
webData.set(loopcounter);
|
||||||
/*
|
/*
|
||||||
* getting values from the internal data store (boatData) requires some special handling
|
* 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
|
* our tasks runs (potentially) at some time on a different core then the main code
|
||||||
|
|
|
@ -32,6 +32,26 @@ Files
|
||||||
This file allows to add some config definitions that are needed for our task. For the possible options have a look at the global [config.json](../../web/config.json). Be careful not to overwrite config defitions from the global file. A good practice wood be to prefix the names of definitions with parts of the library name. Always put them in a separate category so that they do not interfere with the system ones.
|
This file allows to add some config definitions that are needed for our task. For the possible options have a look at the global [config.json](../../web/config.json). Be careful not to overwrite config defitions from the global file. A good practice wood be to prefix the names of definitions with parts of the library name. Always put them in a separate category so that they do not interfere with the system ones.
|
||||||
The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)).
|
The defined config items can later be accessed in the code (see the example in [GwExampleTask.cpp](GwExampleTask.cpp)).
|
||||||
|
|
||||||
|
* [index.js](index.js)<br>
|
||||||
|
You can add javascript code that will contribute to the UI of the system. The WebUI provides a small API that allows you to "hook" into some functions to include your own parts of the UI. This includes adding new tabs, modifying/replacing the data display items, modifying the status display or accessing the config items.
|
||||||
|
For the API refer to [../../web/index.js](../../web/index.js#L2001).
|
||||||
|
To start interacting just register for some events like api.EVENTS.init. You can check the capabilities you have defined to see if your task is active.
|
||||||
|
By registering an own formatter [api.addUserFormatter](../../web/index.js#L2054) you can influence the way boat data items are shown.
|
||||||
|
You can even go for an own display by registering for the event *dataItemCreated* and replace the dom element content with your own html. By additionally having added a user formatter you can now fill your own html with the current value.
|
||||||
|
By using [api.addTabPage](../../web/index.js#L2046) you can add new tabs that you can populate with your own code. Or you can link to an external URL.<br>
|
||||||
|
Please be aware that your js code is always combined with the code from the core into one js file.<br>
|
||||||
|
For fast testing there is a small python script that allow you to test the UI without always flushing each change.
|
||||||
|
Just run it with
|
||||||
|
```
|
||||||
|
tools/testServer.py nnn http://x.x.x.x/api
|
||||||
|
```
|
||||||
|
with nnn being the local port and x.x.x.x the address of a running system. Open `http://localhost:nnn` in your browser.<br>
|
||||||
|
After a change just start the compilation and reload the page.
|
||||||
|
|
||||||
|
* [index.css](index.css)<br>
|
||||||
|
You can add own css to influence the styling of the display.
|
||||||
|
|
||||||
|
|
||||||
Interfaces
|
Interfaces
|
||||||
----------
|
----------
|
||||||
The task init function and the task function interact with the core using an [API](../api/GwApi.h) that they get when started.
|
The task init function and the task function interact with the core using an [API](../api/GwApi.h) that they get when started.
|
||||||
|
|
|
@ -28,6 +28,21 @@
|
||||||
//you can use the helper addEl to create elements
|
//you can use the helper addEl to create elements
|
||||||
let page=api.addTabPage(tabName,"Example");
|
let page=api.addTabPage(tabName,"Example");
|
||||||
api.addEl('div','hdg',page,"this is a test tab");
|
api.addEl('div','hdg',page,"this is a test tab");
|
||||||
|
let vrow=api.addEl('div','row',page);
|
||||||
|
api.addEl('span','label',vrow,'loops: ');
|
||||||
|
let lcount=api.addEl('span','value',vrow,'0');
|
||||||
|
//query the loop count
|
||||||
|
window.setInterval(()=>{
|
||||||
|
fetch('/api/user/exampleTask/data')
|
||||||
|
.then((res)=>{
|
||||||
|
if (! res.ok) throw Error("server error: "+res.status);
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then((txt)=>{
|
||||||
|
lcount.textContent=txt;
|
||||||
|
})
|
||||||
|
.catch((e)=>console.log("rq:",e));
|
||||||
|
},1000);
|
||||||
api.addEl('button','',page,'Info').addEventListener('click',function(ev){
|
api.addEl('button','',page,'Info').addEventListener('click',function(ev){
|
||||||
window.open(infoUrl,'info');
|
window.open(infoUrl,'info');
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include "GwMessage.h"
|
#include "GwMessage.h"
|
||||||
#include "GwLog.h"
|
#include "GwLog.h"
|
||||||
|
#include "GwApi.h"
|
||||||
class GwWebServer{
|
class GwWebServer{
|
||||||
private:
|
private:
|
||||||
AsyncWebServer *server;
|
AsyncWebServer *server;
|
||||||
|
@ -11,7 +12,7 @@ class GwWebServer{
|
||||||
GwLog *logger;
|
GwLog *logger;
|
||||||
public:
|
public:
|
||||||
typedef GwRequestMessage *(RequestCreator)(AsyncWebServerRequest *request);
|
typedef GwRequestMessage *(RequestCreator)(AsyncWebServerRequest *request);
|
||||||
using HandlerFunction=std::function<void(AsyncWebServerRequest *)>;
|
using HandlerFunction=GwApi::HandlerFunction;
|
||||||
GwWebServer(GwLog *logger, GwRequestQueue *queue,int port);
|
GwWebServer(GwLog *logger, GwRequestQueue *queue,int port);
|
||||||
~GwWebServer();
|
~GwWebServer();
|
||||||
void begin();
|
void begin();
|
||||||
|
|
|
@ -7,10 +7,10 @@ class GwSynchronized{
|
||||||
public:
|
public:
|
||||||
GwSynchronized(SemaphoreHandle_t *locker){
|
GwSynchronized(SemaphoreHandle_t *locker){
|
||||||
this->locker=locker;
|
this->locker=locker;
|
||||||
xSemaphoreTake(*locker, portMAX_DELAY);
|
if (locker != nullptr) xSemaphoreTake(*locker, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
~GwSynchronized(){
|
~GwSynchronized(){
|
||||||
xSemaphoreGive(*locker);
|
if (locker != nullptr) xSemaphoreGive(*locker);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -191,6 +191,7 @@ class TaskApi : public GwApiInternal
|
||||||
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;
|
||||||
String name;
|
String name;
|
||||||
bool counterUsed=false;
|
bool counterUsed=false;
|
||||||
int counterIdx=0;
|
int counterIdx=0;
|
||||||
|
@ -315,6 +316,10 @@ public:
|
||||||
virtual bool addXdrMapping(const GwXDRMappingDef &def){
|
virtual bool addXdrMapping(const GwXDRMappingDef &def){
|
||||||
return api->addXdrMapping(def);
|
return api->addXdrMapping(def);
|
||||||
}
|
}
|
||||||
|
virtual void registerRequestHandler(const String &url,HandlerFunction handler){
|
||||||
|
GWSYNCHRONIZED(&localLock);
|
||||||
|
webHandlers[url]=handler;
|
||||||
|
}
|
||||||
virtual void addCapability(const String &name, const String &value){
|
virtual void addCapability(const String &name, const String &value){
|
||||||
if (! isInit) return;
|
if (! isInit) return;
|
||||||
userCapabilities[name]=value;
|
userCapabilities[name]=value;
|
||||||
|
@ -335,6 +340,16 @@ public:
|
||||||
virtual void setCalibrationValue(const String &name, double value){
|
virtual void setCalibrationValue(const String &name, double value){
|
||||||
api->setCalibrationValue(name,value);
|
api->setCalibrationValue(name,value);
|
||||||
}
|
}
|
||||||
|
virtual bool handleWebRequest(const String &url,AsyncWebServerRequest *req){
|
||||||
|
GWSYNCHRONIZED(&localLock);
|
||||||
|
auto it=webHandlers.find(url);
|
||||||
|
if (it == webHandlers.end()){
|
||||||
|
api->getLogger()->logDebug(GwLog::LOG,"no web handler task=%s url=%s",name.c_str(),url.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
it->second(req);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -404,4 +419,19 @@ int GwUserCode::getJsonSize(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rt;
|
return rt;
|
||||||
|
}
|
||||||
|
void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){
|
||||||
|
int sep1=url.indexOf('/');
|
||||||
|
String tname;
|
||||||
|
if (sep1 > 0){
|
||||||
|
tname=url.substring(0,sep1);
|
||||||
|
for (auto &&it:userTasks){
|
||||||
|
if (it.api && it.name == tname){
|
||||||
|
if (it.api->handleWebRequest(url.substring(sep1+1),req)) return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str());
|
||||||
|
req->send(404, "text/plain", "not found");
|
||||||
}
|
}
|
|
@ -11,6 +11,7 @@ class GwApiInternal : public GwApi{
|
||||||
~GwApiInternal(){}
|
~GwApiInternal(){}
|
||||||
virtual void fillStatus(GwJsonDocument &status){};
|
virtual void fillStatus(GwJsonDocument &status){};
|
||||||
virtual int getJsonSize(){return 0;};
|
virtual int getJsonSize(){return 0;};
|
||||||
|
virtual bool handleWebRequest(const String &url,AsyncWebServerRequest *req){return false;}
|
||||||
};
|
};
|
||||||
class GwUserTask{
|
class GwUserTask{
|
||||||
public:
|
public:
|
||||||
|
@ -50,5 +51,6 @@ class GwUserCode{
|
||||||
Capabilities *getCapabilities();
|
Capabilities *getCapabilities();
|
||||||
void fillStatus(GwJsonDocument &status);
|
void fillStatus(GwJsonDocument &status);
|
||||||
int getJsonSize();
|
int getJsonSize();
|
||||||
|
void handleWebRequest(const String &url,AsyncWebServerRequest *);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
10
src/main.cpp
10
src/main.cpp
|
@ -348,6 +348,8 @@ public:
|
||||||
}
|
}
|
||||||
return xdrMappings.addFixedMapping(mapping);
|
return xdrMappings.addFixedMapping(mapping);
|
||||||
}
|
}
|
||||||
|
virtual void registerRequestHandler(const String &url,HandlerFunction handler){
|
||||||
|
}
|
||||||
virtual void addCapability(const String &name, const String &value){}
|
virtual void addCapability(const String &name, const String &value){}
|
||||||
virtual bool addUserTask(GwUserTaskFunction task,const String Name, int stackSize=2000){
|
virtual bool addUserTask(GwUserTaskFunction task,const String Name, int stackSize=2000){
|
||||||
return false;
|
return false;
|
||||||
|
@ -768,6 +770,7 @@ void loopFunction(void *){
|
||||||
//delay(1);
|
//delay(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const String USERPREFIX="/api/user/";
|
||||||
void setup() {
|
void setup() {
|
||||||
mainLock=xSemaphoreCreateMutex();
|
mainLock=xSemaphoreCreateMutex();
|
||||||
uint8_t chipid[6];
|
uint8_t chipid[6];
|
||||||
|
@ -845,7 +848,12 @@ void setup() {
|
||||||
snprintf(buffer,29,"%g",value);
|
snprintf(buffer,29,"%g",value);
|
||||||
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){
|
||||||
|
String turl=req->url().substring(USERPREFIX.length());
|
||||||
|
logger.logDebug(GwLog::DEBUG,"user web request for %s",turl.c_str());
|
||||||
|
userCodeHandler.handleWebRequest(turl,req);
|
||||||
|
});
|
||||||
|
|
||||||
webserver.begin();
|
webserver.begin();
|
||||||
xdrMappings.begin();
|
xdrMappings.begin();
|
||||||
|
|
Loading…
Reference in New Issue