Merge branch 'wellenvogel:master' into feature/env2

This commit is contained in:
free-x 2021-11-24 10:57:32 +01:00 committed by GitHub
commit 0bc071f856
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 824 additions and 76 deletions

View File

@ -10,6 +10,16 @@ GwBoatData::~GwBoatData(){
}
}
template<class T> GwBoatItem<T> *GwBoatData::getOrCreate(T dummy, String name, String format,
unsigned long invalidTime)
{
for (auto it=values.begin();it != values.end();it++){
if ((*it)->getName() == name){
return *it;
}
}
return new GwBoatItem<T>(name,format,invalidTime,&values);
}
String GwBoatData::toJson() const {
unsigned long minTime=millis();
GwBoatItemBase::GwBoatItemMap::const_iterator it;

View File

@ -55,6 +55,7 @@ class GwBoatItemBase{
virtual size_t getJsonSize(){return JSON_OBJECT_SIZE(15);}
virtual int getLastSource()=0;
virtual void refresh(unsigned long ts=0){uls(ts);}
String getName(){return name;}
};
class GwBoatData;
template<class T> class GwBoatItem : public GwBoatItemBase{
@ -253,8 +254,43 @@ class GwBoatData{
public:
GwBoatData(GwLog *logger);
~GwBoatData();
template<class T> GwBoatItem<T> *getOrCreate(T dummy,String name,String format,
unsigned long invalidTime=GwBoatItemBase::INVALID_TIME);
String toJson() const;
};
/**
* class for lazy creation of a boat item
* once we have someone that knows the name for it
* and providing fast access without searching all the time trough the map
* xdr mappings implement such a provider
*/
class GwBoatItemNameProvider
{
public:
virtual String getBoatItemName() = 0;
virtual String getBoatItemFormat() = 0;
virtual ~GwBoatItemNameProvider() {}
};
template<class T> class GwBoatItemHolder{
private:
GwBoatItem<T> *item=NULL;
GwBoatData *data;
unsigned long invalidTime=GwBoatItemBase::INVALID_TIME;
public:
GwBoatItemHolder(GwBoatData *data,unsigned long invalidTime=GwBoatItemBase::INVALID_TIME){
this->data=data;
this->invalidTime=invalidTime;
}
GwBoatItem<T> *getItem(GwBoatItemNameProvider *provider){
if (item) return item;
T dummy;
item=data->getOrCreate(dummy,
provider->getBoatItemName() ,
provider->getBoatItemFormat(),
invalidTime);
return item;
}
GwBoatItem<T> *getItem(){return item;}
};
#endif

View File

@ -2,6 +2,7 @@
#define _GWXDRMAPPINGS_H
#include "GwLog.h"
#include "GWConfig.h"
#include "GwBoatData.h"
#include <WString.h>
#include <vector>
#include <map>
@ -141,7 +142,7 @@ class GwXDRMapping{
typedef std::map<String,MappingList> N138Map;
typedef std::map<unsigned long,MappingList> N2KMap;
};
class GwXDRFoundMapping{
class GwXDRFoundMapping : public GwBoatItemNameProvider{
public:
GwXDRMappingDef *definition=NULL;
GwXDRType *type=NULL;
@ -163,6 +164,14 @@ class GwXDRFoundMapping{
return definition->getTransducerName(instanceId);
}
String buildXdrEntry(double value);
//boat Data info
virtual String getBoatItemName(){
return getTransducerName();
};
virtual String getBoatItemFormat(){
return "formatXdr"+type->xdrunit; //TODO: use the type def for the correct format
};
virtual ~GwXDRFoundMapping(){}
};
//the class GwXDRMappings is not intended to be deleted

15
tools/genXdrCfg.sh Executable file
View File

@ -0,0 +1,15 @@
#! /bin/sh
i=0
[ "$2" != "" ] && i=$2
if [ "$1" = "" ] ; then
echo "usage: $0 num [start]"
exit 1
fi
num=$1
while [ $num -gt 0 ]
do
echo '{"name": "XDR'$i'","label": "XDR'$i'","type": "xdr","default": "", "check": "checkXDR","category":"xdr'$i'"},'
num=`expr $num - 1`
i=`expr $i + 1`
done

View File

@ -6,16 +6,101 @@
"default": "ESP32NMEA2K",
"check": "checkSystemName",
"description": "system name, used for the access point and for services",
"category":"system"
"category": "system"
},
{
"name":"talkerId",
"label":"NMEA0183 ID",
"type":"list",
"default":"GP",
"list":["AB","AD","AG","AP","AI","AN","AR","AS","AT","AX","BI","BN","CA","CD","CR","CS","CT","CV","CX","DF","DU","DP","EC","EI","EP","ER","FD","FE","FR","FS","GA","GB","GI","GL","GN","GP","GQ","HC","HE","HF","HN","HD","HS","II","IN","JA","JB","JC","JD","JE","JF","JG","JH","LC","NL","NV","RA","RB","RC","RI","SA","SC","SD","SG","SN","SS","TC","TI","UP","VD","VM","VW","VA","VS","VT","VR","WD","WI","WL","YX","ZA","ZC","ZQ","ZV"],
"description":"the talkerId used in generated NMEA0183 records",
"category":"system"
"name": "talkerId",
"label": "NMEA0183 ID",
"type": "list",
"default": "GP",
"list": [
"AB",
"AD",
"AG",
"AP",
"AI",
"AN",
"AR",
"AS",
"AT",
"AX",
"BI",
"BN",
"CA",
"CD",
"CR",
"CS",
"CT",
"CV",
"CX",
"DF",
"DU",
"DP",
"EC",
"EI",
"EP",
"ER",
"FD",
"FE",
"FR",
"FS",
"GA",
"GB",
"GI",
"GL",
"GN",
"GP",
"GQ",
"HC",
"HE",
"HF",
"HN",
"HD",
"HS",
"II",
"IN",
"JA",
"JB",
"JC",
"JD",
"JE",
"JF",
"JG",
"JH",
"LC",
"NL",
"NV",
"RA",
"RB",
"RC",
"RI",
"SA",
"SC",
"SD",
"SG",
"SN",
"SS",
"TC",
"TI",
"UP",
"VD",
"VM",
"VW",
"VA",
"VS",
"VT",
"VR",
"WD",
"WI",
"WL",
"YX",
"ZA",
"ZC",
"ZQ",
"ZV"
],
"description": "the talkerId used in generated NMEA0183 records",
"category": "system"
},
{
"name": "stopApTime",
@ -23,7 +108,7 @@
"default": "0",
"check": "checkStopApTime",
"description": "stop the access point after that many minutes if not used",
"category":"system"
"category": "system"
},
{
"name": "apPassword",
@ -31,8 +116,12 @@
"default": "esp32nmea2k",
"check": "checkApPass",
"description": "set the password for the Wifi access point",
"category":"system",
"capabilities":{"hardwareReset":["true"]}
"category": "system",
"capabilities": {
"hardwareReset": [
"true"
]
}
},
{
"name": "usbBaud",
@ -40,8 +129,21 @@
"type": "list",
"default": "115200",
"description": "baud rate for the USB port",
"list": [1200,2400,4800,9600,14400,19200,28800,38400,57600,115200,230400,460800],
"category":"usb port"
"list": [
1200,
2400,
4800,
9600,
14400,
19200,
28800,
38400,
57600,
115200,
230400,
460800
],
"category": "usb port"
},
{
"name": "sendUsb",
@ -49,7 +151,7 @@
"type": "boolean",
"default": "true",
"description": "send out NMEA data on the USB port",
"category":"usb port"
"category": "usb port"
},
{
"name": "receiveUsb",
@ -57,7 +159,7 @@
"type": "boolean",
"default": "true",
"description": "receive NMEA data on the USB port",
"category":"usb port"
"category": "usb port"
},
{
"name": "usbToN2k",
@ -65,7 +167,7 @@
"type": "boolean",
"default": "true",
"description": "convert NMEA0183 from the USB port to NMEA2000",
"category":"usb port"
"category": "usb port"
},
{
"name": "usbReadFilter",
@ -73,7 +175,7 @@
"type": "filter",
"default": "",
"description": "filter for NMEA0183 data when reading from USB\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB",
"category":"usb port"
"category": "usb port"
},
{
"name": "usbWriteFilter",
@ -81,17 +183,25 @@
"type": "filter",
"default": "",
"description": "filter for NMEA0183 data when writing to USB\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB",
"category":"usb port"
"category": "usb port"
},
{
"name": "serialDirection",
"label": "serial direction",
"type": "list",
"default": "receive",
"list": ["send","receive","off"],
"list": [
"send",
"receive",
"off"
],
"description": "use the serial port to send or receive data",
"capabilities":{"serialmode":["UNI"]},
"category":"serial port"
"capabilities": {
"serialmode": [
"UNI"
]
},
"category": "serial port"
},
{
"name": "serialBaud",
@ -99,9 +209,29 @@
"type": "list",
"default": "115200",
"description": "baud rate for the serial port",
"list": [1200,2400,4800,9600,14400,19200,28800,38400,57600,115200,230400,460800],
"capabilities":{"serialmode":["RX","TX","UNI","BI"]},
"category":"serial port"
"list": [
1200,
2400,
4800,
9600,
14400,
19200,
28800,
38400,
57600,
115200,
230400,
460800
],
"capabilities": {
"serialmode": [
"RX",
"TX",
"UNI",
"BI"
]
},
"category": "serial port"
},
{
"name": "sendSerial",
@ -109,8 +239,13 @@
"type": "boolean",
"default": "true",
"description": "send out NMEA data on the serial port",
"capabilities":{"serialmode":["TX","BI"]},
"category":"serial port"
"capabilities": {
"serialmode": [
"TX",
"BI"
]
},
"category": "serial port"
},
{
"name": "receiveSerial",
@ -118,8 +253,13 @@
"type": "boolean",
"default": "true",
"description": "receive NMEA data on the serial port",
"capabilities":{"serialmode":["RX","BI"]},
"category":"serial port"
"capabilities": {
"serialmode": [
"RX",
"BI"
]
},
"category": "serial port"
},
{
"name": "serialToN2k",
@ -127,8 +267,14 @@
"type": "boolean",
"default": "true",
"description": "convert NMEA0183 from the serial port to NMEA2000",
"capabilities":{"serialmode":["RX","BI","UNI"]},
"category":"serial port"
"capabilities": {
"serialmode": [
"RX",
"BI",
"UNI"
]
},
"category": "serial port"
},
{
"name": "serialReadF",
@ -136,8 +282,14 @@
"type": "filter",
"default": "",
"description": "filter for NMEA0183 data when reading from serial\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB",
"capabilities":{"serialmode":["RX","BI","UNI"]},
"category":"serial port"
"capabilities": {
"serialmode": [
"RX",
"BI",
"UNI"
]
},
"category": "serial port"
},
{
"name": "serialWriteF",
@ -145,8 +297,14 @@
"type": "filter",
"default": "",
"description": "filter for NMEA0183 data when writing to serial\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB",
"capabilities":{"serialmode":["TX","BI","UNI"]},
"category":"serial port"
"capabilities": {
"serialmode": [
"TX",
"BI",
"UNI"
]
},
"category": "serial port"
},
{
"name": "serverPort",
@ -154,16 +312,16 @@
"type": "number",
"default": "10110",
"description": "the TCP port we listen on",
"category":"TCP port"
"category": "TCP port"
},
{
"name": "maxClients",
"label": "max. TCP clients",
"type": "number",
"default": "10",
"check":"checkMaxClients",
"check": "checkMaxClients",
"description": "the number of clients we allow to connect to us",
"category":"TCP port"
"category": "TCP port"
},
{
"name": "sendTCP",
@ -171,7 +329,7 @@
"type": "boolean",
"default": "true",
"description": "send out NMEA data to connected TCP clients",
"category":"TCP port"
"category": "TCP port"
},
{
"name": "readTCP",
@ -179,7 +337,7 @@
"type": "boolean",
"default": "true",
"description": "receive NMEA data from connected TCP clients",
"category":"TCP port"
"category": "TCP port"
},
{
"name": "tcpToN2k",
@ -187,7 +345,7 @@
"type": "boolean",
"default": "true",
"description": "convert NMEA0183 from TCP clients to NMEA2000",
"category":"TCP port"
"category": "TCP port"
},
{
"name": "tcpReadFilter",
@ -195,7 +353,7 @@
"type": "filter",
"default": "",
"description": "filter for NMEA0183 data when reading from TCP\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB",
"category":"TCP port"
"category": "TCP port"
},
{
"name": "tcpWriteFilter",
@ -203,7 +361,7 @@
"type": "filter",
"default": "",
"description": "filter for NMEA0183 data when writing to TCP\nselect aison|aisoff, set a whitelist or a blacklist with NMEA sentences like RMC,RMB",
"category":"TCP port"
"category": "TCP port"
},
{
"name": "sendSeasmart",
@ -211,7 +369,7 @@
"type": "boolean",
"default": "false",
"description": "send NMEA2000 as seasmart to connected TCP clients",
"category":"TCP port"
"category": "TCP port"
},
{
"name": "wifiClient",
@ -219,7 +377,7 @@
"type": "boolean",
"default": "false",
"description": "connect to an external WIFI network",
"category":"wifi client"
"category": "wifi client"
},
{
"name": "wifiPass",
@ -227,7 +385,7 @@
"type": "password",
"default": "",
"description": "the password for an external WIFI network",
"category":"wifi client"
"category": "wifi client"
},
{
"name": "wifiSSID",
@ -236,7 +394,7 @@
"default": "",
"check": "checkSSID",
"description": "the SSID for an external WIFI network",
"category":"wifi client"
"category": "wifi client"
},
{
"name": "XDR1",
@ -244,7 +402,7 @@
"type": "xdr",
"default": "",
"check": "checkXDR",
"category":"xdr1"
"category": "xdr1"
},
{
"name": "XDR2",
@ -252,8 +410,230 @@
"type": "xdr",
"default": "",
"check": "checkXDR",
"category":"xdr2"
"category": "xdr2"
},
{
"name": "XDR3",
"label": "XDR3",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr3"
},
{
"name": "XDR4",
"label": "XDR4",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr4"
},
{
"name": "XDR5",
"label": "XDR5",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr5"
},
{
"name": "XDR6",
"label": "XDR6",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr6"
},
{
"name": "XDR7",
"label": "XDR7",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr7"
},
{
"name": "XDR8",
"label": "XDR8",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr8"
},
{
"name": "XDR9",
"label": "XDR9",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr9"
},
{
"name": "XDR10",
"label": "XDR10",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr10"
},
{
"name": "XDR11",
"label": "XDR11",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr11"
},
{
"name": "XDR12",
"label": "XDR12",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr12"
},
{
"name": "XDR13",
"label": "XDR13",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr13"
},
{
"name": "XDR14",
"label": "XDR14",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr14"
},
{
"name": "XDR15",
"label": "XDR15",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr15"
},
{
"name": "XDR16",
"label": "XDR16",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr16"
},
{
"name": "XDR17",
"label": "XDR17",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr17"
},
{
"name": "XDR18",
"label": "XDR18",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr18"
},
{
"name": "XDR19",
"label": "XDR19",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr19"
},
{
"name": "XDR20",
"label": "XDR20",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr20"
},
{
"name": "XDR21",
"label": "XDR21",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr21"
},
{
"name": "XDR22",
"label": "XDR22",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr22"
},
{
"name": "XDR23",
"label": "XDR23",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr23"
},
{
"name": "XDR24",
"label": "XDR24",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr24"
},
{
"name": "XDR25",
"label": "XDR25",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr25"
},
{
"name": "XDR26",
"label": "XDR26",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr26"
},
{
"name": "XDR27",
"label": "XDR27",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr27"
},
{
"name": "XDR28",
"label": "XDR28",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr28"
},
{
"name": "XDR29",
"label": "XDR29",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr29"
},
{
"name": "XDR30",
"label": "XDR30",
"type": "xdr",
"default": "",
"check": "checkXDR",
"category": "xdr30"
}
]

View File

@ -146,7 +146,15 @@ body{
.xdrexample {
max-width: 16em;
overflow: auto;
}
}
.uploadXdr{
width: 1px;
height: 1px;
opacity: 0;
}
button.addunassigned {
margin-left: 1em;
}
.msgDetails .value {
width: 5em;
text-align: right;
@ -162,6 +170,7 @@ body{
right: 0;
background-color: #80808070;
display: flex;
overflow-y: auto;
}
div#overlay {

View File

@ -65,6 +65,8 @@
<button id="resetForm">ReloadConfig</button>
<button id="changeConfig">Save&Restart</button>
<button id="loadUnassigned">Show Unmapped</button>
<button id="exportXdr">Export</button>
<button id="importXdr">Import</button>
</div>
</div>
<div class="tabPage hidden" id="dashboardPage">
@ -82,6 +84,40 @@
</div>
</div>
</div>
<div id="xdrHelp" class="hidden">
<h1>XDR Help</h1>
<p>You can configure the mapping of various NMEA2000 entities to XDR records.</p>
<p>To set up such a mapping you select the category, the source of data
(if the category has different sources) and the field (if the category has multiple fields).</p>
<p>You have to provide the name of the transducer for the XDR record.</p>
<p>Many of the NMEA2000 messages have an instance id (0..255) to allow
for different sources of the data. You need to decide how do you want to
map such IDs to XDR records.
</p>
<ul>
<li>IGNORE: the instance id will be ignored</li>
<li>AUTO: the instance will be appended to the transducer name: #123</li>
<li>SINGLE: just map exactlly one instance to this transducer name</li>
</ul>
<p>You can also decide if you want a both way mapping, i.e. also map
received XDR records back to NMEA 2000.
</p>
<p>Once you create a mapping the system will show an example of the
generated XDR records (and the accepted ones).
</p>
<p>To ease the set up the system will track received NMEA2000 data
that it currently does not map.
With clicking <span class="txtShowUnmapped">"Show Unmapped"</span> you bring up such a list and for each
found mapping there is a "+" button that will allow you to create a mapping
for this record.
</p>
<p>The set of handled PGNs that can be mapped to XDR records can be found
in the documentation.
</p>
</div>
<div class="hidden">
<a id="downloadXdr"></a>
</div>
</body>
</html>

View File

@ -110,9 +110,39 @@ function checkApPass(v) {
return "password must be at least 8 characters";
}
}
function checkXDR(v,allValues){
if (! v) return;
let parts=v.split(',');
if (parseInt(parts[1]) == 0) return;
if (parseInt(parts[1]) != 0 && ! parts[6]) return "missing transducer name";
for (let k in allValues){
if (! k.match(/^XDR/)) continue;
let cmp=allValues[k];
if (cmp == v){
return "same mapping already defined in "+k;
}
let cmpParts=cmp.split(',');
if (parseInt(cmpParts[1]) != 0 && parts[6] == cmpParts[6]){
return "transducer "+parts[6]+" already defined in "+k;
}
//check similar mappings
if (parts[0]==cmpParts[0] && parts[2] == cmpParts[2] && parts[3] == cmpParts[3]){
if (parts[4] == cmpParts[4] && parts[5] == cmpParts[5]){
return "mapping for the same entity already defined in "+k;
}
if ((parseInt(parts[4]) == 0 && parseInt(cmpParts[4]) == 1)||
(parseInt(parts[4]) == 1 && parseInt(cmpParts[4]) == 0)
){
//ignore and single for the same mapping
return "mapping for the same entity already defined in "+k;
}
}
}
}
function changeConfig() {
let url = "/api/setConfig?";
let values = document.querySelectorAll('.configForm select , .configForm input');
let allValues={};
for (let i = 0; i < values.length; i++) {
let v = values[i];
let name = v.getAttribute('name');
@ -121,7 +151,7 @@ function changeConfig() {
let check = v.getAttribute('data-check');
if (check) {
if (typeof (self[check]) === 'function') {
let res = self[check](v.value);
let res = self[check](v.value,allValues);
if (res) {
let value = v.value;
if (v.type === 'password') value = "******";
@ -130,6 +160,7 @@ function changeConfig() {
}
}
}
allValues[name]=v.value;
url += name + "=" + encodeURIComponent(v.value) + "&";
}
getJson(url)
@ -236,8 +267,8 @@ function checkChange(el, row) {
}
}
}
let configDefinitions;
let xdrConfig;
let configDefinitions={};
let xdrConfig={};
function createInput(configItem, frame,clazz) {
let el;
if (configItem.type === 'boolean' || configItem.type === 'list') {
@ -295,9 +326,11 @@ function createInput(configItem, frame,clazz) {
function updateSelectList(item,slist){
item.innerHTML='';
let idx=0;
slist.forEach(function (sitem) {
let sitemEl = addEl('option','',item,sitem.l);
sitemEl.setAttribute('value', sitem.v);
sitemEl.setAttribute('value', sitem.v !== undefined?sitem.v:idx);
idx++;
})
}
function getXdrCategories(){
@ -309,25 +342,39 @@ function getXdrCategories(){
}
return rt;
}
function getXdrSelectors(category){
category=parseInt(category);
function getXdrCategoryName(cid){
category=parseInt(cid);
for (let c in xdrConfig){
let base=xdrConfig[c];
if (parseInt(base.id) == category){
return base.selector || [];
return c;
}
}
return [];
}
function getXdrCategory(cid){
category=parseInt(cid);
for (let c in xdrConfig){
let base=xdrConfig[c];
if (parseInt(base.id) == category){
return base;
}
}
return {};
}
function getXdrSelectors(category){
let base=getXdrCategory(category);
return base.selector || [];
}
function getXdrFields(category){
category=parseInt(category);
for (let c in xdrConfig){
let base=xdrConfig[c];
if (parseInt(base.id) == category){
return base.fields || [];
}
}
return [];
let base=getXdrCategory(category);
if (!base.fields) return [];
let rt=[];
base.fields.forEach(function(f){
if (f.t === undefined) return;
if (parseInt(f.t) == 99) return; //unknown type
rt.push(f);
});
return rt;
}
function createXdrLine(parent,label){
@ -421,10 +468,16 @@ function createXdrInput(configItem,frame){
if (used) {
el.classList.add('xdrcused');
el.classList.remove('xdrcunused');
forEl('.categoryAdd',function(add){
add.textContent=xdrName.value;
},el);
}
else {
el.classList.remove('xdrcused');
el.classList.add('xdrcunused');
forEl('.categoryAdd',function(add){
add.textContent='';
},el);
}
if (modified){
el.classList.add('changed');
@ -443,7 +496,12 @@ function createXdrInput(configItem,frame){
example.textContent='';
}
}
let updateFunction = function () {
let updateFunction = function (evt) {
if (evt.target == category){
selector.value=0;
field.value=0;
instance.value=0;
}
let txt=category.value+","+direction.value+","+
selector.value+","+field.value+","+imode.value;
let instanceValue=parseInt(instance.value||0);
@ -471,7 +529,8 @@ function createXdrInput(configItem,frame){
function isXdrUsed(element){
let parts=element.value.split(',');
if (! parts[0]) return false;
if (! parts[1]) return false;
if (! parseInt(parts[1])) return false;
if (! parts[6]) return false;
return true;
}
@ -520,6 +579,194 @@ function createFilterInput(configItem, frame) {
data.setAttribute('name', configItem.name);
return data;
}
let moreicons=['icon-more','icon-less'];
function collapseCategories(parent,expand){
let doExpand=expand;
forEl('.category',function(cat){
if (typeof(expand) === 'function') doExpand=expand(cat);
forEl('.content',function(content){
if (doExpand){
content.classList.remove('hidden');
}
else{
content.classList.add('hidden');
}
},cat);
forEl('.title .icon',function(icon){
toggleClass(icon,doExpand?1:0,moreicons);
},cat);
},parent);
}
function findFreeXdr(data){
let page=document.getElementById('xdrPage');
let el=undefined;
collapseCategories(page,function(cat){
if (el) return false;
let vEl=cat.querySelector('.xdrvalue');
if (!vEl) return false;
if (isXdrUsed(vEl)) return false;
el=vEl;
if (data){
el.value=data;
let ev=new Event('change');
el.dispatchEvent(ev);
window.setTimeout(function(){
cat.scrollIntoView();
},50);
}
return true;
});
}
function convertUnassigned(value){
let rt={};
value=parseInt(value);
if (isNaN(value)) return;
//see GwXDRMappings::addUnknown
let instance=value & 0x1ff;
value = value >> 9;
let field=value & 0x7f;
value = value >> 7;
let selector=value & 0x7f;
value = value >> 7;
let cid=value & 0x7f;
let category=getXdrCategory(cid);
let cname=getXdrCategoryName(cid);
if (! cname) return rt;
let fieldName="";
(category.fields || []).forEach(function(f){
if (parseInt(f.v) == field) fieldName=f.l;
});
let selectorName=selector+"";
(category.selector ||[]).forEach(function(s){
if (parseInt(s.v) == selector) selectorName=s.l;
});
rt.l=cname+","+selectorName+","+fieldName+","+instance;
rt.v=cid+",1,"+selector+","+field+",1,"+instance+",";
return rt;
}
function unassignedAdd(ev) {
let dv = ev.target.getAttribute('data-value');
if (dv) {
findFreeXdr(dv);
hideOverlay();
}
}
function loadUnassigned(){
getText("/api/xdrUnmapped")
.then(function(txt){
let ot="";
txt.split('\n').forEach(function(line){
let cv=convertUnassigned(line);
if (!cv || !cv.l) return;
ot+='<div class="xdrunassigned"><span>'+
cv.l+'</span>'+
'<button class="addunassigned" data-value="'+
cv.v+
'">+</button></div>';
})
showOverlay(ot,true);
forEl('.addunassigned',function(bt){
bt.onclick=unassignedAdd;
});
})
}
function showXdrHelp(){
let helpContent=document.getElementById('xdrHelp');
if (helpContent){
showOverlay(helpContent.innerHTML,true);
}
}
function formatDate(d){
if (! d) d=new Date();
let rt=""+d.getFullYear();
let v=d.getMonth();
if (v < 10) rt+="0"+v;
else rt+=v;
v=d.getDate();
if (v < 10) rt+="0"+v;
else rt+=v;
return rt;
}
function exportXdr(){
let data={};
forEl('.xdrvalue',function(el) {
let name=el.getAttribute('name');
let value=el.value;
let err=checkXDR(value,data);
if (err){
alert("error in "+name+": "+value+"\n"+err);
return;
}
data[name]=value;
})
let url="data:application/octet-stream,"+encodeURIComponent(JSON.stringify(data,undefined,2));
let target=document.getElementById('downloadXdr');
if (! target) return;
target.setAttribute('href',url);
target.setAttribute('download',"xdr"+formatDate()+".json");
target.click();
}
function importXdr(){
forEl('.uploadXdr',function(ul){
ul.remove();
});
let ip=addEl('input','uploadXdr',document.body);
ip.setAttribute('type','file');
ip.addEventListener('change',function(ev){
if (ip.files.length > 0){
let f=ip.files[0];
let reader=new FileReader();
reader.onloadend=function(status){
try{
let idata=JSON.parse(reader.result);
let hasOverwrites=false;
for (let k in idata){
if (! k.match(/^XDR[0-9][0-9]*/)){
alert("file contains invalid key "+k);
return;
}
let del=document.querySelector('input[name='+k+']');
if (del){
hasOverwrites=true;
}
}
if (hasOverwrites){
if (!confirm("overwrite existing data?")) return;
}
for (let k in idata){
let del=document.querySelector('input[name='+k+']');
if (del){
del.value=idata[k];
let ev=new Event('change');
del.dispatchEvent(ev);
}
}
}catch (error){
alert("unable to parse upload: "+error);
return;
}
};
reader.readAsBinaryString(f);
}
ip.remove();
});
ip.click();
}
function toggleClass(el,id,classList){
let nc=classList[id];
let rt=false;
if (nc && !el.classList.contains(nc)) rt=true;
for (let i in classList){
if (i == id) continue;
el.classList.remove(classList[i])
}
if (nc) el.classList.add(nc);
return rt;
}
function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
let category;
@ -542,18 +789,17 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
let categoryTitle = addEl('div', 'title', categoryFrame);
let categoryButton = addEl('span', 'icon icon-more', categoryTitle);
addEl('span', 'label', categoryTitle, item.category);
addEl('span','categoryAdd',categoryTitle);
categoryEl = addEl('div', 'content', categoryFrame);
categoryEl.classList.add('hidden');
let currentEl = categoryEl;
categoryTitle.addEventListener('click', function (ev) {
let rs = currentEl.classList.toggle('hidden');
if (rs) {
categoryButton.classList.add('icon-more');
categoryButton.classList.remove('icon-less');
toggleClass(categoryButton,0,moreicons);
}
else {
categoryButton.classList.remove('icon-more');
categoryButton.classList.add('icon-less');
toggleClass(categoryButton,1,moreicons);
}
})
category = item.category;
@ -591,7 +837,14 @@ function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
})
bt = addEl('button', 'infoButton', btContainer, '?');
bt.addEventListener('click', function (ev) {
showOverlay(item.description);
if (item.description){
showOverlay(item.description);
}
else{
if (item.category.match(/^xdr/)){
showXdrHelp();
}
}
});
})
}