initial import
This commit is contained in:
commit
e2f8e4ff56
|
@ -0,0 +1,5 @@
|
|||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
|
@ -0,0 +1,19 @@
|
|||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:m5stack-atom]
|
||||
platform = espressif32
|
||||
board = m5stack-atom
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
ttlappalainen/NMEA2000-library @ ^4.17.2
|
||||
ttlappalainen/NMEA2000_esp32 @ ^1.0.3
|
||||
ttlappalainen/NMEA0183 @ ^1.7.1
|
||||
bblanchon/ArduinoJson@^6.18.5
|
|
@ -0,0 +1,38 @@
|
|||
#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_
|
|
@ -0,0 +1,459 @@
|
|||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2016 Thomas Sarlandie thomas@sarlandie.net
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
A big thanks to NorthWestern University ~riesbeck on implementing iterators.
|
||||
Could not have done it without you!
|
||||
http://www.cs.northwestern.edu/~riesbeck/programming/c++/stl-iterator-define.html
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//#define _USE_STD_LIST_FOR_LINKED_LIST_
|
||||
|
||||
#ifdef _USE_STD_LIST_FOR_LINKED_LIST_
|
||||
#include <list>
|
||||
|
||||
template <class T> using LinkedList = std::list<T>;
|
||||
#else
|
||||
|
||||
template <class T> class LinkedListIterator;
|
||||
template <class T> class LinkedListConstIterator;
|
||||
template <class T> class LinkedListCircularIterator;
|
||||
|
||||
template <class T> class LinkedList {
|
||||
private:
|
||||
class node {
|
||||
public:
|
||||
T value;
|
||||
node *next;
|
||||
|
||||
node(const T &v) : value(v), next(0) {};
|
||||
};
|
||||
|
||||
node *head;
|
||||
|
||||
friend class LinkedListIterator<T>;
|
||||
friend class LinkedListConstIterator<T>;
|
||||
friend class LinkedListCircularIterator<T>;
|
||||
|
||||
public:
|
||||
typedef LinkedListIterator<T> iterator;
|
||||
typedef LinkedListConstIterator<T> const_iterator;
|
||||
using constIterator=const_iterator;
|
||||
typedef LinkedListCircularIterator<T> circularIterator;
|
||||
|
||||
LinkedList() : head(0) {};
|
||||
|
||||
LinkedList(const LinkedList &l) : head(0) {
|
||||
for (constIterator it = l.begin(); it != l.end(); it++) {
|
||||
add(*it);
|
||||
}
|
||||
};
|
||||
|
||||
~LinkedList() {
|
||||
clear();
|
||||
};
|
||||
|
||||
LinkedList<T>& operator=(const LinkedList<T> &l) {
|
||||
clear();
|
||||
for (constIterator it = l.begin(); it != l.end(); it++) {
|
||||
add(*it);
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
|
||||
void push_back(const T &v) {
|
||||
if (head == 0) {
|
||||
head = new node(v);
|
||||
}
|
||||
else {
|
||||
node *n = head;
|
||||
while (n->next != 0) {
|
||||
n = n->next;
|
||||
}
|
||||
n->next = new node(v);
|
||||
}
|
||||
};
|
||||
|
||||
inline void add(const T &v) { push_back(v); }
|
||||
|
||||
iterator insert(iterator pos, const T& v) {
|
||||
if ( head==0 || pos._current==head ) {
|
||||
head=new node(v);
|
||||
head->next=pos._current;
|
||||
pos._current=head;
|
||||
} else {
|
||||
node *n;
|
||||
for ( n=head;n->next!=pos._current; n=n->next );
|
||||
n->next=new node(v);
|
||||
n->next->next=pos._current;
|
||||
pos._current=n->next;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
T& front() {
|
||||
return head->value;
|
||||
}
|
||||
|
||||
T& back() {
|
||||
node *n = head;
|
||||
while ( n!=0 && n->next!=0 ) {
|
||||
n=n->next;
|
||||
}
|
||||
return n->value;
|
||||
}
|
||||
|
||||
void remove(const T &v) {
|
||||
node *n = head;
|
||||
// remove from beginning
|
||||
while ( n!=0 && n->value==v ) {
|
||||
head=n->next;
|
||||
delete(n);
|
||||
n=head;
|
||||
}
|
||||
|
||||
while ( n!=0 ) {
|
||||
node *del=n->next;
|
||||
if ( del!=0 && del->value==v ) {
|
||||
n->next=del->next;
|
||||
delete(del);
|
||||
}
|
||||
n=n->next;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
node *n = head;
|
||||
while (n != 0) {
|
||||
node *next = n->next;
|
||||
delete(n);
|
||||
n = next;
|
||||
}
|
||||
head = 0;
|
||||
};
|
||||
|
||||
iterator erase(LinkedListIterator<T> l) {
|
||||
if ( l==end() ) return l;
|
||||
node *dn = l._current;
|
||||
l++;
|
||||
if ( dn==head ) {
|
||||
head=dn->next;
|
||||
} else {
|
||||
node *n=head;
|
||||
for ( ;n->next!=dn;n=n->next );
|
||||
n->next=dn->next;
|
||||
}
|
||||
delete dn;
|
||||
return l;
|
||||
}
|
||||
|
||||
void removeFirst() {
|
||||
node *n = head;
|
||||
if (head) {
|
||||
head = head->next;
|
||||
delete(n);
|
||||
}
|
||||
};
|
||||
|
||||
iterator begin() {
|
||||
return iterator(head);
|
||||
};
|
||||
|
||||
constIterator begin() const {
|
||||
return constIterator(head);
|
||||
};
|
||||
|
||||
iterator end() {
|
||||
return iterator(0);
|
||||
};
|
||||
|
||||
constIterator end() const {
|
||||
return constIterator(0);
|
||||
};
|
||||
|
||||
circularIterator circularBegin() {
|
||||
return circularIterator(head);
|
||||
};
|
||||
|
||||
circularIterator circularEnd() {
|
||||
return circularIterator(0);
|
||||
};
|
||||
|
||||
int size() const {
|
||||
LinkedList<T>::node *n = head;
|
||||
int sz = 0;
|
||||
while (n != 0) {
|
||||
n = n->next;
|
||||
sz++;
|
||||
}
|
||||
return sz;
|
||||
};
|
||||
|
||||
bool operator==(const LinkedList<T> &l) const {
|
||||
auto it=begin();
|
||||
auto oit=l.begin();
|
||||
for (;it!=end() && oit!=l.end() && (*it)==(*oit); it++, oit++);
|
||||
return ( (*it)==(*oit) );
|
||||
};
|
||||
|
||||
bool operator!=(const LinkedList<T> &l) const {
|
||||
return !(*this==l);
|
||||
};
|
||||
|
||||
template< class Compare >
|
||||
void sort( Compare comp ) {
|
||||
if ( head==0 || head->next==0 ) return; // Empty list or 1 element list
|
||||
bool sortDone;
|
||||
do {
|
||||
sortDone=true;
|
||||
node *pa=head;
|
||||
node *a=head;
|
||||
node *b=a->next;
|
||||
while ( b!=0 ) {
|
||||
if ( !(*comp)(b->value,a->value) ) {
|
||||
pa=a;
|
||||
} else { // sort required
|
||||
sortDone=false;
|
||||
a->next=b->next;
|
||||
b->next=a;
|
||||
if ( a==head ) {
|
||||
head=b;
|
||||
} else {
|
||||
pa->next=b;
|
||||
}
|
||||
}
|
||||
a=pa->next;
|
||||
b=a->next;
|
||||
}
|
||||
|
||||
} while ( !sortDone );
|
||||
}
|
||||
protected:
|
||||
template<class L> friend void MoveListItemForward(LinkedList<L> &list, const L &v);
|
||||
template<class L> friend void MoveListItemBack(LinkedList<L> &list, const L &v);
|
||||
void moveBack(const T &v) {
|
||||
node *n = head;
|
||||
|
||||
if ( n==0 || n->value==v ) return; // can not move
|
||||
|
||||
node *p=n;
|
||||
n=n->next;
|
||||
if ( n!=0 && n->value==v ) { // move second
|
||||
head->next=n->next;
|
||||
head=n;
|
||||
n->next=p;
|
||||
}
|
||||
|
||||
node *pp=p;
|
||||
for (p=n,n=n->next;n!=0 && n->value!=v; pp=p, p=n, n=n->next );
|
||||
if ( n==0 ) return;
|
||||
|
||||
p->next=n->next;
|
||||
pp->next=n;
|
||||
n->next=p;
|
||||
}
|
||||
|
||||
void moveForward(const T &v) {
|
||||
node *n = head;
|
||||
|
||||
if ( n==0 || n->next==0 ) return; // can not move, only one value
|
||||
|
||||
node *p=n;
|
||||
n=n->next;
|
||||
if ( p->value==v ) { // move first
|
||||
head=n;
|
||||
p->next=n->next;
|
||||
n->next=p;
|
||||
}
|
||||
|
||||
node *pp=p;
|
||||
for (p=n,n=n->next;n!=0 && p->value!=v; pp=p, p=n, n=n->next );
|
||||
if ( n==0 ) return;
|
||||
pp->next=n;
|
||||
p->next=n->next;
|
||||
n->next=p;
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> class LinkedListIterator {
|
||||
friend class LinkedList<T>;
|
||||
protected:
|
||||
typename LinkedList<T>::node *_current;
|
||||
|
||||
public:
|
||||
LinkedListIterator(typename LinkedList<T>::node *node=0) : _current(node) {};
|
||||
|
||||
LinkedListIterator<T>& operator=(const LinkedListIterator<T> &l) {
|
||||
_current = l._current;
|
||||
return *this;
|
||||
};
|
||||
|
||||
LinkedListIterator<T>& operator=(const LinkedListConstIterator<T> &l) {
|
||||
_current = l._current;
|
||||
return *this;
|
||||
};
|
||||
|
||||
bool operator==(LinkedListIterator<T> l) {
|
||||
return l._current == _current;
|
||||
}
|
||||
|
||||
bool operator!=(LinkedListIterator<T> l) {
|
||||
return l._current != _current;
|
||||
}
|
||||
|
||||
LinkedListIterator<T> & operator++() {
|
||||
if ( _current!=0) _current = _current->next;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Postfix operator takes an argument (that we do not use)
|
||||
LinkedListIterator<T> operator++(int) {
|
||||
LinkedListIterator<T> clone(*this);
|
||||
++(*this);
|
||||
return clone;
|
||||
}
|
||||
|
||||
T & operator*() {
|
||||
return _current->value;
|
||||
}
|
||||
|
||||
T * operator->() {
|
||||
return &(operator*());
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> class LinkedListConstIterator {
|
||||
protected:
|
||||
friend class LinkedListIterator<T>;
|
||||
typename LinkedList<T>::node const *_current;
|
||||
|
||||
public:
|
||||
LinkedListConstIterator(typename LinkedList<T>::node const *node) : _current(node) {};
|
||||
|
||||
bool operator==(LinkedListConstIterator<T> l) {
|
||||
return l._current == _current;
|
||||
}
|
||||
|
||||
bool operator!=(LinkedListConstIterator<T> l) {
|
||||
return l._current != _current;
|
||||
}
|
||||
|
||||
LinkedListConstIterator<T> & operator++() {
|
||||
if ( _current!=0) _current = _current->next;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Postfix operator takes an argument (that we do not use)
|
||||
LinkedListConstIterator<T> operator++(int) {
|
||||
LinkedListConstIterator<T> clone(*this);
|
||||
_current = _current->next;
|
||||
return clone;
|
||||
}
|
||||
|
||||
const T & operator*() {
|
||||
return _current->value;
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> class LinkedListCircularIterator : public LinkedListIterator<T> {
|
||||
protected:
|
||||
typename LinkedList<T>::node *_head;
|
||||
|
||||
public:
|
||||
LinkedListCircularIterator(typename LinkedList<T>::node *head) : LinkedListIterator<T>(head), _head(head) {};
|
||||
|
||||
LinkedListCircularIterator<T> & operator++() {
|
||||
if (this->_current->next) {
|
||||
this->_current = this->_current->next;
|
||||
}
|
||||
else {
|
||||
this->_current = this->_head;
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
|
||||
LinkedListIterator<T> operator++(int) {
|
||||
LinkedListIterator<T> clone(*this);
|
||||
++(*this);
|
||||
return clone;
|
||||
};
|
||||
};
|
||||
|
||||
#ifdef DEBUG_TEST_LINKED_LIST
|
||||
LinkedList<double> DoubleList;
|
||||
|
||||
void PrintTestList() {
|
||||
for (auto it=DoubleList.begin(); it!=DoubleList.end(); it++) {
|
||||
Serial.println((*it));
|
||||
}
|
||||
}
|
||||
void TestList() {
|
||||
DoubleList.add(5);
|
||||
DoubleList.add(6);
|
||||
DoubleList.add(9);
|
||||
PrintTestList();
|
||||
DoubleList.remove(6);
|
||||
PrintTestList();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
void MoveListItemForward(LinkedList<T> &list, const T &v) {
|
||||
#ifdef _USE_STD_LIST_FOR_LINKED_LIST_
|
||||
auto it=list.begin();
|
||||
if ( it==list.end() ) return; // Empty list
|
||||
|
||||
for ( ;it!=list.end() && (*it)!=v; it++);
|
||||
if ( it==list.end() ) return; // not found
|
||||
auto itn=it;
|
||||
itn++;
|
||||
if ( itn==list.end() ) return; // v is last value
|
||||
itn++;
|
||||
list.erase(it);
|
||||
list.insert(itn,v);
|
||||
#else
|
||||
list.moveForward(v);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void MoveListItemBack(LinkedList<T> &list, const T &v) {
|
||||
#ifdef _USE_STD_LIST_FOR_LINKED_LIST_
|
||||
auto it=list.begin();
|
||||
if ( it==list.end() ) return; // Empty list
|
||||
if ( (*it)==v ) return; // v at begin
|
||||
|
||||
auto itp=it;
|
||||
it++;
|
||||
for ( ;it!=list.end() && (*it)!=v; itp=it, it++);
|
||||
if ( it==list.end() ) return; // not found
|
||||
list.erase(it);
|
||||
list.insert(itp,v);
|
||||
#else
|
||||
list.moveBack(v);
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,356 @@
|
|||
/*
|
||||
N2kDataToNMEA0183.cpp
|
||||
|
||||
Copyright (c) 2015-2018 Timo Lappalainen, Kave Oy, www.kave.fi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <N2kMessages.h>
|
||||
#include <NMEA0183Messages.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "N2kDataToNMEA0183.h"
|
||||
#include "BoatData.h"
|
||||
|
||||
const double radToDeg = 180.0 / M_PI;
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleMsg(const tN2kMsg &N2kMsg) {
|
||||
switch (N2kMsg.PGN) {
|
||||
case 127250UL: HandleHeading(N2kMsg);
|
||||
case 127258UL: HandleVariation(N2kMsg);
|
||||
case 128259UL: HandleBoatSpeed(N2kMsg);
|
||||
case 128267UL: HandleDepth(N2kMsg);
|
||||
case 129025UL: HandlePosition(N2kMsg);
|
||||
case 129026UL: HandleCOGSOG(N2kMsg);
|
||||
case 129029UL: HandleGNSS(N2kMsg);
|
||||
case 130306UL: HandleWind(N2kMsg);
|
||||
case 128275UL: HandleLog(N2kMsg);
|
||||
case 127245UL: HandleRudder(N2kMsg);
|
||||
case 130310UL: HandleWaterTemp(N2kMsg);
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
long tN2kDataToNMEA0183::Update(tBoatData *BoatData) {
|
||||
SendRMC();
|
||||
if ( LastHeadingTime + 2000 < millis() ) Heading = N2kDoubleNA;
|
||||
if ( LastCOGSOGTime + 2000 < millis() ) {
|
||||
COG = N2kDoubleNA;
|
||||
SOG = N2kDoubleNA;
|
||||
}
|
||||
if ( LastPositionTime + 4000 < millis() ) {
|
||||
Latitude = N2kDoubleNA;
|
||||
Longitude = N2kDoubleNA;
|
||||
}
|
||||
if ( LastWindTime + 2000 < millis() ) {
|
||||
AWS = N2kDoubleNA;
|
||||
AWA = N2kDoubleNA;
|
||||
TWS = N2kDoubleNA;
|
||||
TWA = N2kDoubleNA;
|
||||
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;
|
||||
|
||||
|
||||
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) {
|
||||
if ( pNMEA0183 != 0 ) pNMEA0183->SendMessage(NMEA0183Msg);
|
||||
if ( SendNMEA0183MessageCallback != 0 ) SendNMEA0183MessageCallback(NMEA0183Msg);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleHeading(const tN2kMsg &N2kMsg) {
|
||||
unsigned char SID;
|
||||
tN2kHeadingReference ref;
|
||||
double _Deviation = 0;
|
||||
double _Variation;
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
|
||||
if ( ParseN2kHeading(N2kMsg, SID, Heading, _Deviation, _Variation, ref) ) {
|
||||
if ( ref == N2khr_magnetic ) {
|
||||
if ( !N2kIsNA(_Variation) ) Variation = _Variation; // Update Variation
|
||||
if ( !N2kIsNA(Heading) && !N2kIsNA(Variation) ) Heading -= Variation;
|
||||
}
|
||||
LastHeadingTime = millis();
|
||||
if ( NMEA0183SetHDG(NMEA0183Msg, Heading, _Deviation, Variation) ) {
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleVariation(const tN2kMsg &N2kMsg) {
|
||||
unsigned char SID;
|
||||
tN2kMagneticVariation Source;
|
||||
uint16_t DaysSince1970;
|
||||
|
||||
ParseN2kMagneticVariation(N2kMsg, SID, Source, DaysSince1970, Variation);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleBoatSpeed(const tN2kMsg &N2kMsg) {
|
||||
unsigned char SID;
|
||||
double WaterReferenced;
|
||||
double GroundReferenced;
|
||||
tN2kSpeedWaterReferenceType SWRT;
|
||||
|
||||
if ( ParseN2kBoatSpeed(N2kMsg, SID, WaterReferenced, GroundReferenced, SWRT) ) {
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
STW=WaterReferenced;
|
||||
double MagneticHeading = ( !N2kIsNA(Heading) && !N2kIsNA(Variation) ? Heading + Variation : NMEA0183DoubleNA);
|
||||
if ( NMEA0183SetVHW(NMEA0183Msg, Heading, MagneticHeading, WaterReferenced) ) {
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleDepth(const tN2kMsg &N2kMsg) {
|
||||
unsigned char SID;
|
||||
double DepthBelowTransducer;
|
||||
double Offset;
|
||||
double Range;
|
||||
|
||||
if ( ParseN2kWaterDepth(N2kMsg, SID, DepthBelowTransducer, Offset, Range) ) {
|
||||
|
||||
WaterDepth=DepthBelowTransducer+Offset;
|
||||
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
if ( NMEA0183SetDPT(NMEA0183Msg, DepthBelowTransducer, Offset) ) {
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
if ( NMEA0183SetDBx(NMEA0183Msg, DepthBelowTransducer, Offset) ) {
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandlePosition(const tN2kMsg &N2kMsg) {
|
||||
|
||||
if ( ParseN2kPGN129025(N2kMsg, Latitude, Longitude) ) {
|
||||
LastPositionTime = millis();
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleCOGSOG(const tN2kMsg &N2kMsg) {
|
||||
unsigned char SID;
|
||||
tN2kHeadingReference HeadingReference;
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
|
||||
if ( ParseN2kCOGSOGRapid(N2kMsg, SID, HeadingReference, COG, SOG) ) {
|
||||
LastCOGSOGTime = millis();
|
||||
double MCOG = ( !N2kIsNA(COG) && !N2kIsNA(Variation) ? COG - Variation : NMEA0183DoubleNA );
|
||||
if ( HeadingReference == N2khr_magnetic ) {
|
||||
MCOG = COG;
|
||||
if ( !N2kIsNA(Variation) ) COG -= Variation;
|
||||
}
|
||||
if ( NMEA0183SetVTG(NMEA0183Msg, COG, MCOG, SOG) ) {
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleGNSS(const tN2kMsg &N2kMsg) {
|
||||
unsigned char SID;
|
||||
tN2kGNSStype GNSStype;
|
||||
tN2kGNSSmethod GNSSmethod;
|
||||
unsigned char nSatellites;
|
||||
double HDOP;
|
||||
double PDOP;
|
||||
double GeoidalSeparation;
|
||||
unsigned char nReferenceStations;
|
||||
tN2kGNSStype ReferenceStationType;
|
||||
uint16_t ReferenceSationID;
|
||||
double AgeOfCorrection;
|
||||
|
||||
if ( ParseN2kGNSS(N2kMsg, SID, DaysSince1970, SecondsSinceMidnight, Latitude, Longitude, Altitude, GNSStype, GNSSmethod,
|
||||
nSatellites, HDOP, PDOP, GeoidalSeparation,
|
||||
nReferenceStations, ReferenceStationType, ReferenceSationID, AgeOfCorrection) ) {
|
||||
LastPositionTime = millis();
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleWind(const tN2kMsg &N2kMsg) {
|
||||
unsigned char SID;
|
||||
tN2kWindReference WindReference;
|
||||
tNMEA0183WindReference NMEA0183Reference = NMEA0183Wind_True;
|
||||
|
||||
double x, y;
|
||||
double WindAngle, WindSpeed;
|
||||
|
||||
// double TWS, TWA, AWD;
|
||||
|
||||
if ( ParseN2kWindSpeed(N2kMsg, SID, WindSpeed, WindAngle, WindReference) ) {
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
LastWindTime = millis();
|
||||
|
||||
if ( WindReference == N2kWind_Apparent ) {
|
||||
NMEA0183Reference = NMEA0183Wind_Apparent;
|
||||
AWA=WindAngle;
|
||||
AWS=WindSpeed;
|
||||
if (AWS > MaxAws) MaxAws=AWS;
|
||||
}
|
||||
|
||||
if ( NMEA0183SetMWV(NMEA0183Msg, WindAngle*radToDeg, NMEA0183Reference , WindSpeed)) SendMessage(NMEA0183Msg);
|
||||
|
||||
if (WindReference == N2kWind_Apparent && SOG != N2kDoubleNA) { // Lets calculate and send TWS/TWA if SOG is available
|
||||
|
||||
AWD=WindAngle*radToDeg + Heading*radToDeg;
|
||||
if (AWD>360) AWD=AWD-360;
|
||||
if (AWD<0) AWD=AWD+360;
|
||||
|
||||
x = WindSpeed * cos(WindAngle);
|
||||
y = WindSpeed * sin(WindAngle);
|
||||
|
||||
TWA = atan2(y, -SOG + x);
|
||||
TWS = sqrt(( y*y) + ((-SOG+x)*(-SOG+x)));
|
||||
|
||||
if (TWS > MaxTws) MaxTws=TWS;
|
||||
|
||||
TWA = TWA * radToDeg +360;
|
||||
|
||||
if (TWA>360) TWA=TWA-360;
|
||||
if (TWA<0) TWA=TWA+360;
|
||||
|
||||
NMEA0183Reference = NMEA0183Wind_True;
|
||||
if ( NMEA0183SetMWV(NMEA0183Msg, TWA, NMEA0183Reference , TWS)) SendMessage(NMEA0183Msg);
|
||||
|
||||
if ( !NMEA0183Msg.Init("MWD", "GP") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(AWD) ) return;
|
||||
if ( !NMEA0183Msg.AddStrField("T") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(AWD) ) return;
|
||||
if ( !NMEA0183Msg.AddStrField("M") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(TWS/0.514444) ) return;
|
||||
if ( !NMEA0183Msg.AddStrField("N") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(TWS) ) return;
|
||||
if ( !NMEA0183Msg.AddStrField("M") ) return;
|
||||
|
||||
SendMessage(NMEA0183Msg);
|
||||
|
||||
TWA=TWA/radToDeg;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::SendRMC() {
|
||||
if ( NextRMCSend <= millis() && !N2kIsNA(Latitude) ) {
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
if ( NMEA0183SetRMC(NMEA0183Msg, SecondsSinceMidnight, Latitude, Longitude, COG, SOG, DaysSince1970, Variation) ) {
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
SetNextRMCSend();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleLog(const tN2kMsg & N2kMsg) {
|
||||
uint16_t DaysSince1970;
|
||||
double SecondsSinceMidnight;
|
||||
|
||||
if ( ParseN2kDistanceLog(N2kMsg, DaysSince1970, SecondsSinceMidnight, Log, TripLog) ) {
|
||||
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
|
||||
if ( !NMEA0183Msg.Init("VLW", "GP") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(Log / 1852.0) ) return;
|
||||
if ( !NMEA0183Msg.AddStrField("N") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(TripLog / 1852.0) ) return;
|
||||
if ( !NMEA0183Msg.AddStrField("N") ) return;
|
||||
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleRudder(const tN2kMsg & N2kMsg) {
|
||||
|
||||
unsigned char Instance;
|
||||
tN2kRudderDirectionOrder RudderDirectionOrder;
|
||||
double AngleOrder;
|
||||
|
||||
if ( ParseN2kRudder(N2kMsg, RudderPosition, Instance, RudderDirectionOrder, AngleOrder) ) {
|
||||
|
||||
if(Instance!=0) return;
|
||||
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
|
||||
if ( !NMEA0183Msg.Init("RSA", "GP") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(RudderPosition * radToDeg) ) return;
|
||||
if ( !NMEA0183Msg.AddStrField("A") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(0.0) ) return;
|
||||
if ( !NMEA0183Msg.AddStrField("A") ) return;
|
||||
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void tN2kDataToNMEA0183::HandleWaterTemp(const tN2kMsg & N2kMsg) {
|
||||
|
||||
unsigned char SID;
|
||||
double OutsideAmbientAirTemperature;
|
||||
double AtmosphericPressure;
|
||||
|
||||
if ( ParseN2kPGN130310(N2kMsg, SID, WaterTemperature, OutsideAmbientAirTemperature, AtmosphericPressure) ) {
|
||||
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
|
||||
if ( !NMEA0183Msg.Init("MTW", "GP") ) return;
|
||||
if ( !NMEA0183Msg.AddDoubleField(KelvinToC(WaterTemperature))) return;
|
||||
if ( !NMEA0183Msg.AddStrField("C") ) return;
|
||||
|
||||
SendMessage(NMEA0183Msg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
N2kDataToNMEA0183.h
|
||||
|
||||
Copyright (c) 2015-2018 Timo Lappalainen, Kave Oy, www.kave.fi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <NMEA0183.h>
|
||||
#include <NMEA2000.h>
|
||||
|
||||
#include "BoatData.h"
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
class tN2kDataToNMEA0183 : public tNMEA2000::tMsgHandler {
|
||||
public:
|
||||
using tSendNMEA0183MessageCallback=void (*)(const tNMEA0183Msg &NMEA0183Msg);
|
||||
|
||||
protected:
|
||||
static const unsigned long RMCPeriod=500;
|
||||
double Latitude;
|
||||
double Longitude;
|
||||
double Altitude;
|
||||
double Variation;
|
||||
double Heading;
|
||||
double COG;
|
||||
double SOG;
|
||||
double STW;
|
||||
|
||||
double TWS;
|
||||
double TWA;
|
||||
double TWD;
|
||||
|
||||
double AWS;
|
||||
double AWA;
|
||||
double AWD;
|
||||
|
||||
double MaxAws;
|
||||
double MaxTws;
|
||||
|
||||
double RudderPosition;
|
||||
double WaterTemperature;
|
||||
double WaterDepth;
|
||||
|
||||
uint32_t TripLog;
|
||||
uint32_t Log;
|
||||
|
||||
uint16_t DaysSince1970;
|
||||
double SecondsSinceMidnight;
|
||||
|
||||
unsigned long LastHeadingTime;
|
||||
unsigned long LastCOGSOGTime;
|
||||
unsigned long LastPositionTime;
|
||||
unsigned long LastPosSend;
|
||||
unsigned long LastWindTime;
|
||||
unsigned long NextRMCSend;
|
||||
|
||||
tNMEA0183 *pNMEA0183;
|
||||
tSendNMEA0183MessageCallback SendNMEA0183MessageCallback;
|
||||
|
||||
protected:
|
||||
void HandleHeading(const tN2kMsg &N2kMsg); // 127250
|
||||
void HandleVariation(const tN2kMsg &N2kMsg); // 127258
|
||||
void HandleBoatSpeed(const tN2kMsg &N2kMsg); // 128259
|
||||
void HandleDepth(const tN2kMsg &N2kMsg); // 128267
|
||||
void HandlePosition(const tN2kMsg &N2kMsg); // 129025
|
||||
void HandleCOGSOG(const tN2kMsg &N2kMsg); // 129026
|
||||
void HandleGNSS(const tN2kMsg &N2kMsg); // 129029
|
||||
void HandleWind(const tN2kMsg &N2kMsg); // 130306
|
||||
void HandleLog(const tN2kMsg &N2kMsg); // 128275
|
||||
void HandleRudder(const tN2kMsg &N2kMsg); // 127245
|
||||
void HandleWaterTemp(const tN2kMsg &N2kMsg); // 130310
|
||||
|
||||
|
||||
void SetNextRMCSend() { NextRMCSend=millis()+RMCPeriod; }
|
||||
void SendRMC();
|
||||
void SendMessage(const tNMEA0183Msg &NMEA0183Msg);
|
||||
|
||||
public:
|
||||
tN2kDataToNMEA0183(tNMEA2000 *_pNMEA2000, tNMEA0183 *_pNMEA0183) : tNMEA2000::tMsgHandler(0,_pNMEA2000) {
|
||||
SendNMEA0183MessageCallback=0;
|
||||
pNMEA0183=_pNMEA0183;
|
||||
Latitude=N2kDoubleNA; Longitude=N2kDoubleNA; Altitude=N2kDoubleNA;
|
||||
Variation=N2kDoubleNA; Heading=N2kDoubleNA; COG=N2kDoubleNA; SOG=N2kDoubleNA;
|
||||
SecondsSinceMidnight=N2kDoubleNA; DaysSince1970=N2kUInt16NA;
|
||||
LastPosSend=0;
|
||||
NextRMCSend=millis()+RMCPeriod;
|
||||
LastHeadingTime=0;
|
||||
LastCOGSOGTime=0;
|
||||
LastPositionTime=0;
|
||||
LastWindTime=0;
|
||||
}
|
||||
void HandleMsg(const tN2kMsg &N2kMsg);
|
||||
void SetSendNMEA0183MessageCallback(tSendNMEA0183MessageCallback _SendNMEA0183MessageCallback) {
|
||||
SendNMEA0183MessageCallback=_SendNMEA0183MessageCallback;
|
||||
}
|
||||
long Update(tBoatData *BoatData);
|
||||
};
|
|
@ -0,0 +1,156 @@
|
|||
|
||||
const char indexHTML[] PROGMEM = R"=====(
|
||||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||
<title>ADC Anzeigeinstrument</title>
|
||||
<script src="gauge.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var myAjax = false;
|
||||
window.onload = start;
|
||||
|
||||
function start ()
|
||||
{
|
||||
Url="/";
|
||||
if (window.XMLHttpRequest) { // Mozilla, Safari,...
|
||||
myAjax = new XMLHttpRequest();
|
||||
if (myAjax.overrideMimeType) {
|
||||
myAjax.overrideMimeType('text/xml');
|
||||
}
|
||||
} else if (window.ActiveXObject) { // IE
|
||||
try {
|
||||
myAjax = new ActiveXObject("Msxml2.XMLHTTP");
|
||||
} catch (e) {
|
||||
try {
|
||||
myAjax = new ActiveXObject("Microsoft.XMLHTTP");
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
myAjax.onreadystatechange=LesenAjax;
|
||||
}
|
||||
|
||||
function aktualisieren()
|
||||
{
|
||||
|
||||
document.getElementById('Online').style.background = "red";
|
||||
|
||||
if (!myAjax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
myAjax.open("GET",Url+"ADC.txt",true);
|
||||
myAjax.send();
|
||||
}
|
||||
}
|
||||
|
||||
function LesenAjax()
|
||||
{
|
||||
if (myAjax.readyState==4 && myAjax.status==200)
|
||||
{
|
||||
|
||||
var Temp=0.5;
|
||||
Temp = parseFloat (myAjax.responseText);
|
||||
Gauge.Collection.get('gauge1').setValue(Temp);
|
||||
document.getElementById('Online').style.background = "#ddd";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
#wrap
|
||||
{
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
max-width: 900px;
|
||||
margin:0 auto;
|
||||
background:#dcd;
|
||||
border-radius: 10px;
|
||||
padding:10px; /*innen*/
|
||||
}
|
||||
|
||||
.Form-Einstellungen
|
||||
{
|
||||
border: 1px solid #666666;
|
||||
margin: 10px; /*aussen*/
|
||||
padding:5px; /*innen*/
|
||||
border-radius: 10px;
|
||||
background:#ddd;
|
||||
|
||||
}
|
||||
|
||||
.but
|
||||
{
|
||||
border-radius: 5px;
|
||||
background:lightgreen;
|
||||
}
|
||||
|
||||
#Online
|
||||
{
|
||||
display: inline-block;
|
||||
float: left;
|
||||
border: 1px solid #666666;
|
||||
border-radius: 10px;
|
||||
width: 20px;
|
||||
}
|
||||
</style>
|
||||
</head><body>
|
||||
<div id="wrap">
|
||||
<div class="Form-Einstellungen">
|
||||
Anzeigeinstrument für Kühlschranktemperatur
|
||||
<div id="Online">  </div>
|
||||
</div>
|
||||
<div class="Anzeige">
|
||||
<canvas id="gauge1" width="500" height="500"
|
||||
data-type="canv-gauge"
|
||||
data-title="Temperatur"
|
||||
data-min-value="-4"
|
||||
data-max-value="15"
|
||||
data-major-ticks="-4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"
|
||||
data-minor-ticks="2"
|
||||
data-stroke-ticks="true"
|
||||
data-units="°C"
|
||||
data-value-format="2.2"
|
||||
data-glow="true"
|
||||
data-animation-delay="5"
|
||||
data-animation-duration="2000"
|
||||
data-animation-fn="bounce"
|
||||
data-colors-needle="#f00 #00f"
|
||||
data-highlights="0 30 #fff"
|
||||
data-onready="setInterval(aktualisieren,1000);"
|
||||
></canvas><br>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)=====" ;
|
||||
|
||||
|
||||
const char gauge[] PROGMEM = R"=====(
|
||||
var Gauge=function(b){function l(a,b){for(var c in b)"object"==typeof b[c]&&"[object Array]"!==Object.prototype.toString.call(b[c])&&"renderTo"!=c?("object"!=typeof a[c]&&(a[c]={}),l(a[c],b[c])):a[c]=b[c]}function q(){z.width=b.width;z.height=b.height;A=z.cloneNode(!0);B=A.getContext("2d");C=z.width;D=z.height;t=C/2;u=D/2;f=t<u?t:u;A.i8d=!1;B.translate(t,u);B.save();a.translate(t,u);a.save()}function v(a){var b=new Date;G=setInterval(function(){var c=(new Date-b)/a.duration;1<c&&(c=1);var f=("function"==
|
||||
typeof a.delta?a.delta:M[a.delta])(c);a.step(f);1==c&&clearInterval(G)},a.delay||10)}function k(){G&&clearInterval(G);var a=I-n,h=n,c=b.animation;v({delay:c.delay,duration:c.duration,delta:c.fn,step:function(b){n=parseFloat(h)+a*b;E.draw()}})}function e(a){return a*Math.PI/180}function g(b,h,c){c=a.createLinearGradient(0,0,0,c);c.addColorStop(0,b);c.addColorStop(1,h);return c}function p(){var m=93*(f/100),h=f-m,c=91*(f/100),e=88*(f/100),d=85*(f/100);a.save();b.glow&&(a.shadowBlur=h,a.shadowColor=
|
||||
"rgba(0, 0, 0, 0.5)");a.beginPath();a.arc(0,0,m,0,2*Math.PI,!0);a.fillStyle=g("#ddd","#aaa",m);a.fill();a.restore();a.beginPath();a.arc(0,0,c,0,2*Math.PI,!0);a.fillStyle=g("#fafafa","#ccc",c);a.fill();a.beginPath();a.arc(0,0,e,0,2*Math.PI,!0);a.fillStyle=g("#eee","#f0f0f0",e);a.fill();a.beginPath();a.arc(0,0,d,0,2*Math.PI,!0);a.fillStyle=b.colors.plate;a.fill();a.save()}function w(a){var h=!1;a=0===b.majorTicksFormat.dec?Math.round(a).toString():a.toFixed(b.majorTicksFormat.dec);return 1<b.majorTicksFormat["int"]?
|
||||
(h=-1<a.indexOf("."),-1<a.indexOf("-")?"-"+(b.majorTicksFormat["int"]+b.majorTicksFormat.dec+2+(h?1:0)-a.length)+a.replace("-",""):""+(b.majorTicksFormat["int"]+b.majorTicksFormat.dec+1+(h?1:0)-a.length)+a):a}function d(){var m=81*(f/100);a.lineWidth=2;a.strokeStyle=b.colors.majorTicks;a.save();if(0===b.majorTicks.length){for(var h=(b.maxValue-b.minValue)/5,c=0;5>c;c++)b.majorTicks.push(w(b.minValue+h*c));b.majorTicks.push(w(b.maxValue))}for(c=0;c<b.majorTicks.length;++c)a.rotate(e(45+c*(270/(b.majorTicks.length-
|
||||
1)))),a.beginPath(),a.moveTo(0,m),a.lineTo(0,m-15*(f/100)),a.stroke(),a.restore(),a.save();b.strokeTicks&&(a.rotate(e(90)),a.beginPath(),a.arc(0,0,m,e(45),e(315),!1),a.stroke(),a.restore(),a.save())}function J(){var m=81*(f/100);a.lineWidth=1;a.strokeStyle=b.colors.minorTicks;a.save();for(var h=b.minorTicks*(b.majorTicks.length-1),c=0;c<h;++c)a.rotate(e(45+c*(270/h))),a.beginPath(),a.moveTo(0,m),a.lineTo(0,m-7.5*(f/100)),a.stroke(),a.restore(),a.save()}function s(){for(var m=55*(f/100),h=0;h<b.majorTicks.length;++h){var c=
|
||||
F(m,e(45+h*(270/(b.majorTicks.length-1))));a.font=20*(f/200)+"px Arial";a.fillStyle=b.colors.numbers;a.lineWidth=0;a.textAlign="center";a.fillText(b.majorTicks[h],c.x,c.y+3)}}function x(a){var h=b.valueFormat.dec,c=b.valueFormat["int"];a=parseFloat(a);var f=0>a;a=Math.abs(a);if(0<h){a=a.toFixed(h).toString().split(".");h=0;for(c-=a[0].length;h<c;++h)a[0]="0"+a[0];a=(f?"-":"")+a[0]+"."+a[1]}else{a=Math.round(a).toString();h=0;for(c-=a.length;h<c;++h)a="0"+a;a=(f?"-":"")+a}return a}function F(a,b){var c=
|
||||
Math.sin(b),f=Math.cos(b);return{x:0*f-a*c,y:0*c+a*f}}function N(){a.save();for(var m=81*(f/100),h=m-15*(f/100),c=0,g=b.highlights.length;c<g;c++){var d=b.highlights[c],r=(b.maxValue-b.minValue)/270,k=e(45+(d.from-b.minValue)/r),r=e(45+(d.to-b.minValue)/r);a.beginPath();a.rotate(e(90));a.arc(0,0,m,k,r,!1);a.restore();a.save();var l=F(h,k),p=F(m,k);a.moveTo(l.x,l.y);a.lineTo(p.x,p.y);var p=F(m,r),n=F(h,r);a.lineTo(p.x,p.y);a.lineTo(n.x,n.y);a.lineTo(l.x,l.y);a.closePath();a.fillStyle=d.color;a.fill();
|
||||
a.beginPath();a.rotate(e(90));a.arc(0,0,h,k-0.2,r+0.2,!1);a.restore();a.closePath();a.fillStyle=b.colors.plate;a.fill();a.save()}}function K(){var m=12*(f/100),h=8*(f/100),c=77*(f/100),d=20*(f/100),k=4*(f/100),r=2*(f/100),l=function(){a.shadowOffsetX=2;a.shadowOffsetY=2;a.shadowBlur=10;a.shadowColor="rgba(188, 143, 143, 0.45)"};l();a.save();n=0>n?Math.abs(b.minValue-n):0<b.minValue?n-b.minValue:Math.abs(b.minValue)+n;a.rotate(e(45+n/((b.maxValue-b.minValue)/270)));a.beginPath();a.moveTo(-r,-d);a.lineTo(-k,
|
||||
0);a.lineTo(-1,c);a.lineTo(1,c);a.lineTo(k,0);a.lineTo(r,-d);a.closePath();a.fillStyle=g(b.colors.needle.start,b.colors.needle.end,c-d);a.fill();a.beginPath();a.lineTo(-0.5,c);a.lineTo(-1,c);a.lineTo(-k,0);a.lineTo(-r,-d);a.lineTo(r/2-2,-d);a.closePath();a.fillStyle="rgba(255, 255, 255, 0.2)";a.fill();a.restore();l();a.beginPath();a.arc(0,0,m,0,2*Math.PI,!0);a.fillStyle=g("#f0f0f0","#ccc",m);a.fill();a.restore();a.beginPath();a.arc(0,0,h,0,2*Math.PI,!0);a.fillStyle=g("#e8e8e8","#f5f5f5",h);a.fill()}
|
||||
function L(){a.save();a.font=40*(f/200)+"px Led";var b=x(y),h=a.measureText("-"+x(0)).width,c=f-33*(f/100),g=0.12*f;a.save();var d=-h/2-0.025*f,e=c-g-0.04*f,h=h+0.05*f,g=g+0.07*f,k=0.025*f;a.beginPath();a.moveTo(d+k,e);a.lineTo(d+h-k,e);a.quadraticCurveTo(d+h,e,d+h,e+k);a.lineTo(d+h,e+g-k);a.quadraticCurveTo(d+h,e+g,d+h-k,e+g);a.lineTo(d+k,e+g);a.quadraticCurveTo(d,e+g,d,e+g-k);a.lineTo(d,e+k);a.quadraticCurveTo(d,e,d+k,e);a.closePath();d=a.createRadialGradient(0,c-0.12*f-0.025*f+(0.12*f+0.045*f)/
|
||||
2,f/10,0,c-0.12*f-0.025*f+(0.12*f+0.045*f)/2,f/5);d.addColorStop(0,"#888");d.addColorStop(1,"#666");a.strokeStyle=d;a.lineWidth=0.05*f;a.stroke();a.shadowBlur=0.012*f;a.shadowColor="rgba(0, 0, 0, 1)";a.fillStyle="#babab2";a.fill();a.restore();a.shadowOffsetX=0.004*f;a.shadowOffsetY=0.004*f;a.shadowBlur=0.012*f;a.shadowColor="rgba(0, 0, 0, 0.3)";a.fillStyle="#444";a.textAlign="center";a.fillText(b,-0,c);a.restore()}Gauge.Collection.push(this);this.config={renderTo:null,width:200,height:200,title:!1,
|
||||
maxValue:100,minValue:0,majorTicks:[],minorTicks:10,strokeTicks:!0,units:!1,valueFormat:{"int":3,dec:2},majorTicksFormat:{"int":1,dec:0},glow:!0,animation:{delay:10,duration:250,fn:"cycle"},colors:{plate:"#fff",majorTicks:"#444",minorTicks:"#666",title:"#888",units:"#888",numbers:"#444",needle:{start:"rgba(240, 128, 128, 1)",end:"rgba(255, 160, 122, .9)"}},highlights:[{from:20,to:60,color:"#eee"},{from:60,to:80,color:"#ccc"},{from:80,to:100,color:"#999"}]};var y=0,E=this,n=0,I=0,H=!1;this.setValue=
|
||||
function(a){n=b.animation?y:a;var d=(b.maxValue-b.minValue)/100;I=a>b.maxValue?b.maxValue+d:a<b.minValue?b.minValue-d:a;y=a;b.animation?k():this.draw();return this};this.setRawValue=function(a){n=y=a;this.draw();return this};this.clear=function(){y=n=I=this.config.minValue;this.draw();return this};this.getValue=function(){return y};this.onready=function(){};l(this.config,b);this.config.minValue=parseFloat(this.config.minValue);this.config.maxValue=parseFloat(this.config.maxValue);b=this.config;n=
|
||||
y=b.minValue;if(!b.renderTo)throw Error("Canvas element was not specified when creating the Gauge object!");var z=b.renderTo.tagName?b.renderTo:document.getElementById(b.renderTo),a=z.getContext("2d"),A,C,D,t,u,f,B;q();this.updateConfig=function(a){l(this.config,a);q();this.draw();return this};var M={linear:function(a){return a},quad:function(a){return Math.pow(a,2)},quint:function(a){return Math.pow(a,5)},cycle:function(a){return 1-Math.sin(Math.acos(a))},bounce:function(a){a:{a=1-a;for(var b=0,
|
||||
c=1;;b+=c,c/=2)if(a>=(7-4*b)/11){a=-Math.pow((11-6*b-11*a)/4,2)+Math.pow(c,2);break a}a=void 0}return 1-a},elastic:function(a){a=1-a;return 1-Math.pow(2,10*(a-1))*Math.cos(30*Math.PI/3*a)}},G=null;a.lineCap="round";this.draw=function(){if(!A.i8d){B.clearRect(-t,-u,C,D);B.save();var g={ctx:a};a=B;p();N();J();d();s();b.title&&(a.save(),a.font=24*(f/200)+"px Arial",a.fillStyle=b.colors.title,a.textAlign="center",a.fillText(b.title,0,-f/4.25),a.restore());b.units&&(a.save(),a.font=22*(f/200)+"px Arial",
|
||||
a.fillStyle=b.colors.units,a.textAlign="center",a.fillText(b.units,0,f/3.25),a.restore());A.i8d=!0;a=g.ctx;delete g.ctx}a.clearRect(-t,-u,C,D);a.save();a.drawImage(A,-t,-u,C,D);if(Gauge.initialized)L(),K(),H||(E.onready&&E.onready(),H=!0);else var e=setInterval(function(){Gauge.initialized&&(clearInterval(e),L(),K(),H||(E.onready&&E.onready(),H=!0))},10);return this}};Gauge.initialized=!1;
|
||||
(function(){var b=document,l=b.getElementsByTagName("head")[0],q=-1!=navigator.userAgent.toLocaleLowerCase().indexOf("msie"),v="@font-face {font-family: 'Led';src: url('fonts/digital-7-mono."+(q?"eot":"ttf")+"');}",k=b.createElement("style");k.type="text/css";if(q)l.appendChild(k),l=k.styleSheet,l.cssText=v;else{try{k.appendChild(b.createTextNode(v))}catch(e){k.cssText=v}l.appendChild(k);l=k.styleSheet?k.styleSheet:k.sheet||b.styleSheets[b.styleSheets.length-1]}var g=setInterval(function(){if(b.body){clearInterval(g);
|
||||
var e=b.createElement("div");e.style.fontFamily="Led";e.style.position="absolute";e.style.height=e.style.width=0;e.style.overflow="hidden";e.innerHTML=".";b.body.appendChild(e);setTimeout(function(){Gauge.initialized=!0;e.parentNode.removeChild(e)},250)}},1)})();Gauge.Collection=[];
|
||||
Gauge.Collection.get=function(b){if("string"==typeof b)for(var l=0,q=this.length;l<q;l++){if((this[l].config.renderTo.tagName?this[l].config.renderTo:document.getElementById(this[l].config.renderTo)).getAttribute("id")==b)return this[l]}else return"number"==typeof b?this[b]:null};function domReady(b){window.addEventListener?window.addEventListener("DOMContentLoaded",b,!1):window.attachEvent("onload",b)}
|
||||
domReady(function(){function b(b){for(var e=b[0],d=1,g=b.length;d<g;d++)e+=b[d].substr(0,1).toUpperCase()+b[d].substr(1,b[d].length-1);return e}for(var l=document.getElementsByTagName("canvas"),q=0,v=l.length;q<v;q++)if("canv-gauge"==l[q].getAttribute("data-type")){var k=l[q],e={},g,p=parseInt(k.getAttribute("width"),10),w=parseInt(k.getAttribute("height"),10);e.renderTo=k;p&&(e.width=p);w&&(e.height=w);p=0;for(w=k.attributes.length;p<w;p++)if(g=k.attributes.item(p).nodeName,"data-type"!=g&&"data-"==
|
||||
g.substr(0,5)){var d=g.substr(5,g.length-5).toLowerCase().split("-");if(g=k.getAttribute(g))switch(d[0]){case "colors":d[1]&&(e.colors||(e.colors={}),"needle"==d[1]?(d=g.split(/\s+/),e.colors.needle=d[0]&&d[1]?{start:d[0],end:d[1]}:g):(d.shift(),e.colors[b(d)]=g));break;case "highlights":e.highlights||(e.highlights=[]);g=g.match(/(?:(?:-?\d*\.)?(-?\d+){1,2} ){2}(?:(?:#|0x)?(?:[0-9A-F|a-f]){3,8}|rgba?\(.*?\))/g);for(var d=0,J=g.length;d<J;d++){var s=g[d].replace(/^\s+|\s+$/g,"").split(/\s+/),x={};
|
||||
s[0]&&""!=s[0]&&(x.from=s[0]);s[1]&&""!=s[1]&&(x.to=s[1]);s[2]&&""!=s[2]&&(x.color=s[2]);e.highlights.push(x)}break;case "animation":d[1]&&(e.animation||(e.animation={}),"fn"==d[1]&&/^\s*function\s*\(/.test(g)&&(g=eval("("+g+")")),e.animation[d[1]]=g);break;default:d=b(d);if("onready"==d)continue;if("majorTicks"==d)g=g.split(/\s+/);else if("strokeTicks"==d||"glow"==d)g="true"==g?!0:!1;else if("valueFormat"==d)if(g=g.split("."),2==g.length)g={"int":parseInt(g[0],10),dec:parseInt(g[1],10)};else continue;
|
||||
e[d]=g}}e=new Gauge(e);k.getAttribute("data-value")&&e.setRawValue(parseFloat(k.getAttribute("data-value")));k.getAttribute("data-onready")&&(e.onready=function(){eval(this.config.renderTo.getAttribute("data-onready"))});e.draw()}});window.Gauge=Gauge;
|
||||
)=====" ;
|
|
@ -0,0 +1,495 @@
|
|||
/*
|
||||
This code is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// Version 1.3, 04.08.2020, AK-Homberger
|
||||
|
||||
#define ESP32_CAN_TX_PIN GPIO_NUM_5 // Set CAN TX port to 5 (Caution!!! Pin 2 before)
|
||||
#define ESP32_CAN_RX_PIN GPIO_NUM_4 // Set CAN RX port to 4
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <NMEA2000_CAN.h> // This will automatically choose right CAN library and create suitable NMEA2000 object
|
||||
#include <Seasmart.h>
|
||||
#include <N2kMessages.h>
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <Preferences.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include "N2kDataToNMEA0183.h"
|
||||
#include "List.h"
|
||||
#include "index_html.h"
|
||||
#include "BoatData.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 HighTempAlarm 12 // Alarm level for fridge temperature (higher)
|
||||
#define LowVoltageAlarm 11 // Alarm level for battery voltage (lower)
|
||||
|
||||
#define ADC_Calibration_Value 34.3 // The real value depends on the true resistor values for the ADC input (100K / 27 K)
|
||||
|
||||
#define WLAN_CLIENT 0 // Set to 1 to enable client network. 0 to act as AP only
|
||||
|
||||
// Wifi cofiguration Client and Access Point
|
||||
const char *AP_ssid = "MyESP32"; // ESP32 as AP
|
||||
const char *CL_ssid = "MyWLAN"; // ESP32 as client in network
|
||||
|
||||
const char *AP_password = "appassw"; // AP password
|
||||
const char *CL_password = "clientpw"; // Client password
|
||||
|
||||
// Put IP address details here
|
||||
IPAddress AP_local_ip(192, 168, 15, 1); // Static address for AP
|
||||
IPAddress AP_gateway(192, 168, 15, 1);
|
||||
IPAddress AP_subnet(255, 255, 255, 0);
|
||||
|
||||
IPAddress CL_local_ip(192, 168, 1, 10); // Static address for Client Network. Please adjust to your AP IP and DHCP range!
|
||||
IPAddress CL_gateway(192, 168, 1, 1);
|
||||
IPAddress CL_subnet(255, 255, 255, 0);
|
||||
|
||||
int wifiType = 0; // 0= Client 1= AP
|
||||
|
||||
const uint16_t ServerPort = 2222; // Define the port, where server sends data. Use this e.g. on OpenCPN. Use 39150 for Navionis AIS
|
||||
|
||||
// UPD broadcast for Navionics, OpenCPN, etc.
|
||||
const char * udpAddress = "192.168.15.255"; // UDP broadcast address. Should be the network of the ESP32 AP (please check!)
|
||||
const int udpPort = 2000; // port 2000 lets think Navionics it is an DY WLN10 device
|
||||
|
||||
// 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
|
||||
|
||||
int alarmstate = false; // Alarm state (low voltage/temperature)
|
||||
int acknowledge = false; // Acknowledge for alarm, button pressed
|
||||
|
||||
|
||||
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 server(ServerPort, MaxClients);
|
||||
WiFiServer json(90);
|
||||
|
||||
using tWiFiClientPtr = std::shared_ptr<WiFiClient>;
|
||||
LinkedList<tWiFiClientPtr> clients;
|
||||
|
||||
tN2kDataToNMEA0183 tN2kDataToNMEA0183(&NMEA2000, 0);
|
||||
|
||||
// Set the information for other bus devices, which messages we support
|
||||
const unsigned long TransmitMessages[] PROGMEM = {127489L, // Engine dynamic
|
||||
0
|
||||
};
|
||||
const unsigned long ReceiveMessages[] PROGMEM = {/*126992L,*/ // System time
|
||||
127250L, // Heading
|
||||
127258L, // Magnetic variation
|
||||
128259UL,// Boat speed
|
||||
128267UL,// Depth
|
||||
129025UL,// Position
|
||||
129026L, // COG and SOG
|
||||
129029L, // GNSS
|
||||
130306L, // Wind
|
||||
128275UL,// Log
|
||||
127245UL,// Rudder
|
||||
0
|
||||
};
|
||||
|
||||
// Forward declarations
|
||||
void HandleNMEA2000Msg(const tN2kMsg &N2kMsg);
|
||||
void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg);
|
||||
|
||||
|
||||
WebServer webserver(80);
|
||||
|
||||
#define MiscSendOffset 120
|
||||
#define SlowDataUpdatePeriod 1000 // Time between CAN Messages sent
|
||||
|
||||
|
||||
|
||||
// Serial port 2 config (GPIO 16)
|
||||
const int baudrate = 38400;
|
||||
const int rs_config = SERIAL_8N1;
|
||||
|
||||
// Buffer config
|
||||
|
||||
#define MAX_NMEA0183_MESSAGE_SIZE 150 // For AIS
|
||||
char buff[MAX_NMEA0183_MESSAGE_SIZE];
|
||||
|
||||
// NMEA message for AIS receiving and multiplexing
|
||||
tNMEA0183Msg NMEA0183Msg;
|
||||
tNMEA0183 NMEA0183;
|
||||
|
||||
|
||||
void debug_log(char* str) {
|
||||
#if ENABLE_DEBUG_LOG == 1
|
||||
Serial.println(str);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Ereignis_Index() // Wenn "http://<ip address>/" aufgerufen wurde
|
||||
{
|
||||
webserver.send(200, "text/html", indexHTML); //dann Index Webseite senden
|
||||
}
|
||||
|
||||
void Ereignis_js() // Wenn "http://<ip address>/gauge.min.js" aufgerufen wurde
|
||||
{
|
||||
webserver.send(200, "text/html", gauge); // dann gauge.min.js senden
|
||||
}
|
||||
|
||||
|
||||
void handleNotFound()
|
||||
{
|
||||
webserver.send(404, "text/plain", "File Not Found\n\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setup() {
|
||||
|
||||
uint8_t chipid[6];
|
||||
uint32_t id = 0;
|
||||
int i = 0;
|
||||
int wifi_retry = 0;
|
||||
|
||||
|
||||
// Init USB serial port
|
||||
Serial.begin(115200);
|
||||
|
||||
// Init AIS serial port 2
|
||||
Serial2.begin(baudrate, rs_config);
|
||||
NMEA0183.Begin(&Serial2, 3, baudrate);
|
||||
|
||||
if (WLAN_CLIENT == 1) {
|
||||
Serial.println("Start WLAN Client"); // WiFi Mode Client
|
||||
|
||||
WiFi.config(CL_local_ip, CL_gateway, CL_subnet, CL_gateway);
|
||||
delay(100);
|
||||
WiFi.begin(CL_ssid, CL_password);
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED && wifi_retry < 20) { // Check connection, try 10 seconds
|
||||
wifi_retry++;
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
}
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) { // No client connection start AP
|
||||
// Init wifi connection
|
||||
Serial.println("Start WLAN AP"); // WiFi Mode AP
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(AP_ssid, AP_password);
|
||||
delay(100);
|
||||
WiFi.softAPConfig(AP_local_ip, AP_gateway, AP_subnet);
|
||||
IPAddress IP = WiFi.softAPIP();
|
||||
Serial.println("");
|
||||
Serial.print("AP IP address: ");
|
||||
Serial.println(IP);
|
||||
wifiType = 1;
|
||||
|
||||
} else { // Wifi Client connection was sucessfull
|
||||
|
||||
Serial.println("");
|
||||
Serial.println("WiFi client connected");
|
||||
Serial.println("IP client address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
|
||||
// Start TCP server
|
||||
server.begin();
|
||||
|
||||
// Start JSON server
|
||||
json.begin();
|
||||
|
||||
|
||||
// Start Web Server
|
||||
webserver.on("/", Ereignis_Index);
|
||||
webserver.on("/gauge.min.js", Ereignis_js);
|
||||
webserver.onNotFound(handleNotFound);
|
||||
|
||||
webserver.begin();
|
||||
Serial.println("HTTP server started");
|
||||
|
||||
// Reserve enough buffer for sending all messages. This does not work on small memory devices like Uno or Mega
|
||||
|
||||
NMEA2000.SetN2kCANMsgBufSize(8);
|
||||
NMEA2000.SetN2kCANReceiveFrameBufSize(250);
|
||||
NMEA2000.SetN2kCANSendFrameBufSize(250);
|
||||
|
||||
esp_efuse_read_mac(chipid);
|
||||
for (i = 0; i < 6; i++) id += (chipid[i] << (7 * i));
|
||||
|
||||
// Set product information
|
||||
NMEA2000.SetProductInformation("1", // Manufacturer's Model serial code
|
||||
100, // Manufacturer's product code
|
||||
"NMEA 2000 WiFi Gateway", // Manufacturer's Model ID
|
||||
"1.0.2.25 (2019-07-07)", // Manufacturer's Software version code
|
||||
"1.0.2.0 (2019-07-07)" // Manufacturer's Model version
|
||||
);
|
||||
// Set device information
|
||||
NMEA2000.SetDeviceInformation(id, // Unique number. Use e.g. Serial number. Id is generated from MAC-Address
|
||||
130, // Device function=Analog to NMEA 2000 Gateway. See codes on http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
|
||||
25, // Device class=Inter/Intranetwork Device. See codes on http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
|
||||
2046 // Just choosen free from code list on http://www.nmea.org/Assets/20121020%20nmea%202000%20registration%20list.pdf
|
||||
);
|
||||
|
||||
// If you also want to see all traffic on the bus use N2km_ListenAndNode instead of N2km_NodeOnly below
|
||||
|
||||
NMEA2000.SetForwardType(tNMEA2000::fwdt_Text); // Show in clear text. Leave uncommented for default Actisense format.
|
||||
|
||||
preferences.begin("nvs", false); // Open nonvolatile storage (nvs)
|
||||
NodeAddress = preferences.getInt("LastNodeAddress", 32); // Read stored last NodeAddress, default 32
|
||||
preferences.end();
|
||||
|
||||
Serial.printf("NodeAddress=%d\n", NodeAddress);
|
||||
|
||||
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, NodeAddress);
|
||||
|
||||
NMEA2000.ExtendTransmitMessages(TransmitMessages);
|
||||
NMEA2000.ExtendReceiveMessages(ReceiveMessages);
|
||||
NMEA2000.AttachMsgHandler(&tN2kDataToNMEA0183); // NMEA 2000 -> NMEA 0183 conversion
|
||||
NMEA2000.SetMsgHandler(HandleNMEA2000Msg); // Also send all NMEA2000 messages in SeaSmart format
|
||||
|
||||
tN2kDataToNMEA0183.SetSendNMEA0183MessageCallback(SendNMEA0183Message);
|
||||
|
||||
NMEA2000.Open();
|
||||
|
||||
}
|
||||
//*****************************************************************************
|
||||
|
||||
|
||||
|
||||
//*****************************************************************************
|
||||
void SendBufToClients(const char *buf) {
|
||||
for (auto it = clients.begin() ; it != clients.end(); it++) {
|
||||
if ( (*it) != NULL && (*it)->connected() ) {
|
||||
(*it)->println(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_NMEA2000_MESSAGE_SEASMART_SIZE 500
|
||||
//*****************************************************************************
|
||||
//NMEA 2000 message handler
|
||||
void HandleNMEA2000Msg(const tN2kMsg &N2kMsg) {
|
||||
|
||||
|
||||
if ( !SendSeaSmart ) return;
|
||||
|
||||
char buf[MAX_NMEA2000_MESSAGE_SEASMART_SIZE];
|
||||
if ( N2kToSeasmart(N2kMsg, millis(), buf, MAX_NMEA2000_MESSAGE_SEASMART_SIZE) == 0 ) return;
|
||||
SendBufToClients(buf);
|
||||
}
|
||||
|
||||
|
||||
//*****************************************************************************
|
||||
void SendNMEA0183Message(const tNMEA0183Msg &NMEA0183Msg) {
|
||||
if ( !SendNMEA0183Conversion ) return;
|
||||
|
||||
char buf[MAX_NMEA0183_MESSAGE_SIZE];
|
||||
if ( !NMEA0183Msg.GetMessage(buf, MAX_NMEA0183_MESSAGE_SIZE) ) return;
|
||||
SendBufToClients(buf);
|
||||
}
|
||||
|
||||
|
||||
bool IsTimeToUpdate(unsigned long NextUpdate) {
|
||||
return (NextUpdate < millis());
|
||||
}
|
||||
unsigned long InitNextUpdate(unsigned long Period, unsigned long Offset = 0) {
|
||||
return millis() + Period + Offset;
|
||||
}
|
||||
|
||||
void SetNextUpdate(unsigned long &NextUpdate, unsigned long Period) {
|
||||
while ( NextUpdate < millis() ) NextUpdate += Period;
|
||||
}
|
||||
|
||||
|
||||
void SendN2kEngine() {
|
||||
static unsigned long SlowDataUpdated = InitNextUpdate(SlowDataUpdatePeriod, MiscSendOffset);
|
||||
tN2kMsg N2kMsg;
|
||||
|
||||
if ( IsTimeToUpdate(SlowDataUpdated) ) {
|
||||
SetNextUpdate(SlowDataUpdated, SlowDataUpdatePeriod);
|
||||
|
||||
SetN2kEngineDynamicParam(N2kMsg, 0, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, N2kInt8NA, N2kInt8NA, true);
|
||||
NMEA2000.SendMsg(N2kMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//*****************************************************************************
|
||||
void AddClient(WiFiClient &client) {
|
||||
Serial.println("New Client.");
|
||||
clients.push_back(tWiFiClientPtr(new WiFiClient(client)));
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void StopClient(LinkedList<tWiFiClientPtr>::iterator &it) {
|
||||
Serial.println("Client Disconnected.");
|
||||
(*it)->stop();
|
||||
it = clients.erase(it);
|
||||
}
|
||||
|
||||
//*****************************************************************************
|
||||
void CheckConnections() {
|
||||
WiFiClient client = server.available(); // listen for incoming clients
|
||||
|
||||
if ( client ) AddClient(client);
|
||||
|
||||
for (auto it = clients.begin(); it != clients.end(); it++) {
|
||||
if ( (*it) != NULL ) {
|
||||
if ( !(*it)->connected() ) {
|
||||
StopClient(it);
|
||||
} else {
|
||||
if ( (*it)->available() ) {
|
||||
char c = (*it)->read();
|
||||
if ( c == 0x03 ) StopClient(it); // Close connection by ctrl-c
|
||||
}
|
||||
}
|
||||
} else {
|
||||
it = clients.erase(it); // Should have been erased by StopClient
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void loop() {
|
||||
unsigned int size;
|
||||
int wifi_retry;
|
||||
|
||||
webserver.handleClient();
|
||||
handle_json();
|
||||
|
||||
if (NMEA0183.GetMessage(NMEA0183Msg)) { // Get AIS NMEA sentences from serial2
|
||||
|
||||
SendNMEA0183Message(NMEA0183Msg); // Send to TCP clients
|
||||
|
||||
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);
|
||||
udp.beginPacket(udpAddress, udpPort); // Send to UDP
|
||||
udp.write((byte*)buff, size);
|
||||
udp.endPacket();
|
||||
#endif
|
||||
}
|
||||
|
||||
SendN2kEngine();
|
||||
CheckConnections();
|
||||
NMEA2000.ParseMessages();
|
||||
|
||||
int SourceAddress = NMEA2000.GetN2kSource();
|
||||
if (SourceAddress != NodeAddress) { // Save potentially changed Source Address to NVS memory
|
||||
NodeAddress = SourceAddress; // Set new Node Address (to save only once)
|
||||
preferences.begin("nvs", false);
|
||||
preferences.putInt("LastNodeAddress", SourceAddress);
|
||||
preferences.end();
|
||||
Serial.printf("Address Change: New Address=%d\n", SourceAddress);
|
||||
}
|
||||
|
||||
tN2kDataToNMEA0183.Update(&BoatData);
|
||||
|
||||
// Dummy to empty input buffer to avoid board to stuck with e.g. NMEA Reader
|
||||
if ( Serial.available() ) {
|
||||
Serial.read();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (wifiType == 0) { // Check connection if working as client
|
||||
wifi_retry = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && wifi_retry < 5 ) { // Connection lost, 5 tries to reconnect
|
||||
wifi_retry++;
|
||||
Serial.println("WiFi not connected. Try to reconnect");
|
||||
WiFi.disconnect();
|
||||
WiFi.mode(WIFI_OFF);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(CL_ssid, CL_password);
|
||||
delay(100);
|
||||
}
|
||||
if (wifi_retry >= 5) {
|
||||
Serial.println("\nReboot"); // Did not work -> restart ESP32
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue