diff --git a/lib/boatData/GwBoatData.cpp b/lib/boatData/GwBoatData.cpp index e05e0a4..533f931 100644 --- a/lib/boatData/GwBoatData.cpp +++ b/lib/boatData/GwBoatData.cpp @@ -10,6 +10,16 @@ GwBoatData::~GwBoatData(){ } } +template GwBoatItem *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(name,format,invalidTime,&values); +} String GwBoatData::toJson() const { unsigned long minTime=millis(); GwBoatItemBase::GwBoatItemMap::const_iterator it; diff --git a/lib/boatData/GwBoatData.h b/lib/boatData/GwBoatData.h index f400241..d2e0db3 100644 --- a/lib/boatData/GwBoatData.h +++ b/lib/boatData/GwBoatData.h @@ -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 GwBoatItem : public GwBoatItemBase{ @@ -253,8 +254,43 @@ class GwBoatData{ public: GwBoatData(GwLog *logger); ~GwBoatData(); + template GwBoatItem *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 GwBoatItemHolder{ + private: + GwBoatItem *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 *getItem(GwBoatItemNameProvider *provider){ + if (item) return item; + T dummy; + item=data->getOrCreate(dummy, + provider->getBoatItemName() , + provider->getBoatItemFormat(), + invalidTime); + return item; + } + GwBoatItem *getItem(){return item;} +}; #endif \ No newline at end of file diff --git a/lib/xdrmappings/GwXDRMappings.h b/lib/xdrmappings/GwXDRMappings.h index e257953..8dffacc 100644 --- a/lib/xdrmappings/GwXDRMappings.h +++ b/lib/xdrmappings/GwXDRMappings.h @@ -2,6 +2,7 @@ #define _GWXDRMAPPINGS_H #include "GwLog.h" #include "GWConfig.h" +#include "GwBoatData.h" #include #include #include @@ -141,7 +142,7 @@ class GwXDRMapping{ typedef std::map N138Map; typedef std::map 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 diff --git a/tools/genXdrCfg.sh b/tools/genXdrCfg.sh new file mode 100755 index 0000000..be3b738 --- /dev/null +++ b/tools/genXdrCfg.sh @@ -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 \ No newline at end of file diff --git a/web/config.json b/web/config.json index 5d8d930..1b4294d 100644 --- a/web/config.json +++ b/web/config.json @@ -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" } - - ] \ No newline at end of file diff --git a/web/index.css b/web/index.css index 6829bfe..67fd23a 100644 --- a/web/index.css +++ b/web/index.css @@ -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 { diff --git a/web/index.html b/web/index.html index abe2a4c..6c3f44a 100644 --- a/web/index.html +++ b/web/index.html @@ -65,6 +65,8 @@ + + + + diff --git a/web/index.js b/web/index.js index d260373..0f67327 100644 --- a/web/index.js +++ b/web/index.js @@ -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+='
'+ + cv.l+''+ + '
'; + }) + 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(); + } + } }); }) }