esp32-nmea2000-obp60/lib/xdrmappings/GwXDRMappings.cpp

461 lines
14 KiB
C++

#include "GwXDRMappings.h"
#include "N2kMessages.h"
double PtoBar(double v)
{
if (N2kIsNA(v))
return v;
return v / 100000L;
}
double BarToP(double v)
{
if (N2kIsNA(v))
return v;
return v * 100000L;
}
double ltrTom3(double v)
{
if (N2kIsNA(v))
return v;
return v / 1000.0;
}
double m3ToL(double v)
{
if (N2kIsNA(v))
return v;
return v * 1000.0;
}
double ph2ps(double v)
{
if (N2kIsNA(v))
return v;
return v * 3600.0;
}
double ps2ph(double v)
{
if (N2kIsNA(v))
return v;
return v / 3600.0;
}
//https://www.nmea.org/Assets/20190916%20Standards%20Update%20NMEA%20Conferencev2.pdf
GwXDRType *types[] = {
new GwXDRType(GwXDRType::PRESS, "P", "P"),
new GwXDRType(GwXDRType::PRESS, "P", "B",
BarToP,
PtoBar,
"P"),
new GwXDRType(GwXDRType::VOLT, "U", "V"),
new GwXDRType(GwXDRType::AMP, "I", "A"),
new GwXDRType(GwXDRType::TEMP, "C", "K"),
new GwXDRType(GwXDRType::TEMP, "C", "C", CToKelvin, KelvinToC,"K"),
new GwXDRType(GwXDRType::HUMID, "H", "P"), //percent
new GwXDRType(GwXDRType::VOLPERCENT, "V", "P"),
new GwXDRType(GwXDRType::VOLUME, "V", "M", m3ToL, ltrTom3,"L"),
new GwXDRType(GwXDRType::FLOW, "R", "I", ps2ph, ph2ps),
new GwXDRType(GwXDRType::GENERIC, "G", ""),
new GwXDRType(GwXDRType::DISPLACEMENT, "A", "P"),
new GwXDRType(GwXDRType::DISPLACEMENTD, "A", "D",DegToRad,RadToDeg,"rd"),
new GwXDRType(GwXDRType::RPM,"T","R"),
//important to have 2x NULL!
NULL,
NULL};
static GwXDRType *findType(GwXDRType::TypeCode type, int *start = NULL)
{
int from = 0;
if (start != NULL)
from = *start;
if (types[from] == NULL)
return NULL;
int i = from;
for (; types[i] != NULL; i++)
{
if (types[i]->code == type)
{
if (start != NULL)
*start = i + 1;
return types[i];
}
}
if (start != NULL)
*start = i;
return NULL;
}
#include "GwXdrTypeMappings.h"
static GwXDRType::TypeCode findTypeMapping(GwXDRCategory category, int field)
{
for (int i = 0; typeMappings[i] != NULL; i++)
{
if (typeMappings[i]->fieldIndex == field &&
typeMappings[i]->category == category)
{
return typeMappings[i]->type;
}
}
return GwXDRType::UNKNOWN;
}
//category,direction,selector,field,instanceMode,instance,name
String GwXDRMappingDef::toString()
{
String rt = "";
rt += String((int)category);
rt += ",";
rt += String((int)direction);
rt += ",";
rt += String(selector);
rt += ",";
rt += String(field);
rt += ",";
rt += String(instanceMode);
rt += ",";
rt += String(instanceId);
rt += ",";
rt += xdrName;
return rt;
}
bool GwXDRMappingDef::handleToken(String tok, int index, GwXDRMappingDef *def)
{
switch (index)
{
int i;
case 0:
//category
i = tok.toInt();
if (i < XDRTEMP || i > XDRATTITUDE)
return false;
def->category = (GwXDRCategory)i;
return true;
case 1:
//direction
i = tok.toInt();
if (i < GwXDRMappingDef::M_DISABLED ||
i >= GwXDRMappingDef::M_LAST)
return false;
def->direction = (GwXDRMappingDef::Direction)i;
return true;
case 2:
//selector
//TODO: check selector?
i = tok.toInt();
def->selector = i;
return true;
case 3:
//field
i = tok.toInt();
def->field = i;
return true;
case 4:
//instance mode
i = tok.toInt();
if (i < GwXDRMappingDef::IS_SINGLE ||
i >= GwXDRMappingDef::IS_LAST)
return false;
def->instanceMode = (GwXDRMappingDef::InstanceMode)i;
return true;
case 5:
//instance id
i = tok.toInt();
def->instanceId = i;
return true;
case 6:
def->xdrName=tok;
return true;
default:
break;
}
return true; //ignore unknown trailing stuff
}
GwXDRMappingDef *GwXDRMappingDef::fromString(String s)
{
int found = 0;
int last = 0;
int index = 0;
GwXDRMappingDef *rt = new GwXDRMappingDef();
while ((found = s.indexOf(',', last)) >= 0)
{
String tok = s.substring(last, found);
if (!handleToken(tok, index, rt))
{
delete rt;
return NULL;
}
last = found + 1;
index++;
}
if (last < s.length())
{
String tok = s.substring(last);
if (!handleToken(tok, index, rt))
{
delete rt;
return NULL;
}
}
if (rt->direction == GwXDRMappingDef::M_DISABLED || rt->xdrName == ""){
delete rt;
return NULL;
}
return rt;
}
String GwXDRMappingDef::getTransducerName(int instance)
{
String name = xdrName;
if (instanceMode == GwXDRMappingDef::IS_AUTO)
{
name += "#";
name += String(instance);
}
return name;
}
GwXDRFoundMapping::XdrEntry GwXDRFoundMapping::buildXdrEntry(double value)
{
char buffer[40];
XdrEntry rt;
rt.transducer = getTransducerName();
if (type->tonmea)
{
value = (*(type->tonmea))(value);
}
snprintf(buffer, 39, "%s,%.3f,%s,%s",
type->xdrtype.c_str(),
value,
type->xdrunit.c_str(),
rt.transducer.c_str());
buffer[39] = 0;
rt.entry=String(buffer);
return rt;
}
GwXDRMappings::GwXDRMappings(GwLog *logger, GwConfigHandler *config)
{
this->logger = logger;
this->config = config;
}
#define MAX_MAPPINGS 100
void GwXDRMappings::begin()
{
char namebuf[10];
/*
build our mappings
for each defined mapping we fetch the type code and type definition
and create a mapping entries in our 2 maps:
n2kmap: map category,selector and field index to a mapping
n183map: map transducer name, transducer type and transducer unit to a mapping
a #nnn will be stripped from the transducer name
entries in the map are lists of mappings as potentially
we have different mappings for different instances
*/
for (int i = 0; i < MAX_MAPPINGS; i++)
{
snprintf(namebuf, 9, "XDR%d", i);
namebuf[9] = 0;
GwConfigInterface *cfg = config->getConfigItem(String(namebuf));
if (cfg)
{
GwXDRMappingDef *def = GwXDRMappingDef::fromString(cfg->asCString());
if (def)
{
int typeIndex = 0;
LOG_DEBUG(GwLog::DEBUG, "read xdr mapping %s from %s",
def->toString().c_str(),namebuf);
//n2k: find first matching type mapping
GwXDRType::TypeCode code = findTypeMapping(def->category, def->field);
if (code == GwXDRType::UNKNOWN)
{
LOG_DEBUG(GwLog::DEBUG, "no type mapping for %s", def->toString().c_str());
continue;
}
GwXDRType *type = findType(code, &typeIndex);
if (!type)
{
LOG_DEBUG(GwLog::DEBUG, "no type definition for %s", def->toString().c_str());
continue;
}
long n2kkey = def->n2kKey();
auto it = n2kMap.find(n2kkey);
GwXDRMapping *mapping = new GwXDRMapping(def, type);
if (it == n2kMap.end())
{
LOG_DEBUG(GwLog::DEBUG, "insert mapping with key %ld", n2kkey);
GwXDRMapping::MappingList mappings;
mappings.push_back(mapping);
n2kMap[n2kkey] = mappings;
}
else
{
LOG_DEBUG(GwLog::DEBUG, "append mapping with key %ld", n2kkey);
it->second.push_back(mapping);
}
//for nmea0183 there could be multiple entries
//as potentially there are different units that we can handle
//so after we inserted the definition we do additional type lookups
while (type != NULL)
{
String n183key = GwXDRMappingDef::n183key(def->xdrName,
type->xdrtype, type->xdrunit);
auto it = n183Map.find(n183key);
if (it == n183Map.end())
{
LOG_DEBUG(GwLog::DEBUG, "insert mapping with n183key %s", n183key.c_str());
GwXDRMapping::MappingList mappings;
mappings.push_back(mapping);
n183Map[n183key] = mappings;
}
else
{
LOG_DEBUG(GwLog::DEBUG, "append mapping with n183key %s", n183key.c_str());
it->second.push_back(mapping);
}
type = findType(code, &typeIndex);
if (!type)
break;
mapping=new GwXDRMapping(def,type);
}
}
else{
LOG_DEBUG(GwLog::DEBUG,"unable to parse mapping %s",cfg->asCString());
}
}
}
}
/**
* select the best matching mapping
* depending on the instance id
*/
GwXDRFoundMapping GwXDRMappings::selectMapping(GwXDRMapping::MappingList *list, int instance, const char *key)
{
GwXDRMapping *candidate = NULL;
for (auto mit = list->begin(); mit != list->end(); mit++)
{
GwXDRMappingDef *def = (*mit)->definition;
//if there is no instance (coming from xdr with no instance) we take a mapping with instance type ignore
//or the first explicit mapping
//for coming from xdr we only should have one mapping in the list
//but auto will not match for unknwn instance
switch (def->instanceMode)
{
case GwXDRMappingDef::IS_SINGLE:
if (def->instanceId == instance)
{
LOG_DEBUG(GwLog::DEBUG + 1, "selected mapping %s for %s, i=%d",
def->toString().c_str(), key, instance);
return GwXDRFoundMapping(*mit, instance);
}
if (instance < 0)
{
if (candidate == NULL)
candidate = *mit;
}
case GwXDRMappingDef::IS_AUTO:
if (instance >= 0)
candidate = *mit;
break;
case GwXDRMappingDef::IS_IGNORE:
if (candidate == NULL)
candidate = *mit;
break;
default:
break;
}
}
if (candidate != NULL)
{
LOG_DEBUG(GwLog::DEBUG + 1, "selected mapping %s for %s, i=%d",
candidate->definition->toString().c_str(), key, instance);
return GwXDRFoundMapping(candidate, instance>=0?instance:candidate->definition->instanceId);
}
LOG_DEBUG(GwLog::DEBUG + 1, "no instance mapping found for key=%s, i=%d", key, instance);
return GwXDRFoundMapping();
}
GwXDRFoundMapping GwXDRMappings::getMapping(String xName,String xType,String xUnit){
//get an instance suffix from the name and separate it
int sepIdx=xName.indexOf('#');
int instance=-1;
if (sepIdx>=0){
String istr=xName.substring(sepIdx+1);
xName=xName.substring(0,sepIdx);
instance=istr.toInt();
}
if (xName == "") return GwXDRFoundMapping();
String n183Key=GwXDRMappingDef::n183key(xName,xType,xUnit);
auto it=n183Map.find(n183Key);
if (it == n183Map.end()) {
LOG_DEBUG(GwLog::DEBUG+1,"find n183mapping for %s,i=%d - nothing found",n183Key.c_str(),instance);
return GwXDRFoundMapping();
}
return selectMapping(&(it->second),instance,n183Key.c_str());
}
GwXDRFoundMapping GwXDRMappings::getMapping(GwXDRCategory category,int selector,int field,int instance){
unsigned long n2kKey=GwXDRMappingDef::n2kKey(category,selector,field);
auto it=n2kMap.find(n2kKey);
if (it == n2kMap.end()){
LOG_DEBUG(GwLog::DEBUG+1,"find n2kmapping for c=%d,s=%d,f=%d,i=%d - nothing found",
(int)category,selector,field,instance);
addUnknown(category,selector,field,instance);
return GwXDRFoundMapping();
}
char kbuf[20];
snprintf(kbuf,19,"%lu",n2kKey);
kbuf[19]=0;
GwXDRFoundMapping rt=selectMapping(&(it->second),instance,kbuf);
if (rt.empty){
addUnknown(category,selector,field,instance);
}
return rt;
}
bool GwXDRMappings::addUnknown(GwXDRCategory category,int selector,int field,int instance){
if (unknown.size() >= 200) return false;
unsigned long uk=((int)category) &0x7f;
if (selector < 0) selector=0;
uk=(uk<<7) + (selector &0x7f);
if (field < 0) field=0;
uk=(uk<<7) + (field & 0x7f);
if (instance < 0 || instance > 255) instance=256; //unknown
uk=(uk << 9) + (instance & 0x1ff);
unknown.insert(uk);
return true;
}
const char * GwXDRMappings::getUnMapped(){
if (unknowAsString == NULL) unknowAsString=new char[(MAX_MAPPINGS+1)*ESIZE];
*unknowAsString=0;
char *ptr=unknowAsString;
for (auto it=unknown.begin();it!=unknown.end();it++){
snprintf(ptr,ESIZE-1,"%lu\n",*it);
*(ptr+ESIZE-1)=0;
while (*ptr != 0) ptr++;
}
return unknowAsString;
}
String GwXDRMappings::getXdrEntry(String mapping, double value,int instance){
String rt;
GwXDRMappingDef *def=GwXDRMappingDef::fromString(mapping);
if (! def) return rt;
int typeIndex=0;
GwXDRType::TypeCode code = findTypeMapping(def->category, def->field);
if (code == GwXDRType::UNKNOWN)
{
return rt;
}
GwXDRType *type = findType(code, &typeIndex);
bool first=true;
while (type){
GwXDRFoundMapping found(def,type);
found.instanceId=instance;
if (first) first=false;
else rt+=",";
rt+=found.buildXdrEntry(value).entry;
type = findType(code, &typeIndex);
}
delete def;
return rt;
}