separate boat data, integrate with web server

This commit is contained in:
andreas 2021-10-18 19:20:00 +02:00
parent fb20135718
commit 3bbd9ef965
6 changed files with 190 additions and 178 deletions

View File

@ -0,0 +1,54 @@
#include "GwBoatData.h"
GwBoatItem *GwBoatData::find(String name,bool doCreate){
GwBoatItemMap::iterator it;
if ((it=values.find(name)) != values.end()){
return it->second;
}
if (! doCreate) return NULL;
GwBoatItem *ni=new GwBoatItem();
values[name]=ni;
return ni;
}
GwBoatData::GwBoatData(GwLog *logger){
}
GwBoatData::~GwBoatData(){
GwBoatItemMap::iterator it;
for (it=values.begin() ; it != values.end();it++){
delete it->second;
}
}
void GwBoatData::update(String name,const char *value){
GwBoatItem *i=find(name);
i->update(value);
}
void GwBoatData::update(String name, String value){
GwBoatItem *i=find(name);
i->update(value);
}
void GwBoatData::update(String name, long value){
GwBoatItem *i=find(name);
i->update(value);
}
void GwBoatData::update(String name, double value){
GwBoatItem *i=find(name);
i->update(value);
}
void GwBoatData::update(String name, bool value){
GwBoatItem *i=find(name);
i->update(value);
}
String GwBoatData::toJson() const {
long minTime=millis() - maxAge;
DynamicJsonDocument json(800);
GwBoatItemMap::const_iterator it;
for (it=values.begin() ; it != values.end();it++){
if (it->second->isValid(minTime)){
json[it->first]=it->second->getValue();
}
}
String buf;
serializeJson(json,buf);
return buf;
}

67
lib/boatData/GwBoatData.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef _GWBOATDATA_H
#define _GWBOATDATA_H
#include "GwLog.h"
#include <ArduinoJson.h>
#include <Arduino.h>
#include <map>
#define GW_BOAT_VALUE_LEN 32
class GwBoatItem{
private:
char value[GW_BOAT_VALUE_LEN];
long lastSet;
void uls(){
lastSet=millis();
}
public:
const char * getValue() const{return value;}
long getLastSet() const {return lastSet;}
bool isValid(long minTime) const {return lastSet > minTime;}
GwBoatItem(){
value[0]=0;
lastSet=-1;
}
void update(String nv){
strncpy(value,nv.c_str(),GW_BOAT_VALUE_LEN-1);
uls();
}
void update(const char * nv){
strncpy(value,nv,GW_BOAT_VALUE_LEN-1);
}
void update(long nv){
ltoa(nv,value,10);
uls();
}
void update(bool nv){
if (nv) strcpy_P(value,PSTR("true"));
else strcpy_P(value,PSTR("false"));
uls();
}
void update(double nv){
dtostrf(nv,3,7,value);
uls();
}
void invalidate(){
lastSet=0;
}
};
class GwBoatData{
typedef std::map<String,GwBoatItem*> GwBoatItemMap;
private:
const long maxAge=60000; //max age for valid data in ms
GwLog *logger;
GwBoatItemMap values;
GwBoatItem *find(String name, bool doCreate=true);
public:
GwBoatData(GwLog *logger);
~GwBoatData();
void update(String name,const char *value);
void update(String name, String value);
void update(String name, long value);
void update(String name, bool value);
void update(String name, double value);
String toJson() const;
};
#endif

View File

@ -26,12 +26,11 @@
#include <math.h>
#include "N2kDataToNMEA0183.h"
#include "BoatData.h"
const double radToDeg = 180.0 / M_PI;
//*****************************************************************************
void tN2kDataToNMEA0183::HandleMsg(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandleMsg(const tN2kMsg &N2kMsg) {
switch (N2kMsg.PGN) {
case 127250UL: HandleHeading(N2kMsg);
case 127258UL: HandleVariation(N2kMsg);
@ -48,18 +47,21 @@ void tN2kDataToNMEA0183::HandleMsg(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
long tN2kDataToNMEA0183::Update(tBoatData *BoatData) {
void N2kDataToNMEA0183::loop() {
unsigned long now=millis();
if ( now < (lastLoopTime + 100)) return;
lastLoopTime=now;
SendRMC();
if ( LastHeadingTime + 2000 < millis() ) Heading = N2kDoubleNA;
if ( LastCOGSOGTime + 2000 < millis() ) {
if ( (LastHeadingTime + 2000) < now ) Heading = N2kDoubleNA;
if ( (LastCOGSOGTime + 2000) < now ) {
COG = N2kDoubleNA;
SOG = N2kDoubleNA;
}
if ( LastPositionTime + 4000 < millis() ) {
if ( (LastPositionTime + 4000) < now ) {
Latitude = N2kDoubleNA;
Longitude = N2kDoubleNA;
}
if ( LastWindTime + 2000 < millis() ) {
if ( ( LastWindTime + 2000) < now ) {
AWS = N2kDoubleNA;
AWA = N2kDoubleNA;
TWS = N2kDoubleNA;
@ -67,45 +69,39 @@ long tN2kDataToNMEA0183::Update(tBoatData *BoatData) {
TWD = N2kDoubleNA;
}
BoatData->Latitude=Latitude;
BoatData->Longitude=Longitude;
BoatData->Altitude=Altitude;
BoatData->Heading=Heading * radToDeg;
BoatData->COG=COG * radToDeg;
BoatData->SOG=SOG * 3600.0/1852.0;
BoatData->STW=STW * 3600.0/1852.0;
BoatData->AWS=AWS * 3600.0/1852.0;
BoatData->TWS=TWS * 3600.0/1852.0;
BoatData->MaxAws=MaxAws * 3600.0/1852.0;;
BoatData->MaxTws=MaxTws * 3600.0/1852.0;;
BoatData->AWA=AWA * radToDeg;
BoatData->TWA=TWA * radToDeg;
BoatData->TWD=TWD * radToDeg;
BoatData->TripLog=TripLog / 1825.0;
BoatData->Log=Log / 1825.0;
BoatData->RudderPosition=RudderPosition * radToDeg;
BoatData->WaterTemperature=KelvinToC(WaterTemperature) ;
BoatData->WaterDepth=WaterDepth;
BoatData->Variation=Variation *radToDeg;
BoatData->GPSTime=SecondsSinceMidnight;
BoatData->DaysSince1970=DaysSince1970;
boatData->update(F("Latitude"),Latitude);
boatData->update(F("Longitude"),Longitude);
boatData->update(F("Altitude"),Altitude);
boatData->update(F("Heading"),Heading * radToDeg);
boatData->update(F("COG"),COG * radToDeg);
boatData->update(F("SOG"),SOG * 3600.0/1852.0);
boatData->update(F("STW"),STW * 3600.0/1852.0);
boatData->update(F("AWS"),AWS * 3600.0/1852.0);
boatData->update(F("TWS"),TWS * 3600.0/1852.0);
boatData->update(F("MaxAws"),MaxAws * 3600.0/1852.0);
boatData->update(F("MaxTws"),MaxTws * 3600.0/1852.0);
boatData->update(F("AWA"),AWA * radToDeg);
boatData->update(F("TWA"),TWA * radToDeg);
boatData->update(F("TWD"),TWD * radToDeg);
boatData->update(F("TripLog"),TripLog / 1825.0);
boatData->update(F("Log"),Log / 1825.0);
boatData->update(F("RudderPosition"),RudderPosition * radToDeg);
boatData->update(F("WaterTemperature"),KelvinToC(WaterTemperature)) ;
boatData->update(F("WaterDepth"),WaterDepth);
boatData->update(F("Variation"),Variation *radToDeg);
boatData->update(F("GPSTime"),SecondsSinceMidnight);
boatData->update(F("DaysSince1970"),(long)DaysSince1970);
if (SecondsSinceMidnight!=N2kDoubleNA && DaysSince1970!=N2kUInt16NA){
return((DaysSince1970*3600*24)+SecondsSinceMidnight); // Needed for SD Filename and time
} else {
return(0); // Needed for SD Filename and time
}
}
//*****************************************************************************
void tN2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) {
void N2kDataToNMEA0183::SendMessage(const tNMEA0183Msg &NMEA0183Msg) {
if ( pNMEA0183 != 0 ) pNMEA0183->SendMessage(NMEA0183Msg);
if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg);
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleHeading(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandleHeading(const tN2kMsg &N2kMsg) {
unsigned char SID;
tN2kHeadingReference ref;
double _Deviation = 0;
@ -125,7 +121,7 @@ void tN2kDataToNMEA0183::HandleHeading(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleVariation(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandleVariation(const tN2kMsg &N2kMsg) {
unsigned char SID;
tN2kMagneticVariation Source;
uint16_t DaysSince1970;
@ -134,7 +130,7 @@ void tN2kDataToNMEA0183::HandleVariation(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleBoatSpeed(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandleBoatSpeed(const tN2kMsg &N2kMsg) {
unsigned char SID;
double WaterReferenced;
double GroundReferenced;
@ -151,7 +147,7 @@ void tN2kDataToNMEA0183::HandleBoatSpeed(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleDepth(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandleDepth(const tN2kMsg &N2kMsg) {
unsigned char SID;
double DepthBelowTransducer;
double Offset;
@ -172,7 +168,7 @@ void tN2kDataToNMEA0183::HandleDepth(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandlePosition(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandlePosition(const tN2kMsg &N2kMsg) {
if ( ParseN2kPGN129025(N2kMsg, Latitude, Longitude) ) {
LastPositionTime = millis();
@ -180,7 +176,7 @@ void tN2kDataToNMEA0183::HandlePosition(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleCOGSOG(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandleCOGSOG(const tN2kMsg &N2kMsg) {
unsigned char SID;
tN2kHeadingReference HeadingReference;
tNMEA0183Msg NMEA0183Msg;
@ -199,7 +195,7 @@ void tN2kDataToNMEA0183::HandleCOGSOG(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleGNSS(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandleGNSS(const tN2kMsg &N2kMsg) {
unsigned char SID;
tN2kGNSStype GNSStype;
tN2kGNSSmethod GNSSmethod;
@ -220,7 +216,7 @@ void tN2kDataToNMEA0183::HandleGNSS(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleWind(const tN2kMsg &N2kMsg) {
void N2kDataToNMEA0183::HandleWind(const tN2kMsg &N2kMsg) {
unsigned char SID;
tN2kWindReference WindReference;
tNMEA0183WindReference NMEA0183Reference = NMEA0183Wind_True;
@ -283,7 +279,7 @@ void tN2kDataToNMEA0183::HandleWind(const tN2kMsg &N2kMsg) {
}
}
//*****************************************************************************
void tN2kDataToNMEA0183::SendRMC() {
void N2kDataToNMEA0183::SendRMC() {
if ( NextRMCSend <= millis() && !N2kIsNA(Latitude) ) {
tNMEA0183Msg NMEA0183Msg;
if ( NMEA0183SetRMC(NMEA0183Msg, SecondsSinceMidnight, Latitude, Longitude, COG, SOG, DaysSince1970, Variation) ) {
@ -295,7 +291,7 @@ void tN2kDataToNMEA0183::HandleWind(const tN2kMsg &N2kMsg) {
//*****************************************************************************
void tN2kDataToNMEA0183::HandleLog(const tN2kMsg & N2kMsg) {
void N2kDataToNMEA0183::HandleLog(const tN2kMsg & N2kMsg) {
uint16_t DaysSince1970;
double SecondsSinceMidnight;
@ -314,7 +310,7 @@ void tN2kDataToNMEA0183::HandleWind(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleRudder(const tN2kMsg & N2kMsg) {
void N2kDataToNMEA0183::HandleRudder(const tN2kMsg & N2kMsg) {
unsigned char Instance;
tN2kRudderDirectionOrder RudderDirectionOrder;
@ -337,7 +333,7 @@ void tN2kDataToNMEA0183::HandleWind(const tN2kMsg &N2kMsg) {
}
//*****************************************************************************
void tN2kDataToNMEA0183::HandleWaterTemp(const tN2kMsg & N2kMsg) {
void N2kDataToNMEA0183::HandleWaterTemp(const tN2kMsg & N2kMsg) {
unsigned char SID;
double OutsideAmbientAirTemperature;

View File

@ -24,12 +24,15 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <NMEA0183.h>
#include <NMEA2000.h>
#include "BoatData.h"
#include <GwLog.h>
#include <GwBoatData.h>
//------------------------------------------------------------------------------
class tN2kDataToNMEA0183 : public tNMEA2000::tMsgHandler {
class N2kDataToNMEA0183 : public tNMEA2000::tMsgHandler {
public:
using tSendNMEA0183MessageCallback=void (*)(const tNMEA0183Msg &NMEA0183Msg);
protected:
static const unsigned long RMCPeriod=500;
@ -69,6 +72,10 @@ protected:
unsigned long LastPosSend;
unsigned long LastWindTime;
unsigned long NextRMCSend;
unsigned long lastLoopTime;
GwLog *logger;
GwBoatData *boatData;
tNMEA0183 *pNMEA0183;
tSendNMEA0183MessageCallback SendNMEA0183MessageCallback;
@ -92,22 +99,25 @@ protected:
void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
public:
tN2kDataToNMEA0183(tNMEA2000 *_pNMEA2000, tNMEA0183 *_pNMEA0183) : tNMEA2000::tMsgHandler(0,_pNMEA2000) {
N2kDataToNMEA0183(GwLog * logger, GwBoatData *boatData, tNMEA2000 *NMEA2000, tNMEA0183 *NMEA0183) : tNMEA2000::tMsgHandler(0,NMEA2000) {
SendNMEA0183MessageCallback=0;
pNMEA0183=_pNMEA0183;
pNMEA0183=NMEA0183;
Latitude=N2kDoubleNA; Longitude=N2kDoubleNA; Altitude=N2kDoubleNA;
Variation=N2kDoubleNA; Heading=N2kDoubleNA; COG=N2kDoubleNA; SOG=N2kDoubleNA;
SecondsSinceMidnight=N2kDoubleNA; DaysSince1970=N2kUInt16NA;
LastPosSend=0;
lastLoopTime=0;
NextRMCSend=millis()+RMCPeriod;
LastHeadingTime=0;
LastCOGSOGTime=0;
LastPositionTime=0;
LastWindTime=0;
this->logger=logger;
this->boatData=boatData;
}
void HandleMsg(const tN2kMsg &N2kMsg);
void SetSendNMEA0183MessageCallback(tSendNMEA0183MessageCallback _SendNMEA0183MessageCallback) {
SendNMEA0183MessageCallback=_SendNMEA0183MessageCallback;
}
long Update(tBoatData *BoatData);
void loop();
};

View File

@ -1,38 +0,0 @@
#ifndef _BoatData_H_
#define _BoatData_H_
struct tBoatData {
unsigned long DaysSince1970; // Days since 1970-01-01
double Heading,SOG,COG,STW,Variation,AWS,TWS,MaxAws,MaxTws,AWA,TWA,AWD,TWD,TripLog,Log,RudderPosition,WaterTemperature,
WaterDepth, GPSTime,// Secs since midnight,
Latitude, Longitude, Altitude;
public:
tBoatData() {
Heading=0;
Latitude=0;
Longitude=0;
SOG=0;
COG=0;
STW=0;
AWS=0;
TWS=0;
MaxAws=0;
MaxTws=0;
AWA=0;
TWA=0;
TWD=0;
TripLog=0;
Log=0;
RudderPosition=0;
WaterTemperature=0;
WaterDepth=0;
Variation=0;
Altitude=0;
GPSTime=0;
DaysSince1970=0;
};
};
#endif // _BoatData_H_

View File

@ -28,27 +28,24 @@
#include "N2kDataToNMEA0183.h"
#include "BoatData.h"
#include "GwLog.h"
#include "GWConfig.h"
#include "GWWifi.h"
#include "GwSocketServer.h"
#include "GwBoatData.h"
#define ENABLE_DEBUG_LOG 0 // Debug log, set to 1 to enable AIS forward on USB-Serial / 2 for ADC voltage to support calibration
#define UDP_Forwarding 0 // Set to 1 for forwarding AIS from serial2 to UDP brodcast
#define NMEA_TO_SERIAL 1
#define HighTempAlarm 12 // Alarm level for fridge temperature (higher)
#define LowVoltageAlarm 11 // Alarm level for battery voltage (lower)
GwLog logger(LOG_SERIAL);
GwConfigHandler config(&logger);
GwWifi gwWifi(&config,&logger);
GwSocketServer socketServer(&config,&logger);
GwBoatData boatData(&logger);
//counter
@ -63,9 +60,6 @@ const int udpPort = 2000; // port 2000 lets think Navionics it is an DY WLN10 de
// Create UDP instance
WiFiUDP udp;
// Struct to update BoatData. See BoatData.h for content
tBoatData BoatData;
int NodeAddress; // To store last Node Address
Preferences preferences; // Nonvolatile storage on ESP32 - To store LastDeviceAddress
@ -78,10 +72,7 @@ const size_t MaxClients = 10;
bool SendNMEA0183Conversion = true; // Do we send NMEA2000 -> NMEA0183 conversion
bool SendSeaSmart = false; // Do we send NMEA2000 messages in SeaSmart format
WiFiServer json(90);
tN2kDataToNMEA0183 nmea0183Converter(&NMEA2000, 0);
N2kDataToNMEA0183 nmea0183Converter(&logger, &boatData,&NMEA2000, 0);
// Set the information for other bus devices, which messages we support
const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic
@ -127,11 +118,6 @@ tNMEA0183Msg NMEA0183Msg;
tNMEA0183 NMEA0183;
void debug_log(char* str) {
#if ENABLE_DEBUG_LOG == 1
Serial.println(str);
#endif
}
#define JSON_OK "{\"status\":\"OK\"}"
//embedded files
@ -168,6 +154,10 @@ void js_config(){
webserver.send(200,F("application/json"),config.toJson());
}
void js_boatData(){
webserver.send(200,F("application/json"),boatData.toJson());
}
void web_setConfig(){
bool ok=true;
String error;
@ -235,10 +225,6 @@ void setup() {
// Start TCP server
socketServer.begin();
// Start JSON server
json.begin();
// Start Web Server
webserver.on("/", web_index);
webserver.on("/api/reset", js_reset);
@ -246,6 +232,7 @@ void setup() {
webserver.on("/api/config",js_config);
webserver.on("/api/setConfig",web_setConfig);
webserver.on("/api/resetConfig",web_resetConfig);
webserver.on("/api/boatData",js_boatData);
webserver.onNotFound(handleNotFound);
webserver.begin();
@ -353,74 +340,12 @@ void SendN2kEngine() {
}
}
void handle_json() {
WiFiClient client = json.available();
// Do we have a client?
if (!client) return;
// Serial.println(F("New client"));
// Read the request (we ignore the content in this example)
while (client.available()) client.read();
// Allocate JsonBuffer
// Use arduinojson.org/assistant to compute the capacity.
StaticJsonDocument<800> root;
root["Latitude"] = BoatData.Latitude;
root["Longitude"] = BoatData.Longitude;
root["Heading"] = BoatData.Heading;
root["COG"] = BoatData.COG;
root["SOG"] = BoatData.SOG;
root["STW"] = BoatData.STW;
root["AWS"] = BoatData.AWS;
root["TWS"] = BoatData.TWS;
root["MaxAws"] = BoatData.MaxAws;
root["MaxTws"] = BoatData.MaxTws;
root["AWA"] = BoatData.AWA;
root["TWA"] = BoatData.TWA;
root["TWD"] = BoatData.TWD;
root["TripLog"] = BoatData.TripLog;
root["Log"] = BoatData.Log;
root["RudderPosition"] = BoatData.RudderPosition;
root["WaterTemperature"] = BoatData.WaterTemperature;
root["WaterDepth"] = BoatData.WaterDepth;
root["Variation"] = BoatData.Variation;
root["Altitude"] = BoatData.Altitude;
root["GPSTime"] = BoatData.GPSTime;
root["DaysSince1970"] = BoatData.DaysSince1970;
//Serial.print(F("Sending: "));
//serializeJson(root, Serial);
//Serial.println();
// Write response headers
client.println("HTTP/1.0 200 OK");
client.println("Content-Type: application/json");
client.println("Connection: close");
client.println();
// Write JSON document
serializeJsonPretty(root, client);
// Disconnect
client.stop();
}
long lastLog=millis();
void loop() {
unsigned int size;
int wifi_retry;
webserver.handleClient();
gwWifi.loop();
handle_json();
if (NMEA0183.GetMessage(NMEA0183Msg)) { // Get AIS NMEA sentences from serial2
@ -428,9 +353,7 @@ void loop() {
NMEA0183Msg.GetMessage(buff, MAX_NMEA0183_MESSAGE_SIZE); // send to buffer
#if ENABLE_DEBUG_LOG == 1
Serial.println(buff);
#endif
#if UDP_Forwarding == 1
size = strlen(buff);
@ -453,7 +376,7 @@ void loop() {
Serial.printf("Address Change: New Address=%d\n", SourceAddress);
}
nmea0183Converter.Update(&BoatData);
nmea0183Converter.loop();
// Dummy to empty input buffer to avoid board to stuck with e.g. NMEA Reader
if ( Serial.available() ) {