add registerRequestHandler to the API with examples
This commit is contained in:
parent
506dd7ea9f
commit
538f643fbf
|
@ -6,7 +6,9 @@
|
|||
#include "GWConfig.h"
|
||||
#include "GwBoatData.h"
|
||||
#include "GwXDRMappings.h"
|
||||
#include "GwSynchronized.h"
|
||||
#include <map>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
class GwApi;
|
||||
typedef void (*GwUserTaskFunction)(GwApi *);
|
||||
//API to be used for additional tasks
|
||||
|
@ -171,6 +173,20 @@ class GwApi{
|
|||
virtual void remove(int idx){}
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#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
|
||||
|
@ -144,6 +145,26 @@ String formatValue(GwApi::BoatValue *value){
|
|||
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
|
||||
|
@ -172,8 +193,24 @@ void exampleTask(GwApi *api){
|
|||
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
|
||||
|
|
|
@ -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.
|
||||
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
|
||||
----------
|
||||
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
|
||||
let page=api.addTabPage(tabName,"Example");
|
||||
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){
|
||||
window.open(infoUrl,'info');
|
||||
})
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <functional>
|
||||
#include "GwMessage.h"
|
||||
#include "GwLog.h"
|
||||
#include "GwApi.h"
|
||||
class GwWebServer{
|
||||
private:
|
||||
AsyncWebServer *server;
|
||||
|
@ -11,7 +12,7 @@ class GwWebServer{
|
|||
GwLog *logger;
|
||||
public:
|
||||
typedef GwRequestMessage *(RequestCreator)(AsyncWebServerRequest *request);
|
||||
using HandlerFunction=std::function<void(AsyncWebServerRequest *)>;
|
||||
using HandlerFunction=GwApi::HandlerFunction;
|
||||
GwWebServer(GwLog *logger, GwRequestQueue *queue,int port);
|
||||
~GwWebServer();
|
||||
void begin();
|
||||
|
|
|
@ -7,10 +7,10 @@ class GwSynchronized{
|
|||
public:
|
||||
GwSynchronized(SemaphoreHandle_t *locker){
|
||||
this->locker=locker;
|
||||
xSemaphoreTake(*locker, portMAX_DELAY);
|
||||
if (locker != nullptr) xSemaphoreTake(*locker, portMAX_DELAY);
|
||||
}
|
||||
~GwSynchronized(){
|
||||
xSemaphoreGive(*locker);
|
||||
if (locker != nullptr) xSemaphoreGive(*locker);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -191,6 +191,7 @@ class TaskApi : public GwApiInternal
|
|||
SemaphoreHandle_t *mainLock;
|
||||
SemaphoreHandle_t localLock;
|
||||
std::map<int,GwCounter<String>> counter;
|
||||
std::map<String,GwApi::HandlerFunction> webHandlers;
|
||||
String name;
|
||||
bool counterUsed=false;
|
||||
int counterIdx=0;
|
||||
|
@ -315,6 +316,10 @@ public:
|
|||
virtual bool addXdrMapping(const GwXDRMappingDef &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){
|
||||
if (! isInit) return;
|
||||
userCapabilities[name]=value;
|
||||
|
@ -335,6 +340,16 @@ public:
|
|||
virtual void setCalibrationValue(const String &name, double 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;
|
||||
}
|
||||
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(){}
|
||||
virtual void fillStatus(GwJsonDocument &status){};
|
||||
virtual int getJsonSize(){return 0;};
|
||||
virtual bool handleWebRequest(const String &url,AsyncWebServerRequest *req){return false;}
|
||||
};
|
||||
class GwUserTask{
|
||||
public:
|
||||
|
@ -50,5 +51,6 @@ class GwUserCode{
|
|||
Capabilities *getCapabilities();
|
||||
void fillStatus(GwJsonDocument &status);
|
||||
int getJsonSize();
|
||||
void handleWebRequest(const String &url,AsyncWebServerRequest *);
|
||||
};
|
||||
#endif
|
10
src/main.cpp
10
src/main.cpp
|
@ -348,6 +348,8 @@ public:
|
|||
}
|
||||
return xdrMappings.addFixedMapping(mapping);
|
||||
}
|
||||
virtual void registerRequestHandler(const String &url,HandlerFunction handler){
|
||||
}
|
||||
virtual void addCapability(const String &name, const String &value){}
|
||||
virtual bool addUserTask(GwUserTaskFunction task,const String Name, int stackSize=2000){
|
||||
return false;
|
||||
|
@ -768,6 +770,7 @@ void loopFunction(void *){
|
|||
//delay(1);
|
||||
}
|
||||
}
|
||||
const String USERPREFIX="/api/user/";
|
||||
void setup() {
|
||||
mainLock=xSemaphoreCreateMutex();
|
||||
uint8_t chipid[6];
|
||||
|
@ -845,7 +848,12 @@ void setup() {
|
|||
snprintf(buffer,29,"%g",value);
|
||||
buffer[29]=0;
|
||||
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();
|
||||
xdrMappings.begin();
|
||||
|
|
Loading…
Reference in New Issue