532 lines
14 KiB
C++
532 lines
14 KiB
C++
#include "GwBoatData.h"
|
|
#include <GwJsonDocument.h>
|
|
#include <ArduinoJson/Json/TextFormatter.hpp>
|
|
#include "GWConfig.h"
|
|
#define GWTYPE_DOUBLE 1
|
|
#define GWTYPE_UINT32 2
|
|
#define GWTYPE_UINT16 3
|
|
#define GWTYPE_INT16 4
|
|
#define GWTYPE_USER 100
|
|
|
|
class GwBoatItemTypes
|
|
{
|
|
public:
|
|
static int getType(const uint32_t &x) { return GWTYPE_UINT32; }
|
|
static int getType(const uint16_t &x) { return GWTYPE_UINT16; }
|
|
static int getType(const int16_t &x) { return GWTYPE_INT16; }
|
|
static int getType(const double &x) { return GWTYPE_DOUBLE; }
|
|
static int getType(const GwSatInfoList &x) { return GWTYPE_USER + 1; }
|
|
};
|
|
|
|
bool GwBoatItemBase::isValid(unsigned long now) const
|
|
{
|
|
if (lastSet == 0)
|
|
return false;
|
|
if (invalidTime == 0)
|
|
return true;
|
|
if (now == 0)
|
|
now = millis();
|
|
return (lastSet + invalidTime) >= now;
|
|
}
|
|
GwBoatItemBase::GwBoatItemBase(String name, String format, unsigned long invalidTime)
|
|
{
|
|
lastSet = 0;
|
|
this->invalidTime = invalidTime;
|
|
this->name = name;
|
|
this->format = format;
|
|
this->type = 0;
|
|
this->lastUpdateSource = -1;
|
|
this->toType=TOType::user;
|
|
}
|
|
GwBoatItemBase::GwBoatItemBase(String name, String format, GwBoatItemBase::TOType toType)
|
|
{
|
|
lastSet = 0;
|
|
this->invalidTime = INVALID_TIME;
|
|
this->toType=toType;
|
|
this->name = name;
|
|
this->format = format;
|
|
this->type = 0;
|
|
this->lastUpdateSource = -1;
|
|
}
|
|
void GwBoatItemBase::setInvalidTime(GwConfigHandler *cfg){
|
|
if (toType != TOType::user){
|
|
unsigned long timeout=GwBoatItemBase::INVALID_TIME;
|
|
switch(getToType()){
|
|
case GwBoatItemBase::TOType::ais:
|
|
timeout=cfg->getInt(GwConfigDefinitions::timoAis);
|
|
break;
|
|
case GwBoatItemBase::TOType::def:
|
|
timeout=cfg->getInt(GwConfigDefinitions::timoDefault);
|
|
break;
|
|
case GwBoatItemBase::TOType::lng:
|
|
timeout=cfg->getInt(GwConfigDefinitions::timoLong);
|
|
break;
|
|
case GwBoatItemBase::TOType::sensor:
|
|
timeout=cfg->getInt(GwConfigDefinitions::timoSensor);
|
|
break;
|
|
case GwBoatItemBase::TOType::keep:
|
|
timeout=0;
|
|
break;
|
|
}
|
|
invalidTime=timeout;
|
|
}
|
|
}
|
|
size_t GwBoatItemBase::getJsonSize() { return JSON_OBJECT_SIZE(10); }
|
|
|
|
void GwBoatItemBase::GwBoatItemMap::add(const String &name,GwBoatItemBase *item){
|
|
boatData->setInvalidTime(item);
|
|
(*this)[name]=item;
|
|
}
|
|
|
|
#define STRING_SIZE 40
|
|
GwBoatItemBase::StringWriter::StringWriter()
|
|
{
|
|
buffer = new uint8_t[STRING_SIZE];
|
|
wp = buffer;
|
|
bufferSize = STRING_SIZE;
|
|
buffer[0] = 0;
|
|
};
|
|
const char *GwBoatItemBase::StringWriter::c_str() const
|
|
{
|
|
return (const char *)buffer;
|
|
}
|
|
int GwBoatItemBase::StringWriter::getSize() const
|
|
{
|
|
int rt=wp - buffer;
|
|
if (rt >= baseOffset) rt-=baseOffset;
|
|
return rt;
|
|
}
|
|
void GwBoatItemBase::StringWriter::setBase()
|
|
{
|
|
baseOffset = wp - buffer;
|
|
}
|
|
bool GwBoatItemBase::StringWriter::baseFilled()
|
|
{
|
|
return baseOffset != 0;
|
|
}
|
|
void GwBoatItemBase::StringWriter::reset()
|
|
{
|
|
wp = buffer + baseOffset;
|
|
*wp = 0;
|
|
}
|
|
void GwBoatItemBase::StringWriter::ensure(size_t size)
|
|
{
|
|
size_t fill = wp - buffer;
|
|
size_t newSize = bufferSize;
|
|
while ((fill + size) >= (newSize - 1))
|
|
{
|
|
newSize += STRING_SIZE;
|
|
}
|
|
if (newSize != bufferSize)
|
|
{
|
|
uint8_t *newBuffer = new uint8_t[newSize];
|
|
memcpy(newBuffer, buffer, fill);
|
|
newBuffer[fill] = 0;
|
|
delete buffer;
|
|
buffer = newBuffer;
|
|
wp = newBuffer + fill;
|
|
bufferSize = newSize;
|
|
}
|
|
}
|
|
size_t GwBoatItemBase::StringWriter::write(uint8_t c)
|
|
{
|
|
ensure(1);
|
|
*wp = c;
|
|
wp++;
|
|
*wp = 0;
|
|
return 1;
|
|
}
|
|
size_t GwBoatItemBase::StringWriter::write(const uint8_t *s, size_t n)
|
|
{
|
|
ensure(n);
|
|
memcpy(wp, s, n);
|
|
wp += n;
|
|
*wp = 0;
|
|
return n;
|
|
}
|
|
template <class T>
|
|
GwBoatItem<T>::GwBoatItem(String name, String formatInfo, unsigned long invalidTime, GwBoatItemMap *map) : GwBoatItemBase(name, formatInfo, invalidTime)
|
|
{
|
|
T dummy;
|
|
this->type = GwBoatItemTypes::getType(dummy);
|
|
if (map)
|
|
{
|
|
map->add(name,this);
|
|
}
|
|
}
|
|
template <class T>
|
|
GwBoatItem<T>::GwBoatItem(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map) : GwBoatItemBase(name, formatInfo, toType)
|
|
{
|
|
T dummy;
|
|
this->type = GwBoatItemTypes::getType(dummy);
|
|
if (map)
|
|
{
|
|
map->add(name,this);
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
bool GwBoatItem<T>::update(T nv, int source)
|
|
{
|
|
unsigned long now = millis();
|
|
if (isValid(now))
|
|
{
|
|
//priority handling
|
|
//sources with lower ids will win
|
|
//and we will not overwrite their value
|
|
if (lastUpdateSource < source && lastUpdateSource >= 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
data = nv;
|
|
lastUpdateSource = source;
|
|
uls(now);
|
|
return true;
|
|
}
|
|
template <class T>
|
|
bool GwBoatItem<T>::updateMax(T nv, int sourceId)
|
|
{
|
|
unsigned long now = millis();
|
|
if (!isValid(now))
|
|
{
|
|
return update(nv, sourceId);
|
|
}
|
|
if (getData() < nv)
|
|
{
|
|
data = nv;
|
|
lastUpdateSource = sourceId;
|
|
uls(now);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
template <class T>
|
|
void GwBoatItem<T>::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
|
|
{
|
|
JsonObject o = doc->createNestedObject(name);
|
|
o[F("value")] = getData();
|
|
o[F("update")] = minTime - lastSet;
|
|
o[F("source")] = lastUpdateSource;
|
|
o[F("valid")] = isValid(minTime);
|
|
o[F("format")] = format;
|
|
}
|
|
|
|
class WriterWrapper
|
|
{
|
|
GwBoatItemBase::StringWriter *writer = NULL;
|
|
|
|
public:
|
|
WriterWrapper(GwBoatItemBase::StringWriter *w)
|
|
{
|
|
writer = w;
|
|
}
|
|
size_t write(uint8_t c)
|
|
{
|
|
if (!writer)
|
|
return 0;
|
|
return writer->write(c);
|
|
}
|
|
size_t write(const uint8_t *s, size_t n)
|
|
{
|
|
if (!writer)
|
|
return 0;
|
|
return writer->write(s, n);
|
|
}
|
|
};
|
|
typedef ARDUINOJSON_NAMESPACE::TextFormatter<WriterWrapper> GwTextWriter;
|
|
|
|
static void writeToString(GwTextWriter *writer, const double &value)
|
|
{
|
|
writer->writeFloat(value);
|
|
}
|
|
static void writeToString(GwTextWriter *writer, const uint16_t &value)
|
|
{
|
|
writer->writeInteger(value);
|
|
}
|
|
static void writeToString(GwTextWriter *writer, const uint32_t &value)
|
|
{
|
|
writer->writeInteger(value);
|
|
}
|
|
static void writeToString(GwTextWriter *writer, const int16_t &value)
|
|
{
|
|
writer->writeInteger(value);
|
|
}
|
|
static void writeToString(GwTextWriter *writer, GwSatInfoList &value)
|
|
{
|
|
writer->writeInteger(value.getNumSats());
|
|
}
|
|
|
|
template <class T>
|
|
void GwBoatItem<T>::fillString()
|
|
{
|
|
bool valid = isValid();
|
|
if (writer.getSize() && (valid == lastStringValid))
|
|
return;
|
|
lastStringValid = valid;
|
|
writer.reset();
|
|
WriterWrapper wrapper(&writer);
|
|
GwTextWriter stringWriter(wrapper);
|
|
if (!writer.baseFilled())
|
|
{
|
|
stringWriter.writeRaw(name.c_str());
|
|
stringWriter.writeChar(',');
|
|
stringWriter.writeRaw(format.c_str());
|
|
stringWriter.writeChar(',');
|
|
writer.setBase();
|
|
}
|
|
stringWriter.writeInteger(valid ? 1 : 0);
|
|
stringWriter.writeChar(',');
|
|
stringWriter.writeInteger(lastSet);
|
|
stringWriter.writeChar(',');
|
|
stringWriter.writeInteger(lastUpdateSource);
|
|
stringWriter.writeChar(',');
|
|
writeToString(&stringWriter, data);
|
|
}
|
|
|
|
template class GwBoatItem<double>;
|
|
template class GwBoatItem<uint32_t>;
|
|
template class GwBoatItem<uint16_t>;
|
|
template class GwBoatItem<int16_t>;
|
|
void GwSatInfoList::houseKeeping(unsigned long ts)
|
|
{
|
|
if (ts == 0)
|
|
ts = millis();
|
|
sats.erase(std::remove_if(
|
|
sats.begin(),
|
|
sats.end(),
|
|
[ts, this](const GwSatInfo &info)
|
|
{
|
|
return info.validTill < ts;
|
|
}),
|
|
sats.end());
|
|
}
|
|
void GwSatInfoList::update(GwSatInfo entry, unsigned long validTill)
|
|
{
|
|
entry.validTill = validTill;
|
|
for (auto it = sats.begin(); it != sats.end(); it++)
|
|
{
|
|
if (it->PRN == entry.PRN)
|
|
{
|
|
*it = entry;
|
|
houseKeeping();
|
|
return;
|
|
}
|
|
}
|
|
houseKeeping();
|
|
sats.push_back(entry);
|
|
}
|
|
|
|
GwBoatDataSatList::GwBoatDataSatList(String name, String formatInfo, GwBoatItemBase::TOType toType, GwBoatItemMap *map) : GwBoatItem<GwSatInfoList>(name, formatInfo, toType, map) {}
|
|
|
|
bool GwBoatDataSatList::update(GwSatInfo info, int source)
|
|
{
|
|
unsigned long now = millis();
|
|
if (isValid(now))
|
|
{
|
|
//priority handling
|
|
//sources with lower ids will win
|
|
//and we will not overwrite their value
|
|
if (lastUpdateSource < source)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
lastUpdateSource = source;
|
|
uls(now);
|
|
data.update(info,now+invalidTime);
|
|
return true;
|
|
}
|
|
void GwBoatDataSatList::toJsonDoc(GwJsonDocument *doc, unsigned long minTime)
|
|
{
|
|
data.houseKeeping();
|
|
GwBoatItem<GwSatInfoList>::toJsonDoc(doc, minTime);
|
|
}
|
|
|
|
GwBoatData::GwBoatData(GwLog *logger, GwConfigHandler *cfg)
|
|
{
|
|
this->logger = logger;
|
|
this->config = cfg;
|
|
}
|
|
void GwBoatData::begin(){
|
|
for (auto &&it : values){
|
|
it.second->setInvalidTime(config);
|
|
}
|
|
}
|
|
GwBoatData::~GwBoatData()
|
|
{
|
|
GwBoatItemBase::GwBoatItemMap::iterator it;
|
|
for (it = values.begin(); it != values.end(); it++)
|
|
{
|
|
delete it->second;
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
GwBoatItem<T> *GwBoatData::getOrCreate(T initial, GwBoatItemNameProvider *provider)
|
|
{
|
|
String name = provider->getBoatItemName();
|
|
auto it = values.find(name);
|
|
if (it != values.end())
|
|
{
|
|
int expectedType = GwBoatItemTypes::getType(initial);
|
|
if (expectedType != it->second->getCurrentType())
|
|
{
|
|
LOG_DEBUG(GwLog::DEBUG, "invalid type for boat item %s, expected %d, got %d",
|
|
name.c_str(), expectedType, it->second->getCurrentType());
|
|
return NULL;
|
|
}
|
|
return (GwBoatItem<T> *)(it->second);
|
|
}
|
|
GwBoatItem<T> *rt = new GwBoatItem<T>(name,
|
|
provider->getBoatItemFormat(),
|
|
provider->getInvalidTime(),
|
|
&values);
|
|
rt->update(initial,-1);
|
|
LOG_DEBUG(GwLog::LOG, "creating boatItem %s, type %d",
|
|
name.c_str(), rt->getCurrentType());
|
|
return rt;
|
|
}
|
|
template <class T>
|
|
bool GwBoatData::update(const T value, int source, GwBoatItemNameProvider *provider)
|
|
{
|
|
GwBoatItem<T> *item = getOrCreate(value, provider);
|
|
if (!item)
|
|
return false;
|
|
return item->update(value, source);
|
|
}
|
|
template bool GwBoatData::update<double>(double value, int source, GwBoatItemNameProvider *provider);
|
|
template <class T>
|
|
T GwBoatData::getDataWithDefault(T defaultv, GwBoatItemNameProvider *provider)
|
|
{
|
|
auto it = values.find(provider->getBoatItemName());
|
|
if (it == values.end())
|
|
return defaultv;
|
|
int expectedType = GwBoatItemTypes::getType(defaultv);
|
|
if (expectedType != it->second->getCurrentType())
|
|
return defaultv;
|
|
return ((GwBoatItem<T> *)(it->second))->getDataWithDefault(defaultv);
|
|
}
|
|
template double GwBoatData::getDataWithDefault<double>(double defaultv, GwBoatItemNameProvider *provider);
|
|
String GwBoatData::toJson() const
|
|
{
|
|
unsigned long minTime = millis();
|
|
GwBoatItemBase::GwBoatItemMap::const_iterator it;
|
|
size_t count = 0;
|
|
size_t elementSizes = 0;
|
|
for (it = values.begin(); it != values.end(); it++)
|
|
{
|
|
count++;
|
|
elementSizes += it->second->getJsonSize();
|
|
}
|
|
int sz = JSON_OBJECT_SIZE(count) + elementSizes + 10;
|
|
LOG_DEBUG(GwLog::DEBUG, "size for boatData: %d", sz);
|
|
GwJsonDocument json(sz);
|
|
for (it = values.begin(); it != values.end(); it++)
|
|
{
|
|
it->second->toJsonDoc(&json, minTime);
|
|
}
|
|
String buf;
|
|
serializeJson(json, buf);
|
|
return buf;
|
|
}
|
|
String GwBoatData::toString()
|
|
{
|
|
String rt;
|
|
rt.reserve(values.size() * 80);
|
|
for (auto it = values.begin(); it != values.end(); it++)
|
|
{
|
|
rt += it->second->getDataString();
|
|
rt += "\n";
|
|
}
|
|
return rt;
|
|
}
|
|
bool GwBoatData::isValid(String name)
|
|
{
|
|
auto it = values.find(name);
|
|
if (it == values.end())
|
|
return false;
|
|
return it->second->isValid();
|
|
}
|
|
GwBoatItemBase *GwBoatData::getBase(String name)
|
|
{
|
|
auto it = values.find(name);
|
|
if (it == values.end())
|
|
return NULL;
|
|
return it->second;
|
|
}
|
|
double GwBoatData::getDoubleValue(String name, double defaultv)
|
|
{
|
|
auto it = values.find(name);
|
|
if (it == values.end())
|
|
return defaultv;
|
|
if (!it->second->isValid())
|
|
return defaultv;
|
|
return it->second->getDoubleValue();
|
|
}
|
|
|
|
void GwBoatData::setInvalidTime(GwBoatItemBase *item){
|
|
if (config != nullptr) item->setInvalidTime(config);
|
|
}
|
|
double formatCourse(double cv)
|
|
{
|
|
double rt = cv * 180.0 / M_PI;
|
|
if (rt > 360)
|
|
rt -= 360;
|
|
if (rt < 0)
|
|
rt += 360;
|
|
return rt;
|
|
}
|
|
double formatDegToRad(double deg)
|
|
{
|
|
return deg / 180.0 * M_PI;
|
|
}
|
|
double formatWind(double cv)
|
|
{
|
|
double rt = formatCourse(cv);
|
|
if (rt > 180)
|
|
rt = -1 * (360 - rt);
|
|
return rt;
|
|
}
|
|
double formatKnots(double cv)
|
|
{
|
|
return cv * 3600.0 / 1852.0;
|
|
}
|
|
|
|
double formatKmh(double cv)
|
|
{
|
|
return cv *3600.0 / 1000.0;
|
|
}
|
|
|
|
uint32_t mtr2nm(uint32_t m)
|
|
{
|
|
return m / 1852;
|
|
}
|
|
double mtr2nm(double m)
|
|
{
|
|
return m / 1852.0;
|
|
}
|
|
|
|
bool convertToJson(const GwSatInfoList &si, JsonVariant &variant)
|
|
{
|
|
return variant.set(si.getNumSats());
|
|
}
|
|
|
|
#ifdef _UNDEF
|
|
#include <ArduinoJson/Json/TextFormatter.hpp>
|
|
|
|
class XWriter
|
|
{
|
|
public:
|
|
void write(uint8_t c)
|
|
{
|
|
}
|
|
|
|
void write(const uint8_t *s, size_t n)
|
|
{
|
|
}
|
|
};
|
|
static XWriter xwriter;
|
|
ARDUINOJSON_NAMESPACE::TextFormatter<XWriter> testWriter(xwriter);
|
|
#endif
|