1
0
mirror of https://github.com/thooge/esp32-nmea2000-obp60.git synced 2026-02-10 22:53:07 +01:00

70 Commits

Author SHA1 Message Date
Norbert Walter
06dcd14bdc Merge branch 'wellenvogel:master' into master 2026-02-02 21:39:00 +01:00
norbert-walter
352009073e Setup to remote project 2026-02-02 21:38:14 +01:00
norbert-walter
aaa4007ae6 Merge remote-tracking branch 'origin/master' 2026-02-02 21:29:23 +01:00
norbert-walter
5b087e61d2 Merge branch 'autopilot' 2026-02-02 21:28:39 +01:00
norbert-walter
753e87068f New function for backlight - 3 brigjhtness steps 2026-02-02 21:26:43 +01:00
Norbert Walter
7445d2db24 Merge pull request #222 from Scorgan01/CalibrationData-v31
Data calibration: Move calibration of boat data to main loop to make calibrated data available everywhere
2026-02-02 18:15:23 +01:00
Norbert Walter
f517abf001 Merge pull request #220 from TobiasE-github/master
use 9.95 as the limit for formatting numbers below and above 10
2026-02-02 18:15:04 +01:00
norbert-walter
6a56a8fb56 Fix I2C adresses for INA219 2026-02-02 17:00:27 +01:00
norbert-walter
576f0a0d4f Fix for LED brightness and add Page Autopilot 2026-02-02 16:29:18 +01:00
norbert-walter
1de936fd47 Typo 2026-02-01 18:09:44 +01:00
TobiasE-github
05dad7a23c Merge branch 'norbert-walter:master' into master 2026-01-31 08:32:06 +01:00
norbert-walter
d19da640ae Add PageDigitalOut 2026-01-30 17:26:10 +01:00
norbert-walter
1da26a90ec Auto stash before merge of "master" and "origin/master" 2026-01-30 10:14:11 +01:00
Ulrich Meine
cb2b85d505 Data Calibration: Extend no. of calibration instances from 3 to 4 2026-01-17 13:45:41 +01:00
Ulrich Meine
cc1d07fac0 True Wind Calculation: change wind angle range to [0..360] temporarily for proper display on pages 2026-01-17 13:07:48 +01:00
Ulrich Meine
b8e64ff64c Update data calibration: calibration in main loop on boat data values:
- modified code to calibrate affected boat data each second in main obp60task loop;
- removed individual calibration call in all pages
- moved code from BoatDataCalibration to OBPDataOperations
- added DBS and HDT to list of supported data calibration types
- changed output format for wind angles from [-180..180] to [0..360], because negative value are not displayed properly on pages
2026-01-17 12:25:57 +01:00
TobiasE-github
e4214beefe general but flexible limits for formatting numbers 2026-01-17 11:02:25 +01:00
TobiasE-github
02c611ead0 use 9.95 as the limit for formatting numbers below and above 10 2026-01-15 17:56:52 +01:00
norbert-walter
0b79b7e2ef Modify PageDigitalOut 2026-01-15 15:08:42 +01:00
wellenvogel
dd3a4f5093 fix a bug in the actisense reader, version 20251126 2025-11-26 18:02:41 +01:00
wellenvogel
32099487fa prepare relase 20251007 2025-10-07 13:02:19 +02:00
wellenvogel
18b46ae5a0 prepare relase 20251007 2025-10-07 13:00:28 +02:00
wellenvogel
fb62e41bd9 prepare relase 20251007 2025-10-07 12:58:28 +02:00
wellenvogel
9211b13dcd #113: add env4 to cibuild 2025-09-30 20:09:13 +02:00
wellenvogel
6da87e4455 add buildname to ci output file names, correctly set initial build name 2025-09-30 20:03:45 +02:00
wellenvogel
5493c9695c add buildname to cibuild, use the firmware name for file names 2025-09-30 15:27:50 +02:00
wellenvogel
034a338a81 set default for serial enable low = 0 2025-09-30 12:16:51 +02:00
wellenvogel
3cd508a239 better description of pins for cibuild 2025-09-30 12:13:06 +02:00
wellenvogel
68239f6199 add fixed baud to cibuild, allow enable pin also for pure rx/tx serial 2025-09-29 19:34:35 +02:00
wellenvogel
b683413129 #117: add handling for an output enable pin for serial channels 2025-09-29 19:13:02 +02:00
wellenvogel
566d84d3e6 correctly handle ifdefs for SHT4X 2025-09-29 18:04:04 +02:00
wellenvogel
432a10bfb1 correctly send 130311 for QMP6988 2025-09-29 17:54:16 +02:00
wellenvogel
32862b9e29 avoid creating unmapped XDR entries for unset N2K values 2025-09-29 17:53:14 +02:00
wellenvogel
c21592599f re-add GwSHTXX* 2025-09-29 17:51:41 +02:00
wellenvogel
fddc3c742b allow to switch PGN 130311 for sensors on/off, simplify config.json for i2csensors 2025-09-29 17:05:48 +02:00
wellenvogel
9831f8da85 add code for SHT4X, ENV4 2025-09-29 12:43:21 +02:00
wellenvogel
8bf8ada30e #111: allow to add extra scripts with custom_script 2025-09-29 09:35:14 +02:00
wellenvogel
6266f85db6 #116: sda and scl are swapped when building with online service 2025-09-28 19:58:54 +02:00
wellenvogel
70ad5cc903 omit external nmea2ktoais 2025-09-28 19:28:30 +02:00
wellenvogel
df9b377b31 move the nmea2ktoais functions back into our code base. 2025-09-28 18:47:17 +02:00
wellenvogel
d0966159c0 separate building AIS class 24 2025-09-28 18:37:26 +02:00
wellenvogel
3f22164b1d use forked NMEA2000 lib 2025-09-26 20:00:30 +02:00
wellenvogel
60d06cd9ee remove wrong addEmptyField 2025-09-26 19:57:08 +02:00
wellenvogel
9633abc481 correct timestamp for pass format 2025-09-26 19:56:44 +02:00
wellenvogel
24502e423e set talker/channel when converting AIS from N2K, new lib version n2ktoais 2025-09-25 19:25:54 +02:00
wellenvogel
4a442c6dfb Merge branch 'master' into new-n2ktoais 2025-09-25 17:38:23 +02:00
wellenvogel
448af708d4 fill timestamp for actisense with frame timestamp in sendN2K 2025-09-25 17:37:59 +02:00
wellenvogel
78aafd308a seasmart for sendN2K 2025-09-24 20:30:44 +02:00
wellenvogel
b7cd8c6bdd add pass format to sendN2K 2025-09-24 18:29:22 +02:00
wellenvogel
e5968b8480 some error handling and stats to sendN2K 2025-09-24 18:10:06 +02:00
wellenvogel
e5c4f0b179 add actisense mode to sendN2K 2025-09-23 20:33:34 +02:00
wellenvogel
4b03fa5a23 add filter to sendN2K 2025-09-23 19:07:53 +02:00
wellenvogel
13eac9508d add some helper tools for converting candumps 2025-09-23 18:29:43 +02:00
wellenvogel
ec807c6925 intermediate: adapt handling to new n2ktoais lib 2025-09-23 13:05:28 +02:00
wellenvogel
3df2571ca2 intermediate: prepare AIS class 21 to 129041 2025-09-21 21:02:31 +02:00
wellenvogel
e578b428c9 more parameters for AIS type 21 2025-09-21 19:32:02 +02:00
wellenvogel
6e0d56316b add some testing tools 2025-09-21 16:40:24 +02:00
wellenvogel
7fd1457296 add conversion from NMEA0183 AIS type 21 (aton) to PGN 129041 2025-09-19 20:43:28 +02:00
wellenvogel
e8c5440a79 #114: correctly distinguish between type 1..3 ais messages when converting from N2K 2025-09-19 12:13:41 +02:00
wellenvogel
95df5858ac #112: clearify licenses to be GPL v2 or later 2025-09-17 20:05:45 +02:00
wellenvogel
d5a9568b67 make birectional channels the default, clean up some channel handling 2025-09-17 17:57:03 +02:00
wellenvogel
3d131c7d98 correctly set the mode/type for serial channels 2025-09-17 17:40:28 +02:00
wellenvogel
7ebd582ca0 set serial mode to RX for M5 GPS base 2025-09-17 17:39:57 +02:00
wellenvogel
85f49857da Merge branch 'master' into pr_115 2025-09-17 15:46:33 +02:00
wellenvogel
976e8172e3 Merge branch 'master' of github.com:wellenvogel/esp32-nmea2000 2025-09-16 20:22:50 +02:00
wellenvogel
47fcb26961 add a generic s3 (devkit-m) to cibuild 2025-09-16 20:22:09 +02:00
free-x
da6022cb28 #110: add GPS v1.1 unit 2025-09-11 20:23:52 +02:00
free-x
2c97eacd76 minor corrections 2025-09-11 19:36:09 +02:00
free-x
370fd47deb add GPS V2 Base to webinstall configs 2025-09-09 15:03:48 +02:00
free-x
37d945a0ea intermediate GPS 2.0 base 2025-09-09 13:07:16 +02:00
93 changed files with 3469 additions and 1267 deletions

View File

@@ -62,5 +62,5 @@ jobs:
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ steps.version.outputs.version}}
file: ./.pio/build/*/*-{all,update}.bin
file: ./.pio/build/*/*${{ steps.version.outputs.version }}*-{all,update}.bin
file_glob: true

View File

@@ -43,6 +43,10 @@ What is included
For the details of the mapped PGNs and NMEA sentences refer to [Conversions](doc/Conversions.pdf).
License
-------
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either [version 2 of the License](LICENSE), or (at your option) any later version.
Hardware
--------
The software is prepared to run on different kinds of ESP32 based modules and accessoirs. For some of them prebuild binaries are available that only need to be flashed, others would require to add some definitions of the used PINs and features and to build the binary.
@@ -170,6 +174,25 @@ For details refer to the [example description](lib/exampletask/Readme.md).
Changelog
---------
[20251126](../../releases/tag/20251126)
* fix a bug in the Actisense reader that could lead to an endless loop (making the device completely non responsive)
* upgrade to 4.24.1 of the NMEA2000 library (2025/11/01) - refer to the [changes](https://github.com/ttlappalainen/NMEA2000/blob/master/Documents/src/changes.md) - Especially UTF8 support
*********
[20251007](../../releases/tag/20251007)
*********
* add AIS Aton translations (PGN 129041 <-> Ais class 21)
* improved mapping of AIS transducer information (NMEA2000) to AIS channel and Talker on NMEA0183
* use a forked version of the NMEA2000 library (as an intermediate workaround)
* [#114](../../issues/114) correctly translate AIS type 1/3 from PGN 129038
* add support for a generic S3 build in the build UI
* [#117](../../issues/117) add support for a transmit enable pin for RS 485 conections (also in the build UI)
* [#116](../../issues/116) SDA and SCL are swapped in the build UI
* [#112](../../issues/112) clearify licenses
* [#110](../../issues/110) / [#115](../../pull/115) support for the M5 GPS unit v1.1
* [#102](../../issues/102) optimize Wifi reconnect handling
* [#111](../../pull/111) allow for a custom python build script
* [#113](../../issues/113) support for M5 stack Env4
[20250305](../../releases/tag/20250305)
*********
* better handling for reconnect to a raspberry pi after reset [#102](../../issues/102)

Binary file not shown.

Binary file not shown.

View File

@@ -10,7 +10,7 @@ from datetime import datetime
import re
import pprint
from platformio.project.config import ProjectConfig
from platformio.project.exception import InvalidProjectConfError
Import("env")
#print(env.Dump())
@@ -104,17 +104,7 @@ def writeFileIfChanged(fileName,data):
return True
def mergeConfig(base,other):
try:
customconfig = env.GetProjectOption("custom_config")
except InvalidProjectConfError:
customconfig = None
for bdir in other:
if customconfig and os.path.exists(os.path.join(bdir,customconfig)):
cname=os.path.join(bdir,customconfig)
print("merge custom config {}".format(cname))
with open(cname,'rb') as ah:
base += json.load(ah)
continue
cname=os.path.join(bdir,"config.json")
if os.path.exists(cname):
print("merge config %s"%cname)
@@ -284,9 +274,9 @@ class Grove:
def _ss(self,z=False):
if z:
return self.name
return self.name if self.name != 'Z' else ''
return self.name if self.name is not 'Z' else ''
def _suffix(self):
return '_'+self.name if self.name != 'Z' else ''
return '_'+self.name if self.name is not 'Z' else ''
def replace(self,line):
if line is None:
return line
@@ -526,17 +516,16 @@ env.Append(
)
#script does not run on clean yet - maybe in the future
env.AddPostAction("clean",cleangenerated)
#look for extra task scripts and include them here
for taskdir in userTaskDirs:
script = os.path.join(taskdir, "extra_task.py")
extraScripts=getFileList(getOption(env,'custom_script',toArray=True))
for script in extraScripts:
if os.path.isfile(script):
taskname = os.path.basename(os.path.normpath(taskdir))
print("#extra task script for '{}'".format(taskname))
print(f"#extra {script}")
with open(script) as fh:
try:
code = compile(fh.read(), taskname, 'exec')
except SyntaxError:
print("#ERROR: script does not compile")
code = compile(fh.read(), script, 'exec')
except SyntaxError as e:
print(f"#ERROR: script {script} does not compile: {e}")
continue
exec(code)
else:
print(f"#ERROR: script {script} not found")

View File

@@ -627,7 +627,7 @@ void AisDecoder::decodeType21(PayloadBuffer &_buffer, unsigned int _uMsgType, in
}
// decode message fields (binary buffer has to go through all fields, but some fields are not used)
_buffer.getUnsignedValue(2); // repeatIndicator
auto repeat=_buffer.getUnsignedValue(2); // repeatIndicator
auto mmsi = _buffer.getUnsignedValue(30);
auto aidType = _buffer.getUnsignedValue(5);
auto name = _buffer.getString(120);
@@ -640,11 +640,11 @@ void AisDecoder::decodeType21(PayloadBuffer &_buffer, unsigned int _uMsgType, in
auto toStarboard = _buffer.getUnsignedValue(6);
_buffer.getUnsignedValue(4); // epfd type
_buffer.getUnsignedValue(6); // timestamp
_buffer.getBoolValue(); // off position
auto timestamp=_buffer.getUnsignedValue(6); // timestamp
auto offPosition=_buffer.getBoolValue(); // off position
_buffer.getUnsignedValue(8); // reserved
_buffer.getBoolValue(); // RAIM
_buffer.getBoolValue(); // virtual aid
auto raim=_buffer.getBoolValue(); // RAIM
auto virtualAton=_buffer.getBoolValue(); // virtual aid
_buffer.getBoolValue(); // assigned mode
_buffer.getUnsignedValue(1); // spare
@@ -654,7 +654,9 @@ void AisDecoder::decodeType21(PayloadBuffer &_buffer, unsigned int _uMsgType, in
nameExt = _buffer.getString(88);
}
onType21(mmsi, aidType, name + nameExt, posAccuracy, posLon, posLat, toBow, toStern, toPort, toStarboard);
onType21(mmsi, aidType, name + nameExt, posAccuracy, posLon, posLat,
toBow, toStern, toPort, toStarboard,
repeat,timestamp, raim, virtualAton, offPosition);
}
/* decode Voyage Report and Static Data (type nibble already pulled from buffer) */

View File

@@ -297,7 +297,8 @@ namespace AIS
bool assigned, unsigned int repeat, bool raim) = 0;
virtual void onType21(unsigned int _uMmsi, unsigned int _uAidType, const std::string &_strName, bool _bPosAccuracy, int _iPosLon, int _iPosLat,
unsigned int _uToBow, unsigned int _uToStern, unsigned int _uToPort, unsigned int _uToStarboard) = 0;
unsigned int _uToBow, unsigned int _uToStern, unsigned int _uToPort, unsigned int _uToStarboard,
unsigned int repeat,unsigned int timestamp, bool raim, bool virtualAton, bool offPosition) = 0;
virtual void onType24A(unsigned int _uMsgType, unsigned int _repeat, unsigned int _uMmsi, const std::string &_strName) = 0;

View File

@@ -14,6 +14,9 @@
#define LOGLEVEL GwLog::DEBUG
#endif
#endif
#ifdef GWBUILD_NAME
#define FIRMWARE_TYPE GWSTRINGIFY(GWBUILD_NAME)
#else
#define FIRMWARE_TYPE GWSTRINGIFY(PIO_ENV_BUILD)
#endif
#define IDF_VERSION GWSTRINGIFY(ESP_IDF_VERSION_MAJOR) "." GWSTRINGIFY(ESP_IDF_VERSION_MINOR) "." GWSTRINGIFY(ESP_IDF_VERSION_PATCH)

View File

@@ -249,3 +249,16 @@ unsigned long GwChannel::countTx(){
if (! countOut) return 0UL;
return countOut->getGlobal();
}
String GwChannel::typeString(int type){
switch (type){
case GWSERIAL_TYPE_UNI:
return "UNI";
case GWSERIAL_TYPE_BI:
return "BI";
case GWSERIAL_TYPE_RX:
return "RX";
case GWSERIAL_TYPE_TX:
return "TX";
}
return "UNKNOWN";
}

View File

@@ -77,7 +77,8 @@ class GwChannel{
if (maxSourceId < 0) return source == sourceId;
return (source >= sourceId && source <= maxSourceId);
}
String getMode(){return impl->getMode();}
static String typeString(int type);
String getMode(){return typeString(impl->getType());}
int getMinId(){return sourceId;};
};

View File

@@ -1,10 +1,11 @@
#pragma once
#include "GwBuffer.h"
#include "GwChannelModes.h"
class GwChannelInterface{
public:
virtual void loop(bool handleRead,bool handleWrite)=0;
virtual void readMessages(GwMessageFetcher *writer)=0;
virtual size_t sendToClients(const char *buffer, int sourceId, bool partial=false)=0;
virtual Stream * getStream(bool partialWrites){ return NULL;}
virtual String getMode(){return "UNKNOWN";}
virtual int getType(){ return GWSERIAL_TYPE_BI;} //return the numeric type
};

View File

@@ -15,8 +15,10 @@ class SerInit{
int tx=-1;
int mode=-1;
int fixedBaud=-1;
SerInit(int s,int r,int t, int m, int b=-1):
serial(s),rx(r),tx(t),mode(m),fixedBaud(b){}
int ena=-1;
int elow=1;
SerInit(int s,int r,int t, int m, int b=-1,int en=-1,int el=-1):
serial(s),rx(r),tx(t),mode(m),fixedBaud(b),ena(en),elow(el){}
};
std::vector<SerInit> serialInits;
@@ -47,11 +49,20 @@ static int typeFromMode(const char *mode){
#ifndef GWSERIAL_RX
#define GWSERIAL_RX -1
#endif
#ifndef GWSERIAL_ENA
#define GWSERIAL_ENA -1
#endif
#ifndef GWSERIAL_ELO
#define GWSERIAL_ELO 0
#endif
#ifndef GWSERIAL_BAUD
#define GWSERIAL_BAUD -1
#endif
#ifdef GWSERIAL_TYPE
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, GWSERIAL_TYPE)
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, GWSERIAL_TYPE,GWSERIAL_BAUD,GWSERIAL_ENA,GWSERIAL_ELO)
#else
#ifdef GWSERIAL_MODE
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, typeFromMode(GWSERIAL_MODE))
CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, typeFromMode(GWSERIAL_MODE),GWSERIAL_BAUD,GWSERIAL_ENA,GWSERIAL_ELO)
#endif
#endif
// serial 2
@@ -61,11 +72,20 @@ CFG_SERIAL(SERIAL1_CHANNEL_ID, GWSERIAL_RX, GWSERIAL_TX, typeFromMode(GWSERIAL_M
#ifndef GWSERIAL2_RX
#define GWSERIAL2_RX -1
#endif
#ifndef GWSERIAL2_ENA
#define GWSERIAL2_ENA -1
#endif
#ifndef GWSERIAL2_ELO
#define GWSERIAL2_ELO 0
#endif
#ifndef GWSERIAL2_BAUD
#define GWSERIAL2_BAUD -1
#endif
#ifdef GWSERIAL2_TYPE
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, GWSERIAL2_TYPE)
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, GWSERIAL2_TYPE,GWSERIAL2_BAUD,GWSERIAL2_ENA,GWSERIAL2_ELO)
#else
#ifdef GWSERIAL2_MODE
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, typeFromMode(GWSERIAL2_MODE))
CFG_SERIAL(SERIAL2_CHANNEL_ID, GWSERIAL2_RX, GWSERIAL2_TX, typeFromMode(GWSERIAL2_MODE),GWSERIAL2_BAUD,GWSERIAL2_ENA,GWSERIAL2_ELO)
#endif
#endif
class GwSerialLog : public GwLogWriter
@@ -285,8 +305,8 @@ static ChannelParam channelParameters[]={
};
template<typename T>
GwSerial* createSerial(GwLog *logger, T* s,int id, bool canRead=true){
return new GwSerialImpl<T>(logger,s,id,canRead);
GwSerial* createSerial(GwLog *logger, T* s,int id, int type, bool canRead=true){
return new GwSerialImpl<T>(logger,s,id,type,canRead);
}
static ChannelParam * findChannelParam(int id){
@@ -300,7 +320,7 @@ static ChannelParam * findChannelParam(int id){
return param;
}
static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int idx,int rx,int tx, bool setLog=false){
static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int idx,int type,int rx,int tx, bool setLog,int ena=-1,int elow=1){
LOG_DEBUG(GwLog::DEBUG,"create serial: channel=%d, rx=%d,tx=%d",
idx,rx,tx);
ChannelParam *param=findChannelParam(idx);
@@ -312,19 +332,45 @@ static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int id
GwLog *streamLog=setLog?nullptr:logger;
switch(param->id){
case USB_CHANNEL_ID:
serialStream=createSerial(streamLog,&USBSerial,param->id);
serialStream=createSerial(streamLog,&USBSerial,param->id,type);
break;
case SERIAL1_CHANNEL_ID:
serialStream=createSerial(streamLog,&Serial1,param->id);
serialStream=createSerial(streamLog,&Serial1,param->id,type);
break;
case SERIAL2_CHANNEL_ID:
serialStream=createSerial(streamLog,&Serial2,param->id);
serialStream=createSerial(streamLog,&Serial2,param->id,type);
break;
}
if (serialStream == nullptr){
LOG_DEBUG(GwLog::ERROR,"invalid serial config with id %d",param->id);
return nullptr;
}
if (ena >= 0){
int value=-1;
if (type == GWSERIAL_TYPE_UNI){
String cfgMode=config->getString(param->direction);
if (cfgMode == "send"){
value=elow?0:1;
}
else{
value=elow?1:0;
}
}
if (type == GWSERIAL_TYPE_RX){
value=elow?1:0;
}
if (type == GWSERIAL_TYPE_TX){
value=elow?0:1;
}
if (value >= 0){
LOG_DEBUG(GwLog::LOG,"serial %d: setting output enable %d to %d",param->id,ena,value);
pinMode(ena,OUTPUT);
digitalWrite(ena,value);
}
else{
LOG_DEBUG(GwLog::ERROR,"serial %d: output enable ignored for mode %d",param->id, type);
}
}
serialStream->begin(config->getInt(param->baud,115200),SERIAL_8N1,rx,tx);
if (setLog){
logger->setWriter(new GwSerialLog(serialStream,config->getBool(param->preventLog,false)));
@@ -332,12 +378,13 @@ static GwSerial * createSerialImpl(GwConfigHandler *config,GwLog *logger, int id
}
return serialStream;
}
static GwChannel * createChannel(GwLog *logger, GwConfigHandler *config, int id,GwChannelInterface *impl, int type=GWSERIAL_TYPE_BI){
static GwChannel * createChannel(GwLog *logger, GwConfigHandler *config, int id,GwChannelInterface *impl){
ChannelParam *param=findChannelParam(id);
if (param == nullptr){
LOG_DEBUG(GwLog::ERROR,"invalid channel id %d",id);
return nullptr;
}
int type=impl->getType();
bool canRead=false;
bool canWrite=false;
bool validType=false;
@@ -425,10 +472,10 @@ void GwChannelList::begin(bool fallbackSerial){
GwChannel *channel=NULL;
//usb
if (! fallbackSerial){
GwSerial *usbSerial=createSerialImpl(config, logger,USB_CHANNEL_ID,GWUSB_RX,GWUSB_TX,true);
GwSerial *usbSerial=createSerialImpl(config, logger,USB_CHANNEL_ID,GWSERIAL_TYPE_BI,GWUSB_RX,GWUSB_TX,true);
if (usbSerial != nullptr){
usbSerial->enableWriteLock(); //as it is used for logging we need this additionally
GwChannel *usbChannel=createChannel(logger,config,USB_CHANNEL_ID,usbSerial,GWSERIAL_TYPE_BI);
GwChannel *usbChannel=createChannel(logger,config,USB_CHANNEL_ID,usbSerial);
if (usbChannel != nullptr){
addChannel(usbChannel);
}
@@ -444,10 +491,11 @@ void GwChannelList::begin(bool fallbackSerial){
//new serial config handling
for (auto &&init:serialInits){
LOG_INFO("creating serial channel %d, rx=%d,tx=%d,type=%d",init.serial,init.rx,init.tx,init.mode);
GwSerial *ser=createSerialImpl(config,logger,init.serial,init.rx,init.tx);
LOG_INFO("creating serial channel %d, rx=%d,tx=%d,type=%d fixedBaud=%d ena=%d elow=%d",
init.serial,init.rx,init.tx,init.mode,init.fixedBaud,init.ena,init.elow);
GwSerial *ser=createSerialImpl(config,logger,init.serial,init.mode,init.rx,init.tx,false,init.ena,init.elow);
if (ser != nullptr){
channel=createChannel(logger,config,init.serial,ser,init.mode);
channel=createChannel(logger,config,init.serial,ser);
if (channel != nullptr){
addChannel(channel);
}
@@ -466,8 +514,8 @@ void GwChannelList::begin(bool fallbackSerial){
config->getInt(config->remotePort),
config->getBool(config->readTCL)
);
addChannel(createChannel(logger,config,TCP_CLIENT_CHANNEL_ID,client));
}
addChannel(createChannel(logger,config,TCP_CLIENT_CHANNEL_ID,client));
//udp writer
if (config->getBool(GwConfigDefinitions::udpwEnabled)){

View File

@@ -3,7 +3,7 @@
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.
version 2 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

View File

@@ -57,6 +57,44 @@ Files
Starting from Version 20250305 you should normally not use this file name any more as those styles would be added for all build environments. Instead define a parameter _custom_css_ in your [platformio.ini](platformio.ini) for the environments you would like to add some styles for. This parameter accepts a list of file names (relative to the project root, separated by , or as multi line entry)
* [script.py](script.py)<br>
Starting from version 20251007 you can define a parameter "custom_script" in your [platformio.ini](platformio.ini).
This parameter can contain a list of file names (relative to the project root) that will be added as a [platformio extra script](https://docs.platformio.org/en/latest/scripting/index.html#scripting). The scripts will be loaded at the end of the main [extra_script](../../extra_script.py).
You can add code there that is specific for your build.
Example:
```
# PlatformIO extra script for obp60task
epdtype = "unknown"
pcbvers = "unknown"
for x in env["BUILD_FLAGS"]:
if x.startswith("-D HARDWARE_"):
pcbvers = x.split('_')[1]
if x.startswith("-D DISPLAY_"):
epdtype = x.split('_')[1]
propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env ["PIOENV"], "GxEPD2/library.properties")
properties = {}
with open(propfilename, 'r') as file:
for line in file:
match = re.match(r'^([^=]+)=(.*)$', line)
if match:
key = match.group(1).strip()
value = match.group(2).strip()
properties[key] = value
gxepd2vers = "unknown"
try:
if properties["name"] == "GxEPD2":
gxepd2vers = properties["version"]
except:
pass
env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)])
print("added hardware info to CPPDEFINES")
print("friendly board name is '{}'".format(env.GetProjectOption ("board_name")))
```
Interfaces
----------

View File

@@ -14,5 +14,6 @@ custom_config=
lib/exampletask/exampleConfig.json
custom_js=lib/exampletask/example.js
custom_css=lib/exampletask/example.css
custom_script=lib/exampletask/script.py
upload_port = /dev/esp32
upload_protocol = esptool

View File

@@ -0,0 +1,4 @@
Import("env")
print("exampletask extra script running")
syntax error here

View File

@@ -0,0 +1,23 @@
/*
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 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
defines for the channel modes(types)
*/
#ifndef _GWCHANNELMODES_H
#define _GWCHANNELMODES_H
#define GWSERIAL_TYPE_UNI 1
#define GWSERIAL_TYPE_BI 2
#define GWSERIAL_TYPE_RX 3
#define GWSERIAL_TYPE_TX 4
#define GWSERIAL_TYPE_UNK 0
#endif

View File

@@ -2,7 +2,7 @@
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.
version 2 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
@@ -20,11 +20,7 @@
#endif
#ifndef _GWHARDWARE_H
#define _GWHARDWARE_H
#define GWSERIAL_TYPE_UNI 1
#define GWSERIAL_TYPE_BI 2
#define GWSERIAL_TYPE_RX 3
#define GWSERIAL_TYPE_TX 4
#define GWSERIAL_TYPE_UNK 0
#include "GwChannelModes.h"
#include <GwConfigItem.h>
#include <HardwareSerial.h>
#include "GwAppInfo.h"

View File

@@ -2,7 +2,7 @@
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.
version 2 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
@@ -35,7 +35,12 @@
#ifdef M5_GPS_KIT
GWRESOURCE_USE(BASE,M5_GPS_KIT)
GWRESOURCE_USE(SERIAL1,M5_GPS_KIT)
#define _GWI_SERIAL1 BOARD_LEFT1,-1,GWSERIAL_TYPE_UNI,9600
#define _GWI_SERIAL1 BOARD_LEFT1,-1,GWSERIAL_TYPE_RX,9600
#endif
#ifdef M5_GPSV2_KIT
GWRESOURCE_USE(BASE,M5_GPSV2_KIT)
GWRESOURCE_USE(SERIAL1,M5_GPSV2_KIT)
#define _GWI_SERIAL1 BOARD_LEFT1,-1,GWSERIAL_TYPE_RX,115200
#endif
//M5 ProtoHub
@@ -61,11 +66,11 @@
#endif
//can kit for M5 Atom
#ifdef M5_CAN_KIT
#if defined (M5_CAN_KIT)
GWRESOURCE_USE(BASE,M5_CAN_KIT)
GWRESOURCE_USE(CAN,M5_CANKIT)
#define ESP32_CAN_TX_PIN BOARD_LEFT1
#define ESP32_CAN_RX_PIN BOARD_LEFT2
#endif
#endif
#endif

View File

@@ -2,7 +2,7 @@
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.
version 2 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

View File

@@ -2,7 +2,7 @@
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.
version 2 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
@@ -43,6 +43,13 @@
#define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_RX,9600
#endif
#GROVE
//https://docs.m5stack.com/en/unit/Unit-GPS%20v1.1
#ifdef M5_GPSV11_UNIT$GS$
GWRESOURCE_USE(GROOVE$G$,M5_GPSV11_UNIT$GS$)
#define _GWI_SERIAL_GROOVE$GS$ GWSERIAL_TYPE_RX,115200
#endif
#GROVE
//CAN via groove
#ifdef M5_CANUNIT$GS$
@@ -64,15 +71,15 @@
#endif
#GROVE
//#ifdef M5_ENV4$GS$
// #ifndef M5_GROOVEIIC$GS$
// #define M5_GROOVEIIC$GS$
// #endif
// GROOVE_IIC(SHT3X,$Z$,1)
// GROOVE_IIC(BMP280,$Z$,1)
// #define _GWSHT3X
// #define _GWBMP280
//#endif
#ifdef M5_ENV4$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(SHT4X,$Z$,1)
GROOVE_IIC(BMP280,$Z$,1)
#define _GWSHT4X
#define _GWBMP280
#endif
#GROVE
//example: -DSHT3XG1_A : defines STH3Xn1 on grove A - x depends on the other devices
@@ -93,6 +100,25 @@
#define _GWSHT3X
#endif
#GROVE
//example: -DSHT4XG1_A : defines STH4Xn1 on grove A - x depends on the other devices
#ifdef GWSHT4XG1$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(SHT4X,$Z$,1)
#define _GWSHT4X
#endif
#GROVE
#ifdef GWSHT4XG2$GS$
#ifndef M5_GROOVEIIC$GS$
#define M5_GROOVEIIC$GS$
#endif
GROOVE_IIC(SHT4X,$Z$,2)
#define _GWSHT4X
#endif
#GROVE
#ifdef GWQMP6988G1$GS$
#ifndef M5_GROOVEIIC$GS$

View File

@@ -23,6 +23,7 @@ class BME280Config : public IICSensorBase{
bool prAct=true;
bool tmAct=true;
bool huAct=true;
bool sEnv=true;
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_InsideHumidity;
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
@@ -152,6 +153,7 @@ SensorBase::Creator registerBME280(GwApi *api){
CFG_SGET(s, prNam, prefix); \
CFG_SGET(s, tmOff, prefix); \
CFG_SGET(s, prOff, prefix); \
CFG_SGET(s, sEnv, prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \

View File

@@ -29,6 +29,7 @@ class BMP280Config : public IICSensorBase{
public:
bool prAct=true;
bool tmAct=true;
bool sEnv=true;
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_Undef;
@@ -150,6 +151,7 @@ SensorBase::Creator registerBMP280(GwApi *api){
CFG_SGET(s, prNam, prefix); \
CFG_SGET(s, tmOff, prefix); \
CFG_SGET(s, prOff, prefix); \
CFG_SGET(s, sEnv,prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \

View File

@@ -104,12 +104,19 @@ void sendN2kTemperature(GwApi *api,CFG &cfg,double value, int counterId){
template <class CFG>
void sendN2kEnvironmentalParameters(GwApi *api,CFG &cfg,double tmValue, double huValue, double prValue, int counterId){
if (! cfg.sEnv) return;
tN2kMsg msg;
SetN2kEnvironmentalParameters(msg,1,cfg.tmSrc,tmValue,cfg.huSrc,huValue,prValue);
api->sendN2kMessage(msg);
api->increment(counterId,cfg.prefix+String("hum"));
api->increment(counterId,cfg.prefix+String("press"));
api->increment(counterId,cfg.prefix+String("temp"));
if (huValue != N2kDoubleNA){
api->increment(counterId,cfg.prefix+String("ehum"));
}
if (prValue != N2kDoubleNA){
api->increment(counterId,cfg.prefix+String("epress"));
}
if (tmValue != N2kDoubleNA){
api->increment(counterId,cfg.prefix+String("etemp"));
}
}
#ifndef _GWI_IIC1

View File

@@ -23,7 +23,7 @@ static std::vector<IICGrove> iicGroveList;
#include "GwBME280.h"
#include "GwBMP280.h"
#include "GwQMP6988.h"
#include "GwSHT3X.h"
#include "GwSHTXX.h"
#include <map>
#include "GwTimer.h"
@@ -91,6 +91,7 @@ void initIicTask(GwApi *api){
GwConfigHandler *config=api->getConfig();
std::vector<SensorBase::Creator> creators;
creators.push_back(registerSHT3X(api));
creators.push_back(registerSHT4X(api));
creators.push_back(registerQMP6988(api));
creators.push_back(registerBME280(api));
creators.push_back(registerBMP280(api));
@@ -147,13 +148,13 @@ bool initWire(GwLog *logger, TwoWire &wire, int num){
#ifdef _GWI_IIC1
return initWireDo(logger,wire,num,_GWI_IIC1);
#endif
return initWireDo(logger,wire,num,"",GWIIC_SDA,GWIIC_SCL);
return initWireDo(logger,wire,num,"",GWIIC_SCL,GWIIC_SDA);
}
if (num == 2){
#ifdef _GWI_IIC2
return initWireDo(logger,wire,num,_GWI_IIC2);
#endif
return initWireDo(logger,wire,num,"",GWIIC_SDA2,GWIIC_SCL2);
return initWireDo(logger,wire,num,"",GWIIC_SCL2,GWIIC_SDA2);
}
return false;
}

View File

@@ -9,6 +9,9 @@ class QMP6988Config : public IICSensorBase{
public:
String prNam="Pressure";
bool prAct=true;
bool sEnv=true;
tN2kTempSource tmSrc=tN2kTempSource::N2kts_InsideTemperature;
tN2kHumiditySource huSrc=tN2kHumiditySource::N2khs_Undef;
tN2kPressureSource prSrc=tN2kPressureSource::N2kps_Atmospheric;
float prOff=0;
QMP6988 *device=nullptr;
@@ -39,6 +42,7 @@ class QMP6988Config : public IICSensorBase{
float computed=pressure+prOff;
LOG_DEBUG(GwLog::DEBUG,"%s measure %2.0fPa, computed %2.0fPa",prefix.c_str(), pressure,computed);
sendN2kPressure(api,*this,computed,counterId);
sendN2kEnvironmentalParameters(api,*this,N2kDoubleNA,N2kDoubleNA,computed,counterId);
}
@@ -90,6 +94,7 @@ SensorBase::Creator registerQMP6988(GwApi *api){
CFG_SGET(s,prAct,prefix); \
CFG_SGET(s,intv,prefix); \
CFG_SGET(s,prOff,prefix); \
CFG_SGET(s,sEnv,prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \

View File

@@ -1,138 +0,0 @@
#include "GwSHT3X.h"
#ifdef _GWSHT3X
class SHT3XConfig;
static GwSensorConfigInitializerList<SHT3XConfig> configs;
class SHT3XConfig : public IICSensorBase{
public:
String tmNam;
String huNam;
bool tmAct=false;
bool huAct=false;
tN2kHumiditySource huSrc;
tN2kTempSource tmSrc;
SHT3X *device=nullptr;
using IICSensorBase::IICSensorBase;
virtual bool isActive(){
return tmAct || huAct;
}
virtual bool initDevice(GwApi * api,TwoWire *wire){
if (! isActive()) return false;
device=new SHT3X();
device->init(addr,wire);
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"initialized %s at address %d, intv %ld",prefix.c_str(),(int)addr,intv);
return true;
}
virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
addHumidXdr(api,*this);
addTempXdr(api,*this);
return isActive();
}
virtual void measure(GwApi * api,TwoWire *wire, int counterId)
{
if (!device)
return;
GwLog *logger=api->getLogger();
int rt = 0;
if ((rt = device->get()) == 0)
{
double temp = device->cTemp;
temp = CToKelvin(temp);
double humid = device->humidity;
LOG_DEBUG(GwLog::DEBUG, "%s measure temp=%2.1f, humid=%2.0f",prefix.c_str(), (float)temp, (float)humid);
if (huAct)
{
sendN2kHumidity(api, *this, humid, counterId);
}
if (tmAct)
{
sendN2kTemperature(api, *this, temp, counterId);
}
}
else
{
LOG_DEBUG(GwLog::DEBUG, "unable to query %s: %d",prefix.c_str(), rt);
}
}
virtual void readConfig(GwConfigHandler *cfg){
if (ok) return;
configs.readConfig(this,cfg);
return;
}
};
SensorBase::Creator creator=[](GwApi *api,const String &prfx)-> SensorBase*{
if (! configs.knowsPrefix(prfx)) return nullptr;
return new SHT3XConfig(api,prfx);
};
SensorBase::Creator registerSHT3X(GwApi *api){
GwLog *logger=api->getLogger();
#if defined(GWSHT3X) || defined (GWSHT3X11)
{
api->addSensor(creator(api,"SHT3X11"));
CHECK_IIC1();
#pragma message "GWSHT3X11 defined"
}
#endif
#if defined(GWSHT3X12)
{
api->addSensor(creator(api,"SHT3X12"));
CHECK_IIC1();
#pragma message "GWSHT3X12 defined"
}
#endif
#if defined(GWSHT3X21)
{
api->addSensor(creator(api,"SHT3X21"));
CHECK_IIC2();
#pragma message "GWSHT3X21 defined"
}
#endif
#if defined(GWSHT3X22)
{
api->addSensor(creator(api,"SHT3X22"));
CHECK_IIC2();
#pragma message "GWSHT3X22 defined"
}
#endif
return creator;
};
/**
* we do not dynamically compute the config names
* just to get compile time errors if something does not fit
* correctly
*/
#define CFGSHT3X(s, prefix, bus, baddr) \
CFG_SGET(s, tmNam, prefix); \
CFG_SGET(s, huNam, prefix); \
CFG_SGET(s, iid, prefix); \
CFG_SGET(s, tmAct, prefix); \
CFG_SGET(s, huAct, prefix); \
CFG_SGET(s, intv, prefix); \
CFG_SGET(s, huSrc, prefix); \
CFG_SGET(s, tmSrc, prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \
s->intv *= 1000;
#define SCSHT3X(prefix, bus, addr) \
GWSENSORDEF(configs, SHT3XConfig, CFGSHT3X, prefix, bus, addr)
SCSHT3X(SHT3X11, 1, 0x44);
SCSHT3X(SHT3X12, 1, 0x45);
SCSHT3X(SHT3X21, 2, 0x44);
SCSHT3X(SHT3X22, 2, 0x45);
#else
SensorBase::Creator registerSHT3X(GwApi *api){
return SensorBase::Creator();
}
#endif

254
lib/iictask/GwSHTXX.cpp Normal file
View File

@@ -0,0 +1,254 @@
#include "GwSHTXX.h"
#if defined(_GWSHT3X) || defined(_GWSHT4X)
class SHTXXConfig : public IICSensorBase{
public:
String tmNam;
String huNam;
bool tmAct=false;
bool huAct=false;
bool sEnv=true;
tN2kHumiditySource huSrc;
tN2kTempSource tmSrc;
using IICSensorBase::IICSensorBase;
virtual bool isActive(){
return tmAct || huAct;
}
virtual bool preinit(GwApi * api){
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"%s configured",prefix.c_str());
addHumidXdr(api,*this);
addTempXdr(api,*this);
return isActive();
}
virtual bool doMeasure(GwApi * api,double &temp, double &humid){
return false;
}
virtual void measure(GwApi * api,TwoWire *wire, int counterId) override
{
GwLog *logger=api->getLogger();
double temp = N2kDoubleNA;
double humid = N2kDoubleNA;
if (doMeasure(api,temp,humid)){
temp = CToKelvin(temp);
LOG_DEBUG(GwLog::DEBUG, "%s measure temp=%2.1f, humid=%2.0f",prefix.c_str(), (float)temp, (float)humid);
if (huAct)
{
sendN2kHumidity(api, *this, humid, counterId);
}
if (tmAct)
{
sendN2kTemperature(api, *this, temp, counterId);
}
if (huAct || tmAct){
sendN2kEnvironmentalParameters(api,*this,temp,humid,N2kDoubleNA,counterId);
}
}
}
};
/**
* we do not dynamically compute the config names
* just to get compile time errors if something does not fit
* correctly
*/
#define INITSHTXX(type,prefix,bus,baddr) \
[] (type *s ,GwConfigHandler *cfg) { \
CFG_SGET(s, tmNam, prefix); \
CFG_SGET(s, huNam, prefix); \
CFG_SGET(s, iid, prefix); \
CFG_SGET(s, tmAct, prefix); \
CFG_SGET(s, huAct, prefix); \
CFG_SGET(s, intv, prefix); \
CFG_SGET(s, huSrc, prefix); \
CFG_SGET(s, tmSrc, prefix); \
CFG_SGET(s, sEnv,prefix); \
s->busId = bus; \
s->addr = baddr; \
s->ok = true; \
s->intv *= 1000; \
}
#if defined(_GWSHT3X)
class SHT3XConfig;
static GwSensorConfigInitializerList<SHT3XConfig> configs3;
class SHT3XConfig : public SHTXXConfig{
SHT3X *device=nullptr;
public:
using SHTXXConfig::SHTXXConfig;
virtual bool initDevice(GwApi * api,TwoWire *wire)override{
if (! isActive()) return false;
device=new SHT3X();
device->init(addr,wire);
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"initialized %s at address %d, intv %ld",prefix.c_str(),(int)addr,intv);
return true;
}
virtual bool doMeasure(GwApi *api,double &temp, double &humid) override{
if (!device)
return false;
int rt=0;
GwLog *logger=api->getLogger();
if ((rt = device->get()) == 0)
{
temp = device->cTemp;
humid = device->humidity;
return true;
}
else{
LOG_DEBUG(GwLog::DEBUG, "unable to query %s: %d",prefix.c_str(), rt);
}
return false;
}
virtual void readConfig(GwConfigHandler *cfg) override{
if (ok) return;
configs3.readConfig(this,cfg);
return;
}
};
SensorBase::Creator creator3=[](GwApi *api,const String &prfx)-> SensorBase*{
if (! configs3.knowsPrefix(prfx)) return nullptr;
return new SHT3XConfig(api,prfx);
};
SensorBase::Creator registerSHT3X(GwApi *api){
GwLog *logger=api->getLogger();
#if defined(GWSHT3X) || defined (GWSHT3X11)
{
api->addSensor(creator3(api,"SHT3X11"));
CHECK_IIC1();
#pragma message "GWSHT3X11 defined"
}
#endif
#if defined(GWSHT3X12)
{
api->addSensor(creator3(api,"SHT3X12"));
CHECK_IIC1();
#pragma message "GWSHT3X12 defined"
}
#endif
#if defined(GWSHT3X21)
{
api->addSensor(creator3(api,"SHT3X21"));
CHECK_IIC2();
#pragma message "GWSHT3X21 defined"
}
#endif
#if defined(GWSHT3X22)
{
api->addSensor(creator3(api,"SHT3X22"));
CHECK_IIC2();
#pragma message "GWSHT3X22 defined"
}
#endif
return creator3;
};
#define SCSHT3X(prefix, bus, addr) \
GwSensorConfigInitializer<SHT3XConfig> __initCFGSHT3X ## prefix \
(configs3,GwSensorConfig<SHT3XConfig>(#prefix,INITSHTXX(SHT3XConfig,prefix,bus,addr)));
SCSHT3X(SHT3X11, 1, 0x44);
SCSHT3X(SHT3X12, 1, 0x45);
SCSHT3X(SHT3X21, 2, 0x44);
SCSHT3X(SHT3X22, 2, 0x45);
#endif
#if defined(_GWSHT4X)
class SHT4XConfig;
static GwSensorConfigInitializerList<SHT4XConfig> configs4;
class SHT4XConfig : public SHTXXConfig{
SHT4X *device=nullptr;
public:
using SHTXXConfig::SHTXXConfig;
virtual bool initDevice(GwApi * api,TwoWire *wire)override{
if (! isActive()) return false;
device=new SHT4X();
device->begin(wire,addr);
GwLog *logger=api->getLogger();
LOG_DEBUG(GwLog::LOG,"initialized %s at address %d, intv %ld",prefix.c_str(),(int)addr,intv);
return true;
}
virtual bool doMeasure(GwApi *api,double &temp, double &humid) override{
if (!device)
return false;
GwLog *logger=api->getLogger();
if (device->update())
{
temp = device->cTemp;
humid = device->humidity;
return true;
}
else{
LOG_DEBUG(GwLog::DEBUG, "unable to query %s",prefix.c_str());
}
return false;
}
virtual void readConfig(GwConfigHandler *cfg) override{
if (ok) return;
configs4.readConfig(this,cfg);
return;
}
};
SensorBase::Creator creator4=[](GwApi *api,const String &prfx)-> SensorBase*{
if (! configs4.knowsPrefix(prfx)) return nullptr;
return new SHT4XConfig(api,prfx);
};
SensorBase::Creator registerSHT4X(GwApi *api){
GwLog *logger=api->getLogger();
#if defined(GWSHT4X) || defined (GWSHT4X11)
{
api->addSensor(creator3(api,"SHT4X11"));
CHECK_IIC1();
#pragma message "GWSHT4X11 defined"
}
#endif
#if defined(GWSHT4X12)
{
api->addSensor(creator3(api,"SHT4X12"));
CHECK_IIC1();
#pragma message "GWSHT4X12 defined"
}
#endif
#if defined(GWSHT4X21)
{
api->addSensor(creator3(api,"SHT4X21"));
CHECK_IIC2();
#pragma message "GWSHT4X21 defined"
}
#endif
#if defined(GWSHT4X22)
{
api->addSensor(creator3(api,"SHT4X22"));
CHECK_IIC2();
#pragma message "GWSHT4X22 defined"
}
#endif
return creator4;
};
#define SCSHT4X(prefix, bus, addr) \
GwSensorConfigInitializer<SHT4XConfig> __initCFGSHT4X ## prefix \
(configs4,GwSensorConfig<SHT4XConfig>(#prefix,INITSHTXX(SHT4XConfig,prefix,bus,addr)));
SCSHT4X(SHT4X11, 1, 0x44);
SCSHT4X(SHT4X12, 1, 0x45);
SCSHT4X(SHT4X21, 2, 0x44);
SCSHT4X(SHT4X22, 2, 0x45);
#endif
#endif
#ifndef _GWSHT3X
SensorBase::Creator registerSHT3X(GwApi *api){
return SensorBase::Creator();
}
#endif
#ifndef _GWSHT4X
SensorBase::Creator registerSHT4X(GwApi *api){
return SensorBase::Creator();
}
#endif

View File

@@ -1,10 +1,13 @@
#ifndef _GWSHT3X_H
#define _GWSHT3X_H
#ifndef _GWSHTXX_H
#define _GWSHTXX_H
#include "GwIicSensors.h"
#ifdef _GWIIC
#if defined(GWSHT3X) || defined(GWSHT3X11) || defined(GWSHT3X12) || defined(GWSHT3X21) || defined(GWSHT3X22)
#define _GWSHT3X
#endif
#if defined(GWSHT4X) || defined(GWSHT4X11) || defined(GWSHT4X12) || defined(GWSHT4X21) || defined(GWSHT4X22)
#define _GWSHT4X
#endif
#else
#undef _GWSHT3X
#undef GWSHT3X
@@ -12,9 +15,19 @@
#undef GWSHT3X12
#undef GWSHT3X21
#undef GWSHT3X22
#undef _GWSHT4X
#undef GWSHT4X
#undef GWSHT4X11
#undef GWSHT4X12
#undef GWSHT4X21
#undef GWSHT4X22
#endif
#ifdef _GWSHT3X
#include "SHT3X.h"
#endif
#ifdef _GWSHT4X
#include "SHT4X.h"
#endif
SensorBase::Creator registerSHT3X(GwApi *api);
SensorBase::Creator registerSHT4X(GwApi *api);
#endif

View File

@@ -1,4 +1,4 @@
#include "GwSHT3X.h"
#include "GwSHTXX.h"
#ifdef _GWSHT3X
bool SHT3X::init(uint8_t slave_addr_in, TwoWire* wire_in)

131
lib/iictask/SHT4X.cpp Normal file
View File

@@ -0,0 +1,131 @@
#include "GwSHTXX.h"
#ifdef _GWSHT4X
uint8_t crc8(const uint8_t *data, int len) {
/*
*
* CRC-8 formula from page 14 of SHT spec pdf
*
* Test data 0xBE, 0xEF should yield 0x92
*
* Initialization data 0xFF
* Polynomial 0x31 (x8 + x5 +x4 +1)
* Final XOR 0x00
*/
const uint8_t POLYNOMIAL(0x31);
uint8_t crc(0xFF);
for (int j = len; j; --j) {
crc ^= *data++;
for (int i = 8; i; --i) {
crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
}
}
return crc;
}
bool SHT4X::begin(TwoWire* wire, uint8_t addr) {
_addr = addr;
_wire = wire;
int error;
_wire->beginTransmission(addr);
error = _wire->endTransmission();
if (error == 0) {
return true;
}
return false;
}
bool SHT4X::update() {
uint8_t readbuffer[6];
uint8_t cmd = SHT4x_NOHEAT_HIGHPRECISION;
uint16_t duration = 10;
if (_heater == SHT4X_NO_HEATER) {
if (_precision == SHT4X_HIGH_PRECISION) {
cmd = SHT4x_NOHEAT_HIGHPRECISION;
duration = 10;
}
if (_precision == SHT4X_MED_PRECISION) {
cmd = SHT4x_NOHEAT_MEDPRECISION;
duration = 5;
}
if (_precision == SHT4X_LOW_PRECISION) {
cmd = SHT4x_NOHEAT_LOWPRECISION;
duration = 2;
}
}
if (_heater == SHT4X_HIGH_HEATER_1S) {
cmd = SHT4x_HIGHHEAT_1S;
duration = 1100;
}
if (_heater == SHT4X_HIGH_HEATER_100MS) {
cmd = SHT4x_HIGHHEAT_100MS;
duration = 110;
}
if (_heater == SHT4X_MED_HEATER_1S) {
cmd = SHT4x_MEDHEAT_1S;
duration = 1100;
}
if (_heater == SHT4X_MED_HEATER_100MS) {
cmd = SHT4x_MEDHEAT_100MS;
duration = 110;
}
if (_heater == SHT4X_LOW_HEATER_1S) {
cmd = SHT4x_LOWHEAT_1S;
duration = 1100;
}
if (_heater == SHT4X_LOW_HEATER_100MS) {
cmd = SHT4x_LOWHEAT_100MS;
duration = 110;
}
// _i2c.writeByte(_addr, cmd, 1);
_wire->beginTransmission(_addr);
_wire->write(cmd);
_wire->write(1);
_wire->endTransmission();
delay(duration);
_wire->requestFrom(_addr, (uint8_t)6);
for (uint16_t i = 0; i < 6; i++) {
readbuffer[i] = _wire->read();
}
if (readbuffer[2] != crc8(readbuffer, 2) ||
readbuffer[5] != crc8(readbuffer + 3, 2)) {
return false;
}
float t_ticks = (uint16_t)readbuffer[0] * 256 + (uint16_t)readbuffer[1];
float rh_ticks = (uint16_t)readbuffer[3] * 256 + (uint16_t)readbuffer[4];
cTemp = -45 + 175 * t_ticks / 65535;
humidity = -6 + 125 * rh_ticks / 65535;
humidity = min(max(humidity, (float)0.0), (float)100.0);
return true;
}
void SHT4X::setPrecision(sht4x_precision_t prec) {
_precision = prec;
}
sht4x_precision_t SHT4X::getPrecision(void) {
return _precision;
}
void SHT4X::setHeater(sht4x_heater_t heat) {
_heater = heat;
}
sht4x_heater_t SHT4X::getHeater(void) {
return _heater;
}
#endif

76
lib/iictask/SHT4X.h Normal file
View File

@@ -0,0 +1,76 @@
#ifndef __SHT4X_H_
#define __SHT4X_H_
#include "Arduino.h"
#include "Wire.h"
#define SHT40_I2C_ADDR_44 0x44
#define SHT40_I2C_ADDR_45 0x45
#define SHT41_I2C_ADDR_44 0x44
#define SHT41_I2C_ADDR_45 0x45
#define SHT45_I2C_ADDR_44 0x44
#define SHT45_I2C_ADDR_45 0x45
#define SHT4x_DEFAULT_ADDR 0x44 /**< SHT4x I2C Address */
#define SHT4x_NOHEAT_HIGHPRECISION \
0xFD /**< High precision measurement, no heater */
#define SHT4x_NOHEAT_MEDPRECISION \
0xF6 /**< Medium precision measurement, no heater */
#define SHT4x_NOHEAT_LOWPRECISION \
0xE0 /**< Low precision measurement, no heater */
#define SHT4x_HIGHHEAT_1S \
0x39 /**< High precision measurement, high heat for 1 sec */
#define SHT4x_HIGHHEAT_100MS \
0x32 /**< High precision measurement, high heat for 0.1 sec */
#define SHT4x_MEDHEAT_1S \
0x2F /**< High precision measurement, med heat for 1 sec */
#define SHT4x_MEDHEAT_100MS \
0x24 /**< High precision measurement, med heat for 0.1 sec */
#define SHT4x_LOWHEAT_1S \
0x1E /**< High precision measurement, low heat for 1 sec */
#define SHT4x_LOWHEAT_100MS \
0x15 /**< High precision measurement, low heat for 0.1 sec */
#define SHT4x_READSERIAL 0x89 /**< Read Out of Serial Register */
#define SHT4x_SOFTRESET 0x94 /**< Soft Reset */
typedef enum {
SHT4X_HIGH_PRECISION,
SHT4X_MED_PRECISION,
SHT4X_LOW_PRECISION,
} sht4x_precision_t;
/** Optional pre-heater configuration setting */
typedef enum {
SHT4X_NO_HEATER,
SHT4X_HIGH_HEATER_1S,
SHT4X_HIGH_HEATER_100MS,
SHT4X_MED_HEATER_1S,
SHT4X_MED_HEATER_100MS,
SHT4X_LOW_HEATER_1S,
SHT4X_LOW_HEATER_100MS,
} sht4x_heater_t;
class SHT4X {
public:
bool begin(TwoWire* wire = &Wire, uint8_t addr = SHT40_I2C_ADDR_44);
bool update(void);
float cTemp = 0;
float humidity = 0;
void setPrecision(sht4x_precision_t prec);
sht4x_precision_t getPrecision(void);
void setHeater(sht4x_heater_t heat);
sht4x_heater_t getHeater(void);
private:
TwoWire* _wire;
uint8_t _addr;
sht4x_precision_t _precision = SHT4X_HIGH_PRECISION;
sht4x_heater_t _heater = SHT4X_NO_HEATER;
};
#endif

View File

@@ -1,49 +1,77 @@
[
{
"type": "array",
"name": "SHT3X",
"name": "SHTXX",
"replace": [
{
"b": "1",
"i": "11",
"n": "99"
"n": "99",
"x": "3"
},
{
"b": "1",
"i": "12",
"n": "98"
"n": "98",
"x": "3"
},
{
"b": "2",
"i": "21",
"n": "109"
"n": "109",
"x": "3"
},
{
"b": "2",
"i": "22",
"n": "108"
"n": "108",
"x": "3"
},
{
"b": "1",
"i": "11",
"n": "119",
"x": "4"
},
{
"b": "1",
"i": "12",
"n": "118",
"x": "4"
},
{
"b": "2",
"i": "21",
"n": "129",
"x": "4"
},
{
"b": "2",
"i": "22",
"n": "128",
"x": "4"
}
],
"children": [
{
"name": "SHT3X$itmAct",
"label": "SHT3X$i Temp",
"name": "SHT$xX$itmAct",
"label": "SHT$xX$i Temp",
"type": "boolean",
"default": "true",
"description": "Enable the $i. I2C SHT3x temp sensor (bus $b)",
"description": "Enable the $i. I2C SHT$xX temp sensor (bus $b)",
"category": "iicsensors$b",
"capabilities": {
"SHT3X$i": "true"
"SHT$xX$i": "true"
}
},
{
"name": "SHT3X$itmSrc",
"label": "SHT3X$i Temp Type",
"name": "SHT$xX$itmSrc",
"label": "SHT$xX$i Temp Type",
"type": "list",
"default": "2",
"description": "the NMEA2000 source type for the temperature",
"description": "the NMEA2000 source type for the temperature (PGN 130312,130311)",
"list": [
{
"l": "SeaTemperature",
@@ -112,23 +140,23 @@
],
"category": "iicsensors$b",
"capabilities": {
"SHT3X$i": "true"
"SHT$xX$i": "true"
}
},
{
"name": "SHT3X$ihuAct",
"label": "SHT3X$i Humidity",
"name": "SHT$xX$ihuAct",
"label": "SHT$xX$i Humidity",
"type": "boolean",
"default": "true",
"description": "Enable the $i. I2C SHT3x humidity sensor (bus $b)",
"description": "Enable the $i. I2C SHT$xX humidity sensor (bus $b)",
"category": "iicsensors$b",
"capabilities": {
"SHT3X$i": "true"
"SHT$xX$i": "true"
}
},
{
"name": "SHT3X$ihuSrc",
"label": "SHT3X$i Humid Type",
"name": "SHT$xX$ihuSrc",
"label": "SHT$xX$i Humid Type",
"list": [
{
"l": "OutsideHumidity",
@@ -141,57 +169,68 @@
],
"category": "iicsensors$b",
"capabilities": {
"SHT3X": "true"
"SHT$xX": "true"
}
},
{
"name": "SHT3X$iiid",
"label": "SHT3X$i N2K iid",
"name": "SHT$xX$iiid",
"label": "SHT$xX$i N2K iid",
"type": "number",
"default": "$n",
"description": "the N2K instance id for the $i. SHT3X Temperature and Humidity ",
"description": "the N2K instance id for the $i. SHT$xX Temperature and Humidity (PGN 130312,130311) ",
"category": "iicsensors$b",
"min": 0,
"max": 253,
"check": "checkMinMax",
"capabilities": {
"SHT3X$i": "true"
"SHT$xX$i": "true"
}
},
{
"name": "SHT3X$iintv",
"label": "SHT3X$i Interval",
"name": "SHT$xX$isEnv",
"label": "SHT$xX$i send Env",
"type": "boolean",
"default": "true",
"description": "also send PGN 130311",
"category": "iicsensors$b",
"capabilities": {
"SHT$xX$i": "true"
}
},
{
"name": "SHT$xX$iintv",
"label": "SHT$xX$i Interval",
"type": "number",
"default": 2,
"description": "Interval(s) to query SHT3X Temperature and Humidity (1...300)",
"description": "Interval(s) to query SHT$xX Temperature and Humidity (1...300)",
"category": "iicsensors$b",
"min": 1,
"max": 300,
"check": "checkMinMax",
"capabilities": {
"SHT3X$i": "true"
"SHT$xX$i": "true"
}
},
{
"name": "SHT3X$itmNam",
"label": "SHT3X$i Temp XDR",
"name": "SHT$xX$itmNam",
"label": "SHT$xX$i Temp XDR",
"type": "String",
"default": "Temp$i",
"description": "set the XDR transducer name for the $i. SHT3X Temperature, leave empty to disable NMEA0183 XDR ",
"description": "set the XDR transducer name for the $i. SHT$xX Temperature, leave empty to disable NMEA0183 XDR ",
"category": "iicsensors$b",
"capabilities": {
"SHT3X$i": "true"
"SHT$xX$i": "true"
}
},
{
"name": "SHT3X$ihuNam",
"label": "SHT3X$i Humid XDR",
"name": "SHT$xX$ihuNam",
"label": "SHT$xX$i Humid XDR",
"type": "String",
"default": "Humidity$i",
"description": "set the XDR transducer name for the $i. SHT3X Humidity, leave empty to disable NMEA0183 XDR",
"description": "set the XDR transducer name for the $i. SHT$xX Humidity, leave empty to disable NMEA0183 XDR",
"category": "iicsensors$b",
"capabilities": {
"SHT3X$i": "true"
"SHT$xX$i": "true"
}
}
]
@@ -247,6 +286,17 @@
"QMP6988$i": "true"
}
},
{
"name": "QMP6988$isEnv",
"label": "QMP6988$i send Env",
"type": "boolean",
"default": "true",
"description": "also send PGN 130311",
"category": "iicsensors$b",
"capabilities": {
"QMP6988$i": "true"
}
},
{
"name": "QMP6988$iintv",
"label": "QMP6988-$i Interval",
@@ -473,7 +523,7 @@
"label": "BME280-$i N2K iid",
"type": "number",
"default": "$n",
"description": "the N2K instance id for the BME280 Temperature and Humidity ",
"description": "the N2K instance id for the BME280 Temperature, Humidity, Pressure (PGN 130312,130313, 130314) ",
"category": "iicsensors$b",
"min": 0,
"max": 253,
@@ -482,6 +532,17 @@
"BME280$i": "true"
}
},
{
"name": "BME280$isEnv",
"label": "BME280$i send Env",
"type": "boolean",
"default": "true",
"description": "also send PGN 130311",
"category": "iicsensors$b",
"capabilities": {
"BME280$i": "true"
}
},
{
"name": "BME280$iintv",
"label": "BME280-$i Interval",
@@ -683,7 +744,7 @@
"label": "BMP280-$i N2K iid",
"type": "number",
"default": "$n",
"description": "the N2K instance id for the BMP280 Temperature",
"description": "the N2K instance id for the BMP280 Temperature/Pressure (PGN 130312,130314)",
"category": "iicsensors$b",
"min": 0,
"max": 253,
@@ -692,6 +753,17 @@
"BMP280$i": "true"
}
},
{
"name": "BMP280$isEnv",
"label": "BMP280$i send Env",
"type": "boolean",
"default": "true",
"description": "also send PGN 130311",
"category": "iicsensors$b",
"capabilities": {
"BMP280$i": "true"
}
},
{
"name": "BMP280$iintv",
"label": "BMP280-$i Interval",

View File

@@ -11,6 +11,17 @@ build_flags=
-D M5_CAN_KIT
${env.build_flags}
[env:m5stack-atom-env4]
extends = sensors
board = m5stack-atom
lib_deps =
${env.lib_deps}
${sensors.lib_deps}
build_flags=
-D M5_ENV4
-D M5_CAN_KIT
${env.build_flags}
[env:m5stack-atom-bme280]
extends = sensors

View File

@@ -2,7 +2,7 @@
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.
version 2 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
@@ -27,6 +27,8 @@ const double nmTom = 1.852 * 1000;
uint16_t DaysSince1970 = 0;
#define boolbit(b) (b?1:0)
class MyAisDecoder : public AIS::AisDecoder
{
public:
@@ -82,25 +84,24 @@ class MyAisDecoder : public AIS::AisDecoder
tN2kMsg N2kMsg;
// PGN129038
N2kMsg.SetPGN(129038L);
N2kMsg.Priority = 4;
N2kMsg.AddByte((_Repeat & 0x03) << 6 | (_uMsgType & 0x3f));
N2kMsg.Add4ByteUInt(_uMmsi);
N2kMsg.Add4ByteDouble(_iPosLon / 600000.0, 1e-07);
N2kMsg.Add4ByteDouble(_iPosLat / 600000.0, 1e-07);
N2kMsg.AddByte((_timestamp & 0x3f) << 2 | (_Raim & 0x01) << 1 | (_bPosAccuracy & 0x01));
N2kMsg.Add2ByteUDouble(decodeCog(_iCog), 1e-04);
N2kMsg.Add2ByteUDouble(_uSog * knToms/10.0, 0.01);
N2kMsg.AddByte(0x00); // Communication State (19 bits)
N2kMsg.AddByte(0x00);
N2kMsg.AddByte(0x00); // AIS transceiver information (5 bits)
N2kMsg.Add2ByteUDouble(decodeHeading(_iHeading), 1e-04);
N2kMsg.Add2ByteDouble(decodeRot(_iRot), 3.125E-05); // 1e-3/32.0
N2kMsg.AddByte(0xF0 | (_uNavstatus & 0x0f));
N2kMsg.AddByte(0xff); // Reserved
N2kMsg.AddByte(0xff); // SID (NA)
SetN2kPGN129038(
N2kMsg,
_uMsgType,
(tN2kAISRepeat)_Repeat,
_uMmsi,
_iPosLon/ 600000.0,
_iPosLat / 600000.0,
_bPosAccuracy,
_Raim,
_timestamp,
decodeCog(_iCog),
_uSog * knToms/10.0,
tN2kAISTransceiverInformation::N2kaischannel_A_VDL_reception,
decodeHeading(_iHeading),
decodeRot(_iRot),
(tN2kAISNavStatus)_uNavstatus,
0xff
);
send(N2kMsg);
}
@@ -255,9 +256,40 @@ class MyAisDecoder : public AIS::AisDecoder
send(N2kMsg);
}
virtual void onType21(unsigned int , unsigned int , const std::string &, bool , int , int , unsigned int , unsigned int , unsigned int , unsigned int ) override {
//mmsi, aidType, name + nameExt, posAccuracy, posLon, posLat, toBow, toStern, toPort, toStarboard
virtual void onType21(unsigned int mmsi , unsigned int aidType , const std::string & name, bool accuracy, int posLon, int posLat, unsigned int toBow,
unsigned int toStern, unsigned int toPort, unsigned int toStarboard,
unsigned int repeat,unsigned int timestamp, bool raim, bool virtualAton, bool offPosition) override {
//Serial.println("21");
//the name can be at most 120bit+88bit (35 byte) + termination -> 36 Byte
//in principle we should use tN2kAISAtoNReportData to directly call the library
//function for 129041. But this makes the conversion really complex.
bool assignedMode=false;
tN2kGNSStype gnssType=tN2kGNSStype::N2kGNSSt_GPS; //canboat considers 0 as undefined...
tN2kAISTransceiverInformation transceiverInfo=tN2kAISTransceiverInformation::N2kaischannel_A_VDL_reception;
tN2kMsg N2kMsg;
N2kMsg.SetPGN(129041);
N2kMsg.Priority=4;
N2kMsg.AddByte((repeat & 0x03) << 6 | (21 & 0x3f));
N2kMsg.Add4ByteUInt(mmsi); //N2kData.UserID
N2kMsg.Add4ByteDouble(posLon / 600000.0, 1e-07);
N2kMsg.Add4ByteDouble(posLat / 600000.0, 1e-07);
N2kMsg.AddByte((timestamp & 0x3f)<<2 | boolbit(raim)<<1 | boolbit(accuracy));
N2kMsg.Add2ByteUDouble(toBow+toStern, 0.1);
N2kMsg.Add2ByteUDouble(toPort+toStarboard, 0.1);
N2kMsg.Add2ByteUDouble(toStarboard, 0.1);
N2kMsg.Add2ByteUDouble(toBow, 0.1);
N2kMsg.AddByte(boolbit(assignedMode) << 7
| boolbit(virtualAton) << 6
| boolbit(offPosition) << 5
| (aidType & 0x1f));
N2kMsg.AddByte((gnssType & 0x0F) << 1 | 0xe0);
N2kMsg.AddByte(N2kUInt8NA); //status
N2kMsg.AddByte((transceiverInfo & 0x1f) | 0xe0);
//bit offset 208 (see canboat/pgns.xml) -> 26 bytes from start
//as MaxDataLen is 223 and the string can be at most 36 bytes + 2 byte heading - no further check here
N2kMsg.AddVarStr(name.c_str());
send(N2kMsg);
}
virtual void onType24A(unsigned int _uMsgType, unsigned int _repeat, unsigned int _uMmsi,

View File

@@ -143,7 +143,7 @@ private:
*/
GwXDRFoundMapping getOtherFieldMapping(GwXDRFoundMapping &found, int field){
if (found.empty) return GwXDRFoundMapping();
return xdrMappings->getMapping(found.definition->category,
return xdrMappings->getMapping(0,found.definition->category,
found.definition->selector,
field,
found.instanceId);

View File

@@ -708,12 +708,37 @@ private:
}
}
//helper for converting the AIS transceiver info to talker/channel
void setTalkerChannel(tNMEA0183AISMsg &msg, tN2kAISTransceiverInformation &transceiver){
bool channelA=true;
bool own=false;
switch (transceiver){
case tN2kAISTransceiverInformation::N2kaischannel_A_VDL_reception:
channelA=true;
own=false;
break;
case tN2kAISTransceiverInformation::N2kaischannel_B_VDL_reception:
channelA=false;
own=false;
break;
case tN2kAISTransceiverInformation::N2kaischannel_A_VDL_transmission:
channelA=true;
own=true;
break;
case tN2kAISTransceiverInformation::N2kaischannel_B_VDL_transmission:
channelA=false;
own=true;
break;
}
msg.SetChannelAndTalker(channelA,own);
}
//*****************************************************************************
// 129038 AIS Class A Position Report (Message 1, 2, 3)
void HandleAISClassAPosReport(const tN2kMsg &N2kMsg)
{
unsigned char SID;
tN2kAISRepeat _Repeat;
uint32_t _UserID; // MMSI
double _Latitude =N2kDoubleNA;
@@ -732,64 +757,19 @@ private:
uint8_t _MessageType = 1;
tNMEA0183AISMsg NMEA0183AISMsg;
if (ParseN2kPGN129038(N2kMsg, SID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, _Seconds,
if (ParseN2kPGN129038(N2kMsg, _MessageType, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM, _Seconds,
_COG, _SOG, _Heading, _ROT, _NavStatus,_AISTransceiverInformation,_SID))
{
// Debug
#ifdef SERIAL_PRINT_AIS_FIELDS
Serial.println(" Msg 1 ");
const double pi = 3.1415926535897932384626433832795;
const double radToDeg = 180.0 / pi;
const double msTokn = 3600.0 / 1852.0;
const double radsToDegMin = 60 * 360.0 / (2 * pi); // [rad/s -> degree/minute]
Serial.print("Repeat: ");
Serial.println(_Repeat);
Serial.print("UserID: ");
Serial.println(_UserID);
Serial.print("Latitude: ");
Serial.println(_Latitude);
Serial.print("Longitude: ");
Serial.println(_Longitude);
Serial.print("Accuracy: ");
Serial.println(_Accuracy);
Serial.print("RAIM: ");
Serial.println(_RAIM);
Serial.print("Seconds: ");
Serial.println(_Seconds);
Serial.print("COG: ");
Serial.println(_COG * radToDeg);
Serial.print("SOG: ");
Serial.println(_SOG * msTokn);
Serial.print("Heading: ");
Serial.println(_Heading * radToDeg);
Serial.print("ROT: ");
Serial.println(_ROT * radsToDegMin);
Serial.print("NavStatus: ");
Serial.println(_NavStatus);
#endif
setTalkerChannel(NMEA0183AISMsg,_AISTransceiverInformation);
if (_MessageType < 1 || _MessageType > 3) _MessageType=1; //only allow type 1...3 for 129038
if (SetAISClassABMessage1(NMEA0183AISMsg, _MessageType, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy,
_RAIM, _Seconds, _COG, _SOG, _Heading, _ROT, _NavStatus))
{
SendMessage(NMEA0183AISMsg);
#ifdef SERIAL_PRINT_AIS_NMEA
// Debug Print AIS-NMEA
Serial.print(NMEA0183AISMsg.GetPrefix());
Serial.print(NMEA0183AISMsg.Sender());
Serial.print(NMEA0183AISMsg.MessageCode());
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
{
Serial.print(",");
Serial.print(NMEA0183AISMsg.Field(i));
}
char buf[7];
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
Serial.print(buf);
#endif
}
}
} // end 129038 AIS Class A Position Report Message 1/3
@@ -825,84 +805,18 @@ private:
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,21,
_AISversion, _GNSStype, _DTE, _AISinfo,_SID))
{
#ifdef SERIAL_PRINT_AIS_FIELDS
// Debug Print N2k Values
Serial.println(" Msg 5 ");
Serial.print("MessageID: ");
Serial.println(_MessageID);
Serial.print("Repeat: ");
Serial.println(_Repeat);
Serial.print("UserID: ");
Serial.println(_UserID);
Serial.print("IMONumber: ");
Serial.println(_IMONumber);
Serial.print("Callsign: ");
Serial.println(_Callsign);
Serial.print("VesselType: ");
Serial.println(_VesselType);
Serial.print("Name: ");
Serial.println(_Name);
Serial.print("Length: ");
Serial.println(_Length);
Serial.print("Beam: ");
Serial.println(_Beam);
Serial.print("PosRefStbd: ");
Serial.println(_PosRefStbd);
Serial.print("PosRefBow: ");
Serial.println(_PosRefBow);
Serial.print("ETAdate: ");
Serial.println(_ETAdate);
Serial.print("ETAtime: ");
Serial.println(_ETAtime);
Serial.print("Draught: ");
Serial.println(_Draught);
Serial.print("Destination: ");
Serial.println(_Destination);
Serial.print("GNSStype: ");
Serial.println(_GNSStype);
Serial.print("DTE: ");
Serial.println(_DTE);
Serial.println(" Msg 5 ");
#endif
setTalkerChannel(NMEA0183AISMsg,_AISinfo);
if (SetAISClassAMessage5(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _IMONumber, _Callsign, _Name, _VesselType,
_Length, _Beam, _PosRefStbd, _PosRefBow, _ETAdate, _ETAtime, _Draught, _Destination,
_GNSStype, _DTE))
_GNSStype, _DTE,_AISversion))
{
SendMessage(NMEA0183AISMsg.BuildMsg5Part1(NMEA0183AISMsg));
#ifdef SERIAL_PRINT_AIS_NMEA
// Debug Print AIS-NMEA Message Type 5, Part 1
char buf[7];
Serial.print(NMEA0183AISMsg.GetPrefix());
Serial.print(NMEA0183AISMsg.Sender());
Serial.print(NMEA0183AISMsg.MessageCode());
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
{
Serial.print(",");
Serial.print(NMEA0183AISMsg.Field(i));
if (NMEA0183AISMsg.BuildMsg5Part1()){
SendMessage(NMEA0183AISMsg);
}
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
Serial.print(buf);
#endif
SendMessage(NMEA0183AISMsg.BuildMsg5Part2(NMEA0183AISMsg));
#ifdef SERIAL_PRINT_AIS_NMEA
// Print AIS-NMEA Message Type 5, Part 2
Serial.print(NMEA0183AISMsg.GetPrefix());
Serial.print(NMEA0183AISMsg.Sender());
Serial.print(NMEA0183AISMsg.MessageCode());
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
{
Serial.print(",");
Serial.print(NMEA0183AISMsg.Field(i));
if (NMEA0183AISMsg.BuildMsg5Part2()){
SendMessage(NMEA0183AISMsg);
}
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
Serial.print(buf);
#endif
}
}
}
@@ -926,35 +840,21 @@ private:
tN2kAISUnit _Unit;
bool _Display, _DSC, _Band, _Msg22, _State;
tN2kAISMode _Mode;
tN2kAISTransceiverInformation _AISTranceiverInformation;
tN2kAISTransceiverInformation _AISTransceiverInformation;
uint8_t _SID;
if (ParseN2kPGN129039(N2kMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
_Seconds, _COG, _SOG, _AISTranceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State,_SID))
_Seconds, _COG, _SOG, _AISTransceiverInformation, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State,_SID))
{
tNMEA0183AISMsg NMEA0183AISMsg;
setTalkerChannel(NMEA0183AISMsg,_AISTransceiverInformation);
if (SetAISClassBMessage18(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Latitude, _Longitude, _Accuracy, _RAIM,
_Seconds, _COG, _SOG, _Heading, _Unit, _Display, _DSC, _Band, _Msg22, _Mode, _State))
{
SendMessage(NMEA0183AISMsg);
#ifdef SERIAL_PRINT_AIS_NMEA
// Debug Print AIS-NMEA
Serial.print(NMEA0183AISMsg.GetPrefix());
Serial.print(NMEA0183AISMsg.Sender());
Serial.print(NMEA0183AISMsg.MessageCode());
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
{
Serial.print(",");
Serial.print(NMEA0183AISMsg.Field(i));
}
char buf[7];
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
Serial.print(buf);
#endif
}
}
return;
@@ -976,8 +876,10 @@ private:
{
tNMEA0183AISMsg NMEA0183AISMsg;
setTalkerChannel(NMEA0183AISMsg,_AISInfo);
if (SetAISClassBMessage24PartA(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _Name))
{
SendMessage(NMEA0183AISMsg);
}
}
return;
@@ -1005,77 +907,51 @@ private:
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID,_AISInfo,_SID))
{
//
#ifdef SERIAL_PRINT_AIS_FIELDS
// Debug Print N2k Values
Serial.println(" Msg 24 ");
Serial.print("MessageID: ");
Serial.println(_MessageID);
Serial.print("Repeat: ");
Serial.println(_Repeat);
Serial.print("UserID: ");
Serial.println(_UserID);
Serial.print("VesselType: ");
Serial.println(_VesselType);
Serial.print("Vendor: ");
Serial.println(_Vendor);
Serial.print("Callsign: ");
Serial.println(_Callsign);
Serial.print("Length: ");
Serial.println(_Length);
Serial.print("Beam: ");
Serial.println(_Beam);
Serial.print("PosRefStbd: ");
Serial.println(_PosRefStbd);
Serial.print("PosRefBow: ");
Serial.println(_PosRefBow);
Serial.print("MothershipID: ");
Serial.println(_MothershipID);
Serial.println(" Msg 24 ");
#endif
tNMEA0183AISMsg NMEA0183AISMsg;
if (SetAISClassBMessage24(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor, _Callsign,
setTalkerChannel(NMEA0183AISMsg,_AISInfo);
if (SetAISClassBMessage24PartB(NMEA0183AISMsg, _MessageID, _Repeat, _UserID, _VesselType, _Vendor, _Callsign,
_Length, _Beam, _PosRefStbd, _PosRefBow, _MothershipID))
{
SendMessage(NMEA0183AISMsg.BuildMsg24PartA(NMEA0183AISMsg));
#ifdef SERIAL_PRINT_AIS_NMEA
// Debug Print AIS-NMEA
char buf[7];
Serial.print(NMEA0183AISMsg.GetPrefix());
Serial.print(NMEA0183AISMsg.Sender());
Serial.print(NMEA0183AISMsg.MessageCode());
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
{
Serial.print(",");
Serial.print(NMEA0183AISMsg.Field(i));
}
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
Serial.print(buf);
#endif
SendMessage(NMEA0183AISMsg.BuildMsg24PartB(NMEA0183AISMsg));
#ifdef SERIAL_PRINT_AIS_NMEA
Serial.print(NMEA0183AISMsg.GetPrefix());
Serial.print(NMEA0183AISMsg.Sender());
Serial.print(NMEA0183AISMsg.MessageCode());
for (int i = 0; i < NMEA0183AISMsg.FieldCount(); i++)
{
Serial.print(",");
Serial.print(NMEA0183AISMsg.Field(i));
}
sprintf(buf, "*%02X\r\n", NMEA0183AISMsg.GetCheckSum());
Serial.print(buf);
#endif
SendMessage(NMEA0183AISMsg);
}
}
return;
}
//*****************************************************************************
// PGN 129041 Aton
void HandleAISMessage21(const tN2kMsg &N2kMsg)
{
tN2kAISAtoNReportData data;
if (ParseN2kPGN129041(N2kMsg,data)){
tNMEA0183AISMsg nmea0183Msg;
setTalkerChannel(nmea0183Msg,data.AISTransceiverInformation);
if (SetAISMessage21(
nmea0183Msg,
data.Repeat,
data.UserID,
data.Latitude,
data.Longitude,
data.Accuracy,
data.RAIM,
data.Seconds,
data.Length,
data.Beam,
data.PositionReferenceStarboard,
data.PositionReferenceTrueNorth,
data.AtoNType,
data.OffPositionIndicator,
data.VirtualAtoNFlag,
data.AssignedModeFlag,
data.GNSSType,
data.AtoNStatus,
data.AtoNName
)){
SendMessage(nmea0183Msg);
}
}
}
void HandleSystemTime(const tN2kMsg &msg){
unsigned char sid=-1;
uint16_t DaysSince1970=N2kUInt16NA;
@@ -1271,12 +1147,12 @@ private:
double Level=N2kDoubleNA;
double Capacity=N2kDoubleNA;
if (ParseN2kPGN127505(N2kMsg,Instance,FluidType,Level,Capacity)) {
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRFLUID,FluidType,0,Instance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(Level,XDRFLUID,FluidType,0,Instance);
if (updateDouble(&mapping,Level)){
LOG_DEBUG(GwLog::DEBUG+1,"found fluidlevel mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Level));
}
mapping=xdrMappings->getMapping(XDRFLUID,FluidType,1,Instance);
mapping=xdrMappings->getMapping(Capacity, XDRFLUID,FluidType,1,Instance);
if (updateDouble(&mapping,Capacity)){
LOG_DEBUG(GwLog::DEBUG+1,"found fluid capacity mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Capacity));
@@ -1294,19 +1170,19 @@ private:
double BatteryTemperature=N2kDoubleNA;
if (ParseN2kPGN127508(N2kMsg,BatteryInstance,BatteryVoltage,BatteryCurrent,BatteryTemperature,SID)) {
int i=0;
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRBAT,0,0,BatteryInstance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(BatteryVoltage, XDRBAT,0,0,BatteryInstance);
if (updateDouble(&mapping,BatteryVoltage)){
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryVoltage mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(BatteryVoltage));
i++;
}
mapping=xdrMappings->getMapping(XDRBAT,0,1,BatteryInstance);
mapping=xdrMappings->getMapping(BatteryCurrent,XDRBAT,0,1,BatteryInstance);
if (updateDouble(&mapping,BatteryCurrent)){
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryCurrent mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(BatteryCurrent));
i++;
}
mapping=xdrMappings->getMapping(XDRBAT,0,2,BatteryInstance);
mapping=xdrMappings->getMapping(BatteryTemperature,XDRBAT,0,2,BatteryInstance);
if (updateDouble(&mapping,BatteryTemperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found BatteryTemperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(BatteryTemperature));
@@ -1338,13 +1214,13 @@ private:
SendMessage(NMEA0183Msg);
}
int i=0;
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,N2kts_OutsideTemperature,0,0);
GwXDRFoundMapping mapping=xdrMappings->getMapping(OutsideAmbientAirTemperature, XDRTEMP,N2kts_OutsideTemperature,0,0);
if (updateDouble(&mapping,OutsideAmbientAirTemperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(OutsideAmbientAirTemperature));
i++;
}
mapping=xdrMappings->getMapping(XDRPRESSURE,N2kps_Atmospheric,0,0);
mapping=xdrMappings->getMapping(AtmosphericPressure,XDRPRESSURE,N2kps_Atmospheric,0,0);
if (updateDouble(&mapping,AtmosphericPressure)){
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(AtmosphericPressure));
@@ -1379,19 +1255,19 @@ private:
SendMessage(NMEA0183Msg);
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,TempSource,0,0);
GwXDRFoundMapping mapping=xdrMappings->getMapping(Temperature, XDRTEMP,TempSource,0,0);
if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Temperature));
i++;
}
mapping=xdrMappings->getMapping(XDRHUMIDITY,HumiditySource,0,0);
mapping=xdrMappings->getMapping(Humidity, XDRHUMIDITY,HumiditySource,0,0);
if (updateDouble(&mapping,Humidity)){
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Humidity));
i++;
}
mapping=xdrMappings->getMapping(XDRPRESSURE,N2kps_Atmospheric,0,0);
mapping=xdrMappings->getMapping(AtmosphericPressure, XDRPRESSURE,N2kps_Atmospheric,0,0);
if (updateDouble(&mapping,AtmosphericPressure)){
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(AtmosphericPressure));
@@ -1426,12 +1302,12 @@ private:
SendMessage(NMEA0183Msg);
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(Temperature, XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Temperature));
}
mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,1,TemperatureInstance);
mapping=xdrMappings->getMapping(setTemperature, XDRTEMP,(int)TemperatureSource,1,TemperatureInstance);
if (updateDouble(&mapping,setTemperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(setTemperature));
@@ -1449,12 +1325,13 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return;
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRHUMIDITY,(int)HumiditySource,0,HumidityInstance);
GwXDRFoundMapping mapping;
mapping=xdrMappings->getMapping(ActualHumidity, XDRHUMIDITY,(int)HumiditySource,0,HumidityInstance);
if (updateDouble(&mapping,ActualHumidity)){
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(ActualHumidity));
}
mapping=xdrMappings->getMapping(XDRHUMIDITY,(int)HumiditySource,1,HumidityInstance);
mapping=xdrMappings->getMapping(SetHumidity, XDRHUMIDITY,(int)HumiditySource,1,HumidityInstance);
if (updateDouble(&mapping,SetHumidity)){
LOG_DEBUG(GwLog::DEBUG+1,"found humidity mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(SetHumidity));
@@ -1472,7 +1349,7 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return;
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRPRESSURE,(int)PressureSource,0,PressureInstance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(ActualPressure, XDRPRESSURE,(int)PressureSource,0,PressureInstance);
if (! updateDouble(&mapping,ActualPressure)) return;
LOG_DEBUG(GwLog::DEBUG+1,"found pressure mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(ActualPressure));
@@ -1490,12 +1367,12 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
}
for (int i=0;i<8;i++){
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRENGINE,0,i,instance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(values[i], XDRENGINE,0,i,instance);
if (! updateDouble(&mapping,values[i])) continue;
addToXdr(mapping.buildXdrEntry(values[i]));
}
for (int i=0;i< 2;i++){
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRENGINE,0,i+8,instance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(ivalues[i],XDRENGINE,0,i+8,instance);
if (! updateDouble(&mapping,ivalues[i])) continue;
addToXdr(mapping.buildXdrEntry((double)ivalues[i]));
}
@@ -1511,7 +1388,7 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
}
for (int i=0;i<3;i++){
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRATTITUDE,0,i,instance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(values[i], XDRATTITUDE,0,i,instance);
if (! updateDouble(&mapping,values[i])) continue;
addToXdr(mapping.buildXdrEntry(values[i]));
}
@@ -1525,15 +1402,15 @@ private:
speed,pressure,tilt)){
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRENGINE,0,10,instance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(speed, XDRENGINE,0,10,instance);
if (updateDouble(&mapping,speed)){
addToXdr(mapping.buildXdrEntry(speed));
}
mapping=xdrMappings->getMapping(XDRENGINE,0,11,instance);
mapping=xdrMappings->getMapping(pressure, XDRENGINE,0,11,instance);
if (updateDouble(&mapping,pressure)){
addToXdr(mapping.buildXdrEntry(pressure));
}
mapping=xdrMappings->getMapping(XDRENGINE,0,12,instance);
mapping=xdrMappings->getMapping(tilt, XDRENGINE,0,12,instance);
if (updateDouble(&mapping,tilt)){
addToXdr(mapping.buildXdrEntry((double)tilt));
}
@@ -1559,12 +1436,12 @@ private:
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
return;
}
GwXDRFoundMapping mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
GwXDRFoundMapping mapping=xdrMappings->getMapping(Temperature, XDRTEMP,(int)TemperatureSource,0,TemperatureInstance);
if (updateDouble(&mapping,Temperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(Temperature));
}
mapping=xdrMappings->getMapping(XDRTEMP,(int)TemperatureSource,1,TemperatureInstance);
mapping=xdrMappings->getMapping(setTemperature, XDRTEMP,(int)TemperatureSource,1,TemperatureInstance);
if (updateDouble(&mapping,setTemperature)){
LOG_DEBUG(GwLog::DEBUG+1,"found temperature mapping %s",mapping.definition->toString().c_str());
addToXdr(mapping.buildXdrEntry(setTemperature));
@@ -1614,6 +1491,7 @@ private:
converters.registerConverter(129794UL, &N2kToNMEA0183Functions::HandleAISClassAMessage5); // AIS Class A Ship Static and Voyage related data, Message Type 5
converters.registerConverter(129809UL, &N2kToNMEA0183Functions::HandleAISClassBMessage24A); // AIS Class B "CS" Static Data Report, Part A
converters.registerConverter(129810UL, &N2kToNMEA0183Functions::HandleAISClassBMessage24B); // AIS Class B "CS" Static Data Report, Part B
converters.registerConverter(129041UL, &N2kToNMEA0183Functions::HandleAISMessage21); // AIS Aton
#endif
}

View File

@@ -26,7 +26,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <NMEA0183AISMessages.h>
#include "NMEA0183AISMessages.h"
#include <N2kTypes.h>
#include <N2kMsg.h>
#include <string.h>
@@ -34,7 +34,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//#include <unordered_map>
#include <sstream>
#include <math.h>
#include <NMEA0183AISMsg.h>
#include "NMEA0183AISMsg.h"
const double pi=3.1415926535897932384626433832795;
const double kmhToms=1000.0/3600.0;
@@ -47,17 +47,15 @@ const double nmTom=1.852*1000;
const double mToFathoms=0.546806649;
const double mToFeet=3.2808398950131;
const double radsToDegMin = 60 * 360.0 / (2 * pi); // [rad/s -> degree/minute]
const char Prefix='!';
std::vector<ship *> vships;
int numShips(){return vships.size();}
// ************************ Helper for AIS ***********************************
static bool AddMessageType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType);
static bool AddRepeat(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat);
static bool AddUserID(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t UserID);
static bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber);
static bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length);
//static bool AddVesselType(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t VesselType);
static bool AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam, double PosRefStbd, double PosRefBow);
static bool AddNavStatus(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t &NavStatus);
static bool AddROT(tNMEA0183AISMsg &NMEA0183AISMsg, double &rot);
@@ -81,8 +79,8 @@ static bool AddETADateTime(tNMEA0183AISMsg &NMEA0183AISMsg, uint16_t &ETAdate, d
//
// Got values from: ParseN2kPGN129038()
bool SetAISClassABMessage1( tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType, uint8_t Repeat,
uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
double COG, double SOG, double Heading, double ROT, uint8_t NavStatus ) {
uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
double COG, double SOG, double Heading, double ROT, uint8_t NavStatus ) {
NMEA0183AISMsg.ClearAIS();
if ( !AddMessageType(NMEA0183AISMsg, MessageType) ) return false; // 0 - 5 | 6 Message Type -> Constant: 1
@@ -91,7 +89,7 @@ bool SetAISClassABMessage1( tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType
if ( !AddNavStatus(NMEA0183AISMsg, NavStatus) ) return false; // 38-41 | 4 Navigational Status e.g.: "Under way sailing"
if ( !AddROT(NMEA0183AISMsg, ROT) ) return false; // 42-49 | 8 Rate of Turn (ROT)
if ( !AddSOG(NMEA0183AISMsg, SOG) ) return false; // 50-59 | 10 [m/s -> kts] SOG with one digit x10, 1023 = N/A
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy, 1) ) return false;// 60 | 1 GPS Accuracy 1 oder 0, Default 0
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy) ) return false;// 60 | 1 GPS Accuracy 1 oder 0, Default 0
if ( !AddLongitude(NMEA0183AISMsg, Longitude) ) return false; // 61-88 | 28 Longitude in Minutes / 10000
if ( !AddLatitude(NMEA0183AISMsg, Latitude) ) return false; // 89-115 | 27 Latitude in Minutes / 10000
if ( !AddCOG(NMEA0183AISMsg, COG) ) return false; // 116-127 | 12 Course over ground will be 3600 (0xE10) if that data is not available.
@@ -99,17 +97,12 @@ bool SetAISClassABMessage1( tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType
if ( !AddSeconds(NMEA0183AISMsg, Seconds) ) return false; // 137-142 | 6 Seconds in UTC timestamp)
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false; // 143-144 | 2 Maneuver Indicator: 0 (default) 1, 2 (not delivered within this PGN)
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 3) ) return false; // 145-147 | 3 Spare
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM, 1) ) return false; // 148-148 | 1 RAIM flag 0 = RAIM not in use (default), 1 = RAIM in use
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM) ) return false; // 148-148 | 1 RAIM flag 0 = RAIM not in use (default), 1 = RAIM in use
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 19) ) return false; // 149-167 | 19 Radio Status (-> 0 NOT SENT WITH THIS PGN!!!!!)
if ( !NMEA0183AISMsg.Init("VDM","AI", Prefix) ) return false;
if ( !NMEA0183AISMsg.AddStrField("1") ) return false;
if ( !NMEA0183AISMsg.AddStrField("1") ) return false;
if ( !NMEA0183AISMsg.AddEmptyField() ) return false;
if ( !NMEA0183AISMsg.AddStrField("A") ) return false;
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayload() ) ) return false;
if ( !NMEA0183AISMsg.AddStrField("0") ) return false; // Message 1,2,3 has always Zero Padding
if ( !NMEA0183AISMsg.InitAis()) return false;
int padBits=0;
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
return true;
}
@@ -121,14 +114,16 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name,
uint8_t VesselType, double Length, double Beam, double PosRefStbd,
double PosRefBow, uint16_t ETAdate, double ETAtime, double Draught,
char *Destination, tN2kGNSStype GNSStype, uint8_t DTE ) {
char *Destination, tN2kGNSStype GNSStype, uint8_t DTE,
tN2kAISVersion AISversion) {
// AIS Type 5 Message
NMEA0183AISMsg.ClearAIS();
if ( !AddMessageType(NMEA0183AISMsg, 5) ) return false; // 0 - 5 | 6 Message Type -> Constant: 5
if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
if ( !NMEA0183AISMsg.AddIntToPayloadBin(1, 2) ) return false; // 38 - 39 | 2 AIS Version -> 0 oder 1 NOT DERIVED FROM N2k, Always 1!!!!
if ( !NMEA0183AISMsg.AddIntToPayloadBin((uint32_t)AISversion, 2) )
return false; // 38 - 39 | 2 AIS Version -> 0 oder 1 NOT DERIVED FROM N2k, Always 1!!!!
if ( !AddIMONumber(NMEA0183AISMsg, IMONumber) ) return false; // 40 - 69 | 30 IMO Number unisgned
if ( !AddText(NMEA0183AISMsg, Callsign, 42) ) return false; // 70 - 111 | 42 Call Sign WDE4178 -> 7 6-bit characters -> Ascii lt. Table)
if ( !AddText(NMEA0183AISMsg, Name, 120) ) return false; // 112-231 | 120 Vessel Name POINT FERMIN -> 20 6-bit characters -> Ascii lt. Table
@@ -146,15 +141,17 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
// ****************************************************************************
// AIS position report (class B 129039) -> Type 18: Standard Class B CS Position Report
// ParseN2kPGN129039(const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID,
// PGN129039
// ParseN2kAISClassBPosition(const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID,
// double &Latitude, double &Longitude, bool &Accuracy, bool &RAIM,
// uint8_t &Seconds, double &COG, double &SOG, double &Heading, tN2kAISUnit &Unit,
// bool &Display, bool &DSC, bool &Band, bool &Msg22, tN2kAISMode &Mode, bool &State)
// uint8_t &Seconds, double &COG, double &SOG, tN2kAISTransceiverInformation &AISTransceiverInformation,
// double &Heading, tN2kAISUnit &Unit, bool &Display, bool &DSC, bool &Band, bool &Msg22, tN2kAISMode &Mode,
// bool &State)
// VDM, VDO (AIS VHF Data-link message 18)
bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID,
double Latitude, double Longitude, bool Accuracy, bool RAIM,
uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State) {
double Latitude, double Longitude, bool Accuracy, bool RAIM,
uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State) {
//
NMEA0183AISMsg.ClearAIS();
if ( !AddMessageType(NMEA0183AISMsg, MessageID) ) return false; // 0 - 5 | 6 Message Type -> Constant: 18
@@ -162,7 +159,7 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false; // 38-45 | 8 Regional Reserved
if ( !AddSOG(NMEA0183AISMsg, SOG) ) return false; // 46-55 | 10 [m/s -> kts] SOG with one digit x10, 1023 = N/A
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy, 1)) return false; // 56 | 1 GPS Accuracy 1 oder 0, Default 0
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy)) return false; // 56 | 1 GPS Accuracy 1 oder 0, Default 0
if ( !AddLongitude(NMEA0183AISMsg, Longitude) ) return false; // 57-84 | 28 Longitude in Minutes / 10000
if ( !AddLatitude(NMEA0183AISMsg, Latitude) ) return false; // 85-111 | 27 Latitude in Minutes / 10000
if ( !AddCOG(NMEA0183AISMsg, COG) ) return false; // 112-123 | 12 Course over ground will be 3600 (0xE10) if that data is not available.
@@ -171,20 +168,16 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false; // 139-140 | 2 Regional Reserved
if ( !NMEA0183AISMsg.AddIntToPayloadBin(Unit, 1) ) return false; // 141 | 1 0=Class B SOTDMA unit 1=Class B CS (Carrier Sense) unit
if ( !NMEA0183AISMsg.AddIntToPayloadBin(Display, 1) ) return false; // 142 | 1 0=No visual display, 1=Has display, (Probably not reliable).
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(DSC, 1) ) return false; // 143 | 1 If 1, unit is attached to a VHF voice radio with DSC capability.
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Band, 1) ) return false; // 144 | 1 If this flag is 1, the unit can use any part of the marine channel.
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Msg22, 1) ) return false; // 145 | 1 If 1, unit can accept a channel assignment via Message Type 22.
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Mode, 1) ) return false; // 146 | 1 Assigned-mode flag: 0 = autonomous mode (default), 1 = assigned mode
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM, 1) ) return false; // 147 | 1 as for Message Type 1,2,3
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(DSC) ) return false; // 143 | 1 If 1, unit is attached to a VHF voice radio with DSC capability.
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Band) ) return false; // 144 | 1 If this flag is 1, the unit can use any part of the marine channel.
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Msg22)) return false; // 145 | 1 If 1, unit can accept a channel assignment via Message Type 22.
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Mode) ) return false; // 146 | 1 Assigned-mode flag: 0 = autonomous mode (default), 1 = assigned mode
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM) ) return false; // 147 | 1 as for Message Type 1,2,3
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 20) ) return false; // 148-167 | 20 Radio Status not in PGN 129039
if ( !NMEA0183AISMsg.Init("VDM","AI", Prefix) ) return false;
if ( !NMEA0183AISMsg.AddStrField("1") ) return false;
if ( !NMEA0183AISMsg.AddStrField("1") ) return false;
if ( !NMEA0183AISMsg.AddEmptyField() ) return false;
if ( !NMEA0183AISMsg.AddStrField("B") ) return false;
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayload() ) ) return false;
if ( !NMEA0183AISMsg.AddStrField("0") ) return false; // Message 18, has always Zero Padding
if ( !NMEA0183AISMsg.InitAis()) return false;
int padBits=0;
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
return true;
}
@@ -209,7 +202,7 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
//
// PGN 129809 AIS Class B "CS" Static Data Report, Part A -> AIS VHF Data-link message 24
// PGN 129810 AIS Class B "CS" Static Data Report, Part B -> AIS VHF Data-link message 24
// ParseN2kPGN129809 (const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID, char *Name) -> store to vector
// ParseN2kPGN129809 (const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID, char *Name) -> store to vector
// ParseN2kPGN129810(const tN2kMsg &N2kMsg, uint8_t &MessageID, tN2kAISRepeat &Repeat, uint32_t &UserID,
// uint8_t &VesselType, char *Vendor, char *Callsign, double &Length, double &Beam,
// double &PosRefStbd, double &PosRefBow, uint32_t &MothershipID);
@@ -217,41 +210,28 @@ bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, u
// Part A: MessageID, Repeat, UserID, ShipName -> store in vector to call on Part B arrivals!!!
// Part B: MessageID, Repeat, UserID, VesselType (5), Callsign (5), Length & Beam, PosRefBow,.. (5)
bool SetAISClassBMessage24PartA(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID, char *Name) {
bool found = false;
for (size_t i = 0; i < vships.size(); i++) {
if ( vships[i]->_userID == UserID ) {
found = true;
break;
}
}
if ( ! found ) {
std::string nm;
nm+= Name;
vships.push_back(new ship(UserID, nm));
}
// AIS Type 24 Message
NMEA0183AISMsg.ClearAIS();
// Common for PART A AND Part B Bit 0 - 39 / len 40
if ( !AddMessageType(NMEA0183AISMsg, 24) ) return false; // 0 - 5 | 6 Message Type -> Constant: 24
if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 2) ) return false; // 38-39 | 2 Part Number 0-1 ->
// Part A: 40 + 128 = len 168
if ( !AddText(NMEA0183AISMsg, Name, 120) ) return false; // 40-159 | 120 Vessel Name 20 6-bit characters -> Ascii Table
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false; // 160-167 | 8 Spare
if ( !NMEA0183AISMsg.InitAis() ) return false;
int padBits=0;
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
return true;
}
// ***************************************************************************************************************
bool SetAISClassBMessage24(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
bool SetAISClassBMessage24PartB(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
uint32_t UserID, uint8_t VesselType, char *VendorID, char *Callsign,
double Length, double Beam, double PosRefStbd, double PosRefBow, uint32_t MothershipID ) {
uint8_t PartNr = 0; // Identifier for the message part number; always 0 for Part A
char *ShipName = (char*)" "; // get from vector to look up for sent Messages Part A
uint8_t i;
for ( i = 0; i < vships.size(); i++) {
if ( vships[i]->_userID == UserID ) {
ShipName = const_cast<char*>( vships[i]->_shipName.c_str() );
}
}
if ( i > MAX_SHIP_IN_VECTOR ) {
std::vector<ship *>::iterator it=vships.begin();
delete *it;
vships.erase(it);
}
// AIS Type 24 Message
NMEA0183AISMsg.ClearAIS();
@@ -259,11 +239,7 @@ bool SetAISClassBMessage24(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID,
if ( !AddMessageType(NMEA0183AISMsg, 24) ) return false; // 0 - 5 | 6 Message Type -> Constant: 24
if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
if ( !NMEA0183AISMsg.AddIntToPayloadBin(PartNr, 2) ) return false; // 38-39 | 2 Part Number 0-1 ->
// Part A: 40 + 128 = len 168
if ( !AddText(NMEA0183AISMsg, ShipName, 120) ) return false; // 40-159 | 120 Vessel Name 20 6-bit characters -> Ascii Table
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 8) ) return false; // 160-167 | 8 Spare
if ( !NMEA0183AISMsg.AddIntToPayloadBin(1, 2) ) return false; // 38-39 | 2 Part Number 0-1 ->
// https://www.navcen.uscg.gov/?pageName=AISMessagesB
// PART B: 40 + 128 = len 168
@@ -272,6 +248,59 @@ bool SetAISClassBMessage24(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID,
if ( !AddText(NMEA0183AISMsg, Callsign, 42) ) return false; // 218-259 | 90-131 | 42 Call Sign WDE4178 -> 7 6-bit characters, as in Msg Type 5
if ( !AddDimensions(NMEA0183AISMsg, Length, Beam, PosRefStbd, PosRefBow) ) return false; // 260-289 | 132-161 | 30 Dimensions
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0, 6) ) return false; // 290-295 | 162-167 | 6 Spare
if ( !NMEA0183AISMsg.InitAis() ) return false;
int padBits=0;
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayloadFix(padBits) ) ) return false;
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
return true;
}
// ****************************************************************************
// AIS ATON report (129041) -> Type 21: Position and status report for aids-to-navigation
// PGN129041
bool SetAISMessage21(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat, uint32_t UserID,
double Latitude, double Longitude, bool Accuracy, bool RAIM,
uint8_t Seconds, double Length, double Beam, double PositionReferenceStarboard,
double PositionReferenceTrueNord, tN2kAISAtoNType Type, bool OffPositionIndicator,
bool VirtualAtoNFlag, bool AssignedModeFlag, tN2kGNSStype GNSSType, uint8_t AtoNStatus,
char * atonName ) {
//
NMEA0183AISMsg.ClearAIS();
if ( !AddMessageType(NMEA0183AISMsg, 21) ) return false; // 0 - 5 | 6 Message Type -> Constant: 18
if ( !AddRepeat(NMEA0183AISMsg, Repeat) ) return false; // 6 - 7 | 2 Repeat Indicator: 0 = default; 3 = do not repeat any more
if ( !AddUserID(NMEA0183AISMsg, UserID) ) return false; // 8 - 37 | 30 MMSI
if ( ! NMEA0183AISMsg.AddIntToPayloadBin(Type,5)) return false; // | 5 aid type
//the name must be split:
//if it's > 120 bits the rest goes to the last parameter
if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(atonName,120))
return false; // | 120 name
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(Accuracy) ) return false; // | 1 accuracy
if ( !AddLongitude(NMEA0183AISMsg,Longitude)) return false; // | 28 lon
if ( !AddLatitude(NMEA0183AISMsg,Latitude)) return false; // | 27 lat
if ( !AddDimensions(NMEA0183AISMsg, Length, Beam,
PositionReferenceStarboard, PositionReferenceTrueNord)) return false; // | 30 dim
if ( !AddEPFDFixType(NMEA0183AISMsg,GNSSType)) return false; // | 4 fix type
if ( !AddSeconds(NMEA0183AISMsg,Seconds)) return false; // | 6 second
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(OffPositionIndicator))
return false; // | 1 off
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0,8)) return false; // | 8 reserverd
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(RAIM)) return false; // | 1 raim
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(VirtualAtoNFlag))
return false; // | 1 virt
if ( !NMEA0183AISMsg.AddBoolToPayloadBin(AssignedModeFlag))
return false; // | 1 assigned
if ( !NMEA0183AISMsg.AddIntToPayloadBin(0,1)) return false; // | 1 spare
size_t l=strlen(atonName);
if (l >=20){
uint8_t bitlen=(l-20)*6;
if (bitlen > 88) bitlen=88;
if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(atonName+20,bitlen)) return false; // | name
}
if ( !NMEA0183AISMsg.InitAis() ) return false;
int padBits=0;
if ( !NMEA0183AISMsg.AddStrField( NMEA0183AISMsg.GetPayload(padBits) ) ) return false;
if ( !NMEA0183AISMsg.AddUInt32Field(padBits) ) return false;
return true;
}
@@ -325,7 +354,6 @@ bool AddIMONumber(tNMEA0183AISMsg &NMEA0183AISMsg, uint32_t &IMONumber) {
// 120bit Name or Destination
bool AddText(tNMEA0183AISMsg &NMEA0183AISMsg, char *FieldVal, uint8_t length) {
uint8_t len = length/6;
if ( strlen(FieldVal) > len ) FieldVal[len] = 0;
if ( !NMEA0183AISMsg.AddEncodedCharToPayloadBin(FieldVal, length) ) return false;
return true;
@@ -347,29 +375,26 @@ bool AddDimensions(tNMEA0183AISMsg &NMEA0183AISMsg, double Length, double Beam,
uint16_t _PosRefStbd = 0;
uint16_t _PosRefPort = 0;
if (PosRefBow < 0) PosRefBow=0; //could be N2kIsNA
if ( PosRefBow <= 511.0 ) {
_PosRefBow = round(PosRefBow);
if ( PosRefBow >= 0.0 && PosRefBow <= 511.0 ) {
_PosRefBow = ceil(PosRefBow);
} else {
_PosRefBow = 511;
}
if (PosRefStbd < 0 ) PosRefStbd=0; //could be N2kIsNA
if (PosRefStbd <= 63.0 ) {
_PosRefStbd = round(PosRefStbd);
if ( PosRefStbd >= 0.0 && PosRefStbd <= 63.0 ) {
_PosRefStbd = ceil(PosRefStbd);
} else {
_PosRefStbd = 63;
}
if ( !N2kIsNA(Length) ) {
if (Length >= PosRefBow){
_PosRefStern=round(Length - PosRefBow);
}
_PosRefStern = ceil( Length ) - _PosRefBow;
if ( _PosRefStern < 0 ) _PosRefStern = 0;
if ( _PosRefStern > 511 ) _PosRefStern = 511;
}
if ( !N2kIsNA(Beam) ) {
if (Beam >= PosRefStbd){
_PosRefPort = round( Beam - PosRefStbd);
}
_PosRefPort = ceil( Beam ) - _PosRefStbd;
if ( _PosRefPort < 0 ) _PosRefPort = 0;
if ( _PosRefPort > 63 ) _PosRefPort = 63;
}
@@ -572,3 +597,5 @@ bool AddETADateTime(tNMEA0183AISMsg &NMEA0183AISMsg, uint16_t &ETAdate, double &
if ( ! NMEA0183AISMsg.AddIntToPayloadBin(minute, 6) ) return false;
return true;
}

View File

@@ -27,29 +27,21 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef _tNMEA0183AISMessages_H_
#define _tNMEA0183AISMessages_H_
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <N2kTypes.h>
#include <NMEA0183AISMsg.h>
#include "NMEA0183AISMsg.h"
#include <stddef.h>
#include <vector>
#include <string>
#define MAX_SHIP_IN_VECTOR 200
class ship {
public:
uint32_t _userID;
std::string _shipName;
ship(uint32_t UserID, std::string ShipName) : _userID(UserID), _shipName(ShipName) {}
};
// Types 1, 2 and 3: Position Report Class A or B
bool SetAISClassABMessage1(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageType, uint8_t Repeat,
uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
double COG, double SOG, double Heading, double ROT, uint8_t NavStatus);
uint32_t UserID, double Latitude, double Longitude, bool Accuracy, bool RAIM, uint8_t Seconds,
double COG, double SOG, double Heading, double ROT, uint8_t NavStatus);
//*****************************************************************************
// AIS Class A Static and Voyage Related Data Message Type 5
@@ -57,14 +49,15 @@ bool SetAISClassAMessage5(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, ui
uint32_t UserID, uint32_t IMONumber, char *Callsign, char *Name,
uint8_t VesselType, double Length, double Beam, double PosRefStbd,
double PosRefBow, uint16_t ETAdate, double ETAtime, double Draught,
char *Destination, tN2kGNSStype GNSStype, uint8_t DTE );
char *Destination, tN2kGNSStype GNSStype, uint8_t DTE,
tN2kAISVersion AISversion);
//*****************************************************************************
// AIS position report (class B 129039) -> Standard Class B CS Position Report Message Type 18 Part B
bool SetAISClassBMessage18(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat, uint32_t UserID,
double Latitude, double Longitude, bool Accuracy, bool RAIM,
uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State);
double Latitude, double Longitude, bool Accuracy, bool RAIM,
uint8_t Seconds, double COG, double SOG, double Heading, tN2kAISUnit Unit,
bool Display, bool DSC, bool Band, bool Msg22, bool Mode, bool State);
//*****************************************************************************
// Static Data Report Class B, Message Type 24
@@ -73,11 +66,19 @@ bool SetAISClassBMessage24PartA(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Message
//*****************************************************************************
// Static Data Report Class B, Message Type 24
bool SetAISClassBMessage24(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
bool SetAISClassBMessage24PartB(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t MessageID, uint8_t Repeat,
uint32_t UserID, uint8_t VesselType, char *VendorID, char *Callsign,
double Length, double Beam, double PosRefStbd, double PosRefBow, uint32_t MothershipID );
int numShips();
//*****************************************************************************
// Aton class 21
bool SetAISMessage21(tNMEA0183AISMsg &NMEA0183AISMsg, uint8_t Repeat, uint32_t UserID,
double Latitude, double Longitude, bool Accuracy, bool RAIM,
uint8_t Seconds, double Length, double Beam, double PositionReferenceStarboard,
double PositionReferenceTrueNord, tN2kAISAtoNType Type, bool OffPositionIndicator,
bool VirtualAtoNFlag, bool AssignedModeFlag, tN2kGNSStype GNSSType, uint8_t AtoNStatus,
char * atonName );
inline int32_t aRoundToInt(double x) {
return x >= 0
? (int32_t) floor(x + 0.5)

View File

@@ -25,7 +25,7 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "NMEA0183AISMsg.h"
#include <NMEA0183Msg.h>
#include <Arduino.h>
//#include <Arduino.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
@@ -43,52 +43,37 @@ tNMEA0183AISMsg::tNMEA0183AISMsg() {
//*****************************************************************************
void tNMEA0183AISMsg::ClearAIS() {
PayloadBin[0]=0;
Payload[0]=0;
PayloadBin.reset();
iAddPldBin=0;
iAddPld=0;
}
//*****************************************************************************
// Add 6bit with no data.
bool tNMEA0183AISMsg::AddEmptyFieldToPayloadBin(uint8_t iBits) {
if ( (iAddPldBin + iBits * 6) >= AIS_BIN_MAX_LEN ) return false; // Is there room for any data
for (uint8_t i=0;i<iBits;i++) {
strncpy(PayloadBin+iAddPldBin, EmptyAISField, 6);
iAddPldBin+=6;
}
return true;
}
//*****************************************************************************
bool tNMEA0183AISMsg::AddIntToPayloadBin(int32_t ival, uint16_t countBits) {
if ( (iAddPldBin + countBits ) >= AIS_BIN_MAX_LEN ) return false; // Is there room for any data
AISBitSet bset(ival);
bset = ival;
PayloadBin[iAddPldBin]=0;
uint16_t iAdd=iAddPldBin;
for(int i = countBits-1; i >= 0 ; i--) {
PayloadBin[iAdd] = bset[i]?'1':'0';
PayloadBin[iAdd]=bset [i];
iAdd++;
}
iAddPldBin += countBits;
PayloadBin[iAddPldBin]=0;
return true;
}
// ****************************************************************************
bool tNMEA0183AISMsg::AddBoolToPayloadBin(bool &bval, uint8_t size) {
int8_t iTemp;
(bval == true)? iTemp = 1 : iTemp = 0;
if ( ! AddIntToPayloadBin(iTemp, size) ) return false;
//****************************************************************************
bool tNMEA0183AISMsg::AddBoolToPayloadBin(bool &bval) {
if ( (iAddPldBin + 1 ) >= AIS_BIN_MAX_LEN ) return false;
PayloadBin[iAddPldBin]=bval;
iAddPldBin++;
return true;
}
@@ -99,13 +84,11 @@ bool tNMEA0183AISMsg::AddEncodedCharToPayloadBin(char *sval, size_t countBits) {
if ( (iAddPldBin + countBits ) >= AIS_BIN_MAX_LEN ) return false; // Is there room for any data
PayloadBin[iAddPldBin]=0;
std::bitset<6> bs;
char * ptr;
const char * ptr;
size_t len = strlen(sval); // e.g.: should be 7 for Callsign
if ( len * 6 > countBits ) len = countBits / 6;
for (int i = 0; i<len; i++) {
for (size_t i = 0; i<len; i++) {
ptr = strchr(AsciiChar, sval[i]);
if ( ptr ) {
@@ -117,37 +100,44 @@ bool tNMEA0183AISMsg::AddEncodedCharToPayloadBin(char *sval, size_t countBits) {
AddIntToPayloadBin(0, 6);
}
}
PayloadBin[iAddPldBin+1]=0;
// fill up with "@", also covers empty sval
if ( len * 6 < countBits ) {
for (int i=0;i<(countBits/6-len);i++) {
for (size_t i=0;i<(countBits/6-len);i++) {
AddIntToPayloadBin(0, 6);
}
}
PayloadBin[iAddPldBin]=0;
return true;
}
// *****************************************************************************
bool tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(const char *payloadbin) {
uint16_t len;
len = strlen( payloadbin ) / 6; // 28
//*****************************************************************************
template <unsigned int S>
int tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(std::bitset<S> &src,uint16_t maxSize,uint16_t bitSize,uint16_t stoffset) {
Payload[0]='\0';
uint16_t slen=maxSize;
if (stoffset >= slen) return 0;
slen-=stoffset;
uint16_t bitLen=bitSize > 0?bitSize:slen;
uint16_t len= bitLen / 6;
if ((len * 6) < bitLen) len+=1;
uint16_t padBits=0;
uint32_t offset;
char s[7];
std::bitset<6> s;
uint8_t dec;
int i;
for ( i=0; i<len; i++ ) {
offset = i * 6;
int k = 0;
for (int j=offset; j<offset+6; j++ ) {
s[k] = payloadbin[j];
k++;
int k = 5;
for (uint32_t j=offset; j<offset+6; j++ ) {
if (j < slen){
s[k] = src[stoffset+j];
}
else{
s[k] = 0;
padBits++;
}
k--;
}
s[k]=0;
dec = strtoull (s, NULL, 2); //binToDec
dec = s.to_ulong();
if (dec < 40 ) dec += 48;
else dec += 56;
@@ -156,142 +146,56 @@ bool tNMEA0183AISMsg::ConvertBinaryAISPayloadBinToAscii(const char *payloadbin)
}
Payload[i]=0;
return true;
return padBits;
}
void tNMEA0183AISMsg::SetChannelAndTalker(bool channelA,bool own){
channel[0]=channelA?'A':'B';
strcpy(talker,own?"VDO":"VDM");
}
//********************** BUILD 2-parted AIS Sentences ************************
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg5Part1(tNMEA0183AISMsg &AISMsg) {
Init("VDM", "AI", '!');
AddStrField("2");
AddStrField("1");
AddStrField("5");
AddStrField("A");
AddStrField( GetPayloadType5_Part1() );
AddStrField("0");
return AISMsg;
bool tNMEA0183AISMsg::InitAis(int max,int number,int sequence){
if ( !Init(talker,"AI", '!') ) return false;
if ( !AddUInt32Field(max) ) return false;
if ( !AddUInt32Field(number) ) return false;
if (sequence >= 0){
if ( !AddUInt32Field(sequence) ) return false;
}
else{
if ( !AddEmptyField() ) return false;
}
if ( !AddStrField(channel) ) return false;
return true;
}
bool tNMEA0183AISMsg::BuildMsg5Part1() {
if ( iAddPldBin != 424 ) return false;
InitAis(2,1,5);
int padBits=0;
AddStrField( GetPayload(padBits,0,336));
AddUInt32Field(padBits);
return true;
}
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg5Part2(tNMEA0183AISMsg &AISMsg) {
Init("VDM", "AI", '!');
AddStrField("2");
AddStrField("2");
AddStrField("5");
AddStrField("A");
AddStrField( GetPayloadType5_Part2() );
AddStrField("2"); // Message 5, Part 2 has always 2 Padding Zeros
return AISMsg;
bool tNMEA0183AISMsg::BuildMsg5Part2() {
if ( iAddPldBin != 424 ) return false;
InitAis(2,2,5);
int padBits=0;
AddStrField( GetPayload(padBits,336,88) );
AddUInt32Field(padBits);
return true;
}
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg24PartA(tNMEA0183AISMsg &AISMsg) {
Init("VDM", "AI", '!');
AddStrField("1");
AddStrField("1");
AddEmptyField();
AddStrField("A");
AddStrField( GetPayloadType24_PartA() );
AddStrField("0");
return AISMsg;
}
const tNMEA0183AISMsg& tNMEA0183AISMsg::BuildMsg24PartB(tNMEA0183AISMsg &AISMsg) {
Init("VDM", "AI", '!');
AddStrField("1");
AddStrField("1");
AddEmptyField();
AddStrField("A");
AddStrField( GetPayloadType24_PartB() );
AddStrField("0"); // Message 24, both parts have always Zero Padding
return AISMsg;
}
//******************************* AIS PAYLOADS *********************************
//******************************************************************************
// get converted Payload for Message 1, 2, 3 & 18, always Length 168
const char *tNMEA0183AISMsg::GetPayload() {
uint16_t lenbin = strlen( PayloadBin);
if ( lenbin != 168 ) return nullptr;
if ( !ConvertBinaryAISPayloadBinToAscii( PayloadBin ) ) return nullptr;
const char *tNMEA0183AISMsg::GetPayloadFix(int &padBits,uint16_t fixLen){
uint16_t lenbin = iAddPldBin;
if ( lenbin != fixLen ) return nullptr;
return GetPayload(padBits,0,0);
}
const char *tNMEA0183AISMsg::GetPayload(int &padBits,uint16_t offset,uint16_t bitLen) {
padBits=ConvertBinaryAISPayloadBinToAscii<AIS_BIN_MAX_LEN>(PayloadBin,iAddPldBin, bitLen,offset );
return Payload;
}
//******************************************************************************
// get converted Part 1 of Payload for Message 5
const char *tNMEA0183AISMsg::GetPayloadType5_Part1() {
uint16_t lenbin = strlen( PayloadBin);
if ( lenbin != 424 ) return nullptr;
char to[337];
strncpy(to, PayloadBin, 336); // First Part is always 336 Length
to[336]=0;
if ( !ConvertBinaryAISPayloadBinToAscii( to ) ) return nullptr;
return Payload;
}
//******************************************************************************
// get converted Part 2 of Payload for Message 5
const char *tNMEA0183AISMsg::GetPayloadType5_Part2() {
uint16_t lenbin = strlen( PayloadBin);
if ( lenbin != 424 ) return nullptr;
lenbin = 88; // Second Part is always 424 - 336 + 2 padding Zeros in Length
char to[91];
strncpy(to, PayloadBin + 336, lenbin);
to[88]='0'; to[89]='0'; to[90]=0;
if ( !ConvertBinaryAISPayloadBinToAscii( to ) ) return nullptr;
return Payload;
}
//******************************************************************************
// get converted Part A of Payload for Message 24
// Bit 0.....167, len 168
// In PayloadBin is Part A and Part B chained together with Length 296
const char *tNMEA0183AISMsg::GetPayloadType24_PartA() {
uint16_t lenbin = strlen( PayloadBin);
if ( lenbin != 296 ) return nullptr; // too short for Part A
char to[169]; // Part A has Length 168
*to = '\0';
for (int i=0; i<168; i++){
to[i] = PayloadBin[i];
}
to[168]=0;
if ( !ConvertBinaryAISPayloadBinToAscii( to ) ) return nullptr;
return Payload;
}
//******************************************************************************
// get converted Part B of Payload for Message 24
// Bit 0.....38 + bit39='1' (part number) + bit 168........295 296='\0' of total PayloadBin
// binary part B: len 40 + 128 = len 168
const char *tNMEA0183AISMsg::GetPayloadType24_PartB() {
uint16_t lenbin = strlen( PayloadBin);
if ( lenbin != 296 ) return nullptr; // too short for Part B
char to[169]; // Part B has Length 168
*to = '\0';
for (int i=0; i<39; i++){
to[i] = PayloadBin[i];
}
to[39] = 49; // part number 1
for (int i=40; i<168; i++) {
to[i] = PayloadBin[i+128];
}
to[168]=0;
if ( !ConvertBinaryAISPayloadBinToAscii( to ) ) return nullptr;
return Payload;
}

View File

@@ -45,43 +45,48 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#define BITSET_LENGTH 120
typedef std::bitset<BITSET_LENGTH> AISBitSet;
class tNMEA0183AISMsg : public tNMEA0183Msg {
protected: // AIS-NMEA
std::bitset<BITSET_LENGTH> bset;
static const char *EmptyAISField; // 6bits 0 not used yet.....
static const char *AsciChar;
uint16_t iAddPldBin;
char Payload[AIS_MSG_MAX_LEN];
uint8_t iAddPld;
char talker[4]="VDM";
char channel[2]="A";
std::bitset<AIS_BIN_MAX_LEN> PayloadBin;
public:
char PayloadBin[AIS_BIN_MAX_LEN];
char PayloadBin2[AIS_BIN_MAX_LEN];
// Clear message
void ClearAIS();
public:
tNMEA0183AISMsg();
const char *GetPayload();
const char *GetPayloadType5_Part1();
const char *GetPayloadType5_Part2();
const char *GetPayloadType24_PartA();
const char *GetPayloadType24_PartB();
const char *GetPayloadBin() const { return PayloadBin; }
const char *GetPayloadFix(int &padBits,uint16_t fixLen=168);
const char *GetPayload(int &padBits,uint16_t offset=0,uint16_t bitLen=0);
const tNMEA0183AISMsg& BuildMsg5Part1(tNMEA0183AISMsg &AISMsg);
const tNMEA0183AISMsg& BuildMsg5Part2(tNMEA0183AISMsg &AISMsg);
const tNMEA0183AISMsg& BuildMsg24PartA(tNMEA0183AISMsg &AISMsg);
const tNMEA0183AISMsg& BuildMsg24PartB(tNMEA0183AISMsg &AISMsg);
bool BuildMsg5Part1();
bool BuildMsg5Part2();
bool InitAis(int max=1,int number=1,int sequence=-1);
// Generally Used
bool AddIntToPayloadBin(int32_t ival, uint16_t countBits);
bool AddBoolToPayloadBin(bool &bval, uint8_t size);
bool AddBoolToPayloadBin(bool &bval);
bool AddEncodedCharToPayloadBin(char *sval, size_t Length);
bool AddEmptyFieldToPayloadBin(uint8_t iBits);
bool ConvertBinaryAISPayloadBinToAscii(const char *payloadbin);
/**
* @param channelA - if set A, otherwise B
* @param own - if set VDO, else VDM
*/
void SetChannelAndTalker(bool channelA,bool own=false);
/**
* convert the payload to ascii
* return the number of padding bits
* @param bitSize the number of bits to be used, 0 - use all bits
*/
template <unsigned int SZ>
int ConvertBinaryAISPayloadBinToAscii(std::bitset<SZ> &src,uint16_t maxSize, uint16_t bitSize,uint16_t offset=0);
// AIS Helper functions
protected:

View File

@@ -1,11 +1,11 @@
# NMEA2000 -> NMEA0183 AIS converter v1.0.0
# NMEA2000 to NMEA0183 AIS Converter
Import from https://github.com/ronzeiller/NMEA0183-AIS
NMEA0183 AIS library © Ronnie Zeiller, www.zeiller.eu
Addendum for NMEA2000 and NMEA0183 Library from Timo Lappalainen https://github.com/ttlappalainen
to get NMEA0183 AIS data from N2k-bus
## Conversions:
@@ -15,6 +15,33 @@ Addendum for NMEA2000 and NMEA0183 Library from Timo Lappalainen https://github.
- NMEA2000 PGN 129809 => AIS Class B "CS" Static Data Report, making a list of UserID (MMSI) and Ship Names used for Message 24 Part A
- NMEA2000 PGN 129810 => AIS Class B "CS" Static Data Report, Message 24 Part A+B
### Versions
1.0.6 2024-03-25
- fixed to work with Timo´s NMEA2000 v4.21.3
1.0.5 2023-12-02
- removed VDO remote print statements
1.0.4 2023-12-02
- merged @Isoltero master with fixed memory over run, added VDO remote print statements Thanks to Luis Soltero
- fixed example, thanks to @arduinomnomnom
1.0.3 2022-05-01
- Update Examples: AISTransceiverInformation in ParseN2kPGN129039 for changes in NMEA2000 library: https://github.com/ttlappalainen/NMEA2000
1.0.2 2022-04-30
- bugfix: malloc without free. Thanks to Luis Soltero (Issue https://github.com/ronzeiller/NMEA0183-AIS/issues/3)
1.0.1 2022-03-15
- bugfix: buffer overrun missing space for termination. Thanks to Luis Soltero (Issue https://github.com/ronzeiller/NMEA0183-AIS/issues/2)
2020-12-25
- corrected Navigational Status 0. Thanks to Li-Ren (Issue https://github.com/ronzeiller/NMEA0183-AIS/issues/1)
1.0.0 2019-11-24
- initial upload
### Remarks
1. Message Type could be set to 1 or 3 (identical messages) on demand
2. Maneuver Indicator (not part of NMEA2000 PGN 129038) => will be set to 0 (default)
@@ -33,17 +60,14 @@ To use this library you need also:
## License
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.
MIT license
Copyright (c) 2019-2022 Ronnie Zeiller, www.zeiller.eu
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.

View File

@@ -1,190 +0,0 @@
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "BoatDataCalibration.h"
#include <cmath>
#include <math.h>
#include <unordered_map>
CalibrationDataList calibrationData;
std::unordered_map<std::string, TypeCalibData> CalibrationDataList::calibMap; // list of calibration data instances
void CalibrationDataList::readConfig(GwConfigHandler* config, GwLog* logger)
// Initial load of calibration data into internal list
// This method is called once at init phase of <obp60task> to read the configuration values
{
std::string instance;
double offset;
double slope;
double smooth;
String calInstance = "";
String calOffset = "";
String calSlope = "";
String calSmooth = "";
// Load user format configuration values
String lengthFormat = config->getString(config->lengthFormat); // [m|ft]
String distanceFormat = config->getString(config->distanceFormat); // [m|km|nm]
String speedFormat = config->getString(config->speedFormat); // [m/s|km/h|kn]
String windspeedFormat = config->getString(config->windspeedFormat); // [m/s|km/h|kn|bft]
String tempFormat = config->getString(config->tempFormat); // [K|C|F]
// Read calibration settings for data instances
for (int i = 0; i < MAX_CALIBRATION_DATA; i++) {
calInstance = "calInstance" + String(i + 1);
calOffset = "calOffset" + String(i + 1);
calSlope = "calSlope" + String(i + 1);
calSmooth = "calSmooth" + String(i + 1);
instance = std::string(config->getString(calInstance, "---").c_str());
if (instance == "---") {
LOG_DEBUG(GwLog::LOG, "no calibration data for instance no. %d", i + 1);
continue;
}
calibMap[instance] = { 0.0f, 1.0f, 1.0f, 0.0f, false };
offset = (config->getString(calOffset, "")).toFloat();
slope = (config->getString(calSlope, "")).toFloat();
smooth = (config->getString(calSmooth, "")).toInt(); // user input is int; further math is done with double
// Convert calibration values to internal standard formats
if (instance == "AWS" || instance == "TWS") {
if (windspeedFormat == "m/s") {
// No conversion needed
} else if (windspeedFormat == "km/h") {
offset /= 3.6; // Convert km/h to m/s
} else if (windspeedFormat == "kn") {
offset /= 1.94384; // Convert kn to m/s
} else if (windspeedFormat == "bft") {
offset *= 2 + (offset / 2); // Convert Bft to m/s (approx) -> to be improved
}
} else if (instance == "AWA" || instance == "COG" || instance == "TWA" || instance == "TWD" || instance == "HDM" || instance == "PRPOS" || instance == "RPOS") {
offset *= M_PI / 180; // Convert deg to rad
} else if (instance == "DBT") {
if (lengthFormat == "m") {
// No conversion needed
} else if (lengthFormat == "ft") {
offset /= 3.28084; // Convert ft to m
}
} else if (instance == "SOG" || instance == "STW") {
if (speedFormat == "m/s") {
// No conversion needed
} else if (speedFormat == "km/h") {
offset /= 3.6; // Convert km/h to m/s
} else if (speedFormat == "kn") {
offset /= 1.94384; // Convert kn to m/s
}
} else if (instance == "WTemp") {
if (tempFormat == "K" || tempFormat == "C") {
// No conversion needed
} else if (tempFormat == "F") {
offset *= 9.0 / 5.0; // Convert °F to K
slope *= 9.0 / 5.0; // Convert °F to K
}
}
// transform smoothing factor from {0.01..10} to {0.3..0.95} and invert for exponential smoothing formula
if (smooth <= 0) {
smooth = 0;
} else {
if (smooth > 10) {
smooth = 10;
}
smooth = 0.3 + ((smooth - 0.01) * (0.95 - 0.3) / (10 - 0.01));
}
smooth = 1 - smooth;
calibMap[instance].offset = offset;
calibMap[instance].slope = slope;
calibMap[instance].smooth = smooth;
calibMap[instance].isCalibrated = false;
LOG_DEBUG(GwLog::LOG, "calibration data: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
calibMap[instance].offset, calibMap[instance].slope, calibMap[instance].smooth);
}
LOG_DEBUG(GwLog::LOG, "all calibration data read");
}
void CalibrationDataList::calibrateInstance(GwApi::BoatValue* boatDataValue, GwLog* logger)
// Method to calibrate the boat data value
{
std::string instance = boatDataValue->getName().c_str();
double offset = 0;
double slope = 1.0;
double dataValue = 0;
std::string format = "";
if (calibMap.find(instance) == calibMap.end()) {
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
return;
} else if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
calibMap[instance].isCalibrated = false;
return;
} else {
offset = calibMap[instance].offset;
slope = calibMap[instance].slope;
dataValue = boatDataValue->value;
format = boatDataValue->getFormat().c_str();
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
if (format == "formatWind") { // instance is of type angle
dataValue = (dataValue * slope) + offset;
dataValue = fmod(dataValue, 2 * M_PI);
if (dataValue > (M_PI)) {
dataValue -= (2 * M_PI);
} else if (dataValue < (M_PI * -1)) {
dataValue += (2 * M_PI);
}
} else if (format == "formatCourse") { // instance is of type direction
dataValue = (dataValue * slope) + offset;
dataValue = fmod(dataValue, 2 * M_PI);
if (dataValue < 0) {
dataValue += (2 * M_PI);
}
} else if (format == "kelvinToC") { // instance is of type temperature
dataValue = ((dataValue - 273.15) * slope) + offset + 273.15;
} else {
dataValue = (dataValue * slope) + offset;
}
calibMap[instance].isCalibrated = true;
boatDataValue->value = dataValue;
calibrationData.smoothInstance(boatDataValue, logger); // smooth the boat data value
calibMap[instance].value = boatDataValue->value; // store the calibrated + smoothed value in the list
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibMap[instance].value);
}
}
void CalibrationDataList::smoothInstance(GwApi::BoatValue* boatDataValue, GwLog* logger)
// Method to smoothen the boat data value
{
static std::unordered_map<std::string, double> lastValue; // array for last values of smoothed boat data values
std::string instance = boatDataValue->getName().c_str();
double oldValue = 0;
double dataValue = boatDataValue->value;
double smoothFactor = 0;
if (!boatDataValue->valid) { // no valid boat data value, so we don't want to smoothen value
return;
} else if (calibMap.find(instance) == calibMap.end()) {
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: smooth factor for %s not found in calibration list", instance.c_str());
return;
} else {
smoothFactor = calibMap[instance].smooth;
if (lastValue.find(instance) != lastValue.end()) {
oldValue = lastValue[instance];
dataValue = oldValue + (smoothFactor * (dataValue - oldValue)); // exponential smoothing algorithm
}
lastValue[instance] = dataValue; // store the new value for next cycle; first time, store only the current value and return
boatDataValue->value = dataValue; // set the smoothed value to the boat data value
}
}
#endif

View File

@@ -1,34 +0,0 @@
// Functions lib for data instance calibration
#ifndef _BOATDATACALIBRATION_H
#define _BOATDATACALIBRATION_H
// #include "Pagedata.h"
#include "GwApi.h"
#include <string>
#include <unordered_map>
#define MAX_CALIBRATION_DATA 3 // maximum number of calibration data instances
typedef struct {
double offset; // calibration offset
double slope; // calibration slope
double smooth; // smoothing factor
double value; // calibrated data value
bool isCalibrated; // is data instance value calibrated?
} TypeCalibData;
class CalibrationDataList {
public:
static std::unordered_map<std::string, TypeCalibData> calibMap; // list of calibration data instances
void readConfig(GwConfigHandler* config, GwLog* logger);
void calibrateInstance(GwApi::BoatValue* boatDataValue, GwLog* logger);
void smoothInstance(GwApi::BoatValue* boatDataValue, GwLog* logger);
private:
};
extern CalibrationDataList calibrationData; // this list holds all calibration data
#endif

View File

@@ -22,9 +22,11 @@ static uint8_t mulcolor(uint8_t f1, uint8_t f2){
}
Color setBrightness(const Color &color,uint8_t brightness){
if (brightness > 100) brightness = 100;
uint16_t br255=brightness*255;
br255=br255/100;
//very simple for now
//Very simple for now
Color rt=color;
rt.g=mulcolor(rt.g,br255);
rt.b=mulcolor(rt.b,br255);

View File

@@ -57,7 +57,7 @@ GxEPD2_BW<GxEPD2_420_SE0420NQ04, GxEPD2_420_SE0420NQ04::HEIGHT> & getdisplay(){r
#endif
// Horter I2C moduls
PCF8574 pcf8574_Out(PCF8574_I2C_ADDR1); // First digital output modul PCF8574 from Horter
PCF8574 pcf8574_Modul1(PCF8574_I2C_ADDR1); // First digital IO modul PCF8574 from Horter
// FRAM
Adafruit_FRAM_I2C fram;
@@ -89,8 +89,8 @@ void hardwareInit(GwApi *api)
Wire.begin();
// Init PCF8574 digital outputs
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
if(pcf8574_Out.begin()){ // Initialize PCF8574
pcf8574_Out.write8(255); // Clear all outputs
if(pcf8574_Modul1.begin()){ // Initialize PCF8574
pcf8574_Modul1.write8(255); // Clear all outputs (low activ)
}
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
fram = Adafruit_FRAM_I2C();
@@ -193,12 +193,25 @@ void powerInit(String powermode) {
}
}
void setPCF8574PortPin(uint pin, uint8_t value){
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz
if(pcf8574_Out.begin()){ // Check available and initialize PCF8574
pcf8574_Out.write(pin, value); // Toggle pin
}
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
void setPCF8574PortPinModul1(uint8_t pin, uint8_t value)
{
static bool firstRunFinished;
static uint8_t port1; // Retained data for port bits
// If fisrt run then set all outputs to low
if(firstRunFinished == false){
port1 = 255; // Low active
firstRunFinished = true;
}
if (pin > 7) return;
Wire.setClock(I2C_SPEED_LOW); // Set I2C clock on 10 kHz for longer wires
// Set bit
if (pcf8574_Modul1.begin(port1)) // Check module availability and start it
{
if (value == LOW) port1 &= ~(1 << pin); // Set bit
else port1 |= (1 << pin);
pcf8574_Modul1.write8(port1); // Write byte
}
Wire.setClock(I2C_SPEED); // Set I2C clock on 100 kHz
}
@@ -318,6 +331,40 @@ void toggleBacklightLED(uint brightness, const Color &color){
ledTaskData->setLedData(current);
}
void stepsBacklightLED(uint brightness, const Color &color){
static uint step = 0;
uint actBrightness = 0;
// Different brightness steps
if(step == 0){
actBrightness = brightness; // 100% from brightess
statusBacklightLED = true;
}
if(step == 1){
actBrightness = brightness * 0.5; // 50% from brightess
statusBacklightLED = true;
}
if(step == 2){
actBrightness = brightness * 0.2; // 20% from brightess
statusBacklightLED = true;
}
if(step == 3){
actBrightness = 0; // 0%
statusBacklightLED = false;
}
if(actBrightness < 5){ // Limiter if values too low
actBrightness = 5;
}
step = step + 1; // Increment step counter
if(step == 4){ // Reset counter
step = 0;
}
if (ledTaskData == nullptr) return;
Color nv=setBrightness(statusBacklightLED?color:COLOR_BLACK,actBrightness);
LedInterface current=ledTaskData->getLedData();
current.setBacklight(nv);
ledTaskData->setLedData(current);
}
void setFlashLED(bool status){
if (ledTaskData == nullptr) return;
Color c=status?COLOR_RED:COLOR_BLACK;
@@ -437,6 +484,27 @@ void drawTextCenter(int16_t cx, int16_t cy, String text) {
getdisplay().print(text);
}
// Draw centered botton with centered text
void drawButtonCenter(int16_t cx, int16_t cy, int8_t sx, int8_t sy, String text, uint16_t fg, uint16_t bg, bool inverted) {
int16_t x1, y1;
uint16_t w, h;
uint16_t color;
getdisplay().getTextBounds(text, cx, cy, &x1, &y1, &w, &h); // Find text center
getdisplay().setCursor(cx - w/2, cy + h/2); // Set cursor to center
//getdisplay().drawPixel(cx, cy, fg); // Debug pixel for center position
if (inverted) {
getdisplay().fillRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
getdisplay().setTextColor(bg);
getdisplay().print(text); // Draw text
}
else{
getdisplay().drawRoundRect(cx - sx / 2, cy - sy / 2, sx, sy, 5, fg); // Draw button
getdisplay().setTextColor(fg);
getdisplay().print(text); // Draw text
}
}
// Draw right aligned text
void drawTextRalign(int16_t x, int16_t y, String text) {
int16_t x1, y1;

View File

@@ -89,13 +89,14 @@ uint8_t getLastPage();
void hardwareInit(GwApi *api);
void powerInit(String powermode);
void setPCF8574PortPin(uint pin, uint8_t value);// Set PCF8574 port pin
void setPCF8574PortPinModul1(uint8_t pin, uint8_t value);// Set PCF8574 port pin
void setPortPin(uint pin, bool value); // Set port pin for extension port
void togglePortPin(uint pin); // Toggle extension port pin
Color colorMapping(const String &colorString); // Color mapping string to CHSV colors
void setBacklightLED(uint brightness, const Color &color);// Set backlight LEDs
void toggleBacklightLED(uint brightness,const Color &color);// Toggle backlight LEDs
void stepsBacklightLED(uint brightness, const Color &color);// Set backlight LEDs in 4 steps (100%, 50%, 10%, 0%)
BacklightMode backlightMapping(const String &backlightString);// Configuration string to value
void setFlashLED(bool status); // Set flash LED
@@ -108,6 +109,7 @@ void setBuzzerPower(uint power); // Set buzzer power
String xdrDelete(String input); // Delete xdr prefix from string
void drawTextCenter(int16_t cx, int16_t cy, String text);
void drawButtonCenter(int16_t cx, int16_t cy, int8_t sx, int8_t sy, String text, uint16_t fg, uint16_t bg, bool inverted);
void drawTextRalign(int16_t x, int16_t y, String text);
void drawTextBoxed(Rect box, String text, uint16_t fg, uint16_t bg, bool inverted, bool border);

View File

@@ -92,6 +92,8 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
const char* fmt_dec_1;
const char* fmt_dec_10;
const char* fmt_dec_100;
double limit_dec_10;
double limit_dec_100;
if (precision == "1") {
//
//All values are displayed using a DSEG7* font. In this font, ' ' is a very short space, and '.' takes up no space at all.
@@ -100,10 +102,14 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
fmt_dec_1 = "!%1.1f"; //insert a blank digit and then display a two-digit number
fmt_dec_10 = "!%2.0f"; //insert a blank digit and then display a two-digit number
fmt_dec_100 = "%3.0f"; //dispay a three digit number
limit_dec_10=9.95; // use fmt_dec_1 below this number to avoid formatting 9.96 as 10.0 instead of 10
limit_dec_100=99.5;
} else {
fmt_dec_1 = "%3.2f";
fmt_dec_10 = "%3.1f";
fmt_dec_100 = "%3.0f";
limit_dec_10=9.995;
limit_dec_100=99.95;
}
// LOG_DEBUG(GwLog::DEBUG,"formatValue init: getFormat: %s date->value: %f time->value: %f", value->getFormat(), commondata.date->value, commondata.time->value);
@@ -243,10 +249,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
speed = speed; // Unit conversion form m/s to m/s
result.unit = "m/s";
}
if(speed < 10) {
if(speed < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, speed);
}
else if (speed < 100) {
else if (speed < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, speed);
}
else {
@@ -323,11 +329,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
snprintf(buffer, bsize, "%2.0f", speed);
}
else{
speed = std::round(speed * 100) / 100; // in rare cases, speed could be 9.9999 kn instead of 10.0 kn
if (speed < 10.0){
if (speed < limit_dec_10){
snprintf(buffer, bsize, fmt_dec_1, speed);
}
else if (speed < 100.0){
else if (speed < limit_dec_100){
snprintf(buffer, bsize, fmt_dec_10, speed);
}
else {
@@ -378,10 +383,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
if (dop > 99.9){
dop = 99.9;
}
if (dop < 10){
if (dop < limit_dec_10){
snprintf(buffer, bsize, fmt_dec_1, dop);
}
else if(dop < 100){
else if(dop < limit_dec_100){
snprintf(buffer, bsize, fmt_dec_10, dop);
}
else {
@@ -457,10 +462,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
else{
result.unit = "m";
}
if (depth < 10) {
if (depth < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, depth);
}
else if (depth < 100){
else if (depth < limit_dec_100){
snprintf(buffer, bsize, fmt_dec_10, depth);
}
else {
@@ -523,10 +528,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
else{
result.unit = "K";
}
if(temp < 10) {
if(temp < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, temp);
}
else if (temp < 100) {
else if (temp < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, temp);
}
else {
@@ -556,10 +561,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
else {
result.unit = "m";
}
if (distance < 10){
if (distance < limit_dec_10){
snprintf(buffer, bsize, fmt_dec_1, distance);
}
else if (distance < 100){
else if (distance < limit_dec_100){
snprintf(buffer, bsize, fmt_dec_10, distance);
}
else {
@@ -613,7 +618,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 12 + float(random(0, 30)) / 10.0;
voltage = rawvalue;
}
if (voltage < 10) {
if (voltage < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, voltage);
}
else {
@@ -633,10 +638,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 8.2 + float(random(0, 50)) / 10.0;
current = rawvalue;
}
if (current < 10) {
if (current < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, current);
}
else if(current < 100) {
else if(current < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, current);
}
else {
@@ -656,10 +661,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 21.8 + float(random(0, 50)) / 10.0;
temperature = rawvalue;
}
if (temperature < 10) {
if (temperature < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, temperature);
}
else if (temperature < 100) {
else if (temperature < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, temperature);
}
else {
@@ -679,10 +684,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 21.8 + float(random(0, 50)) / 10.0;
temperature = rawvalue;
}
if (temperature < 10) {
if (temperature < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, temperature);
}
else if(temperature < 100) {
else if(temperature < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, temperature);
}
else {
@@ -702,10 +707,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 41.3 + float(random(0, 50)) / 10.0;
humidity = rawvalue;
}
if (humidity < 10) {
if (humidity < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, humidity);
}
else if(humidity < 100) {
else if(humidity < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, humidity);
}
else {
@@ -725,13 +730,13 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 85.8 + float(random(0, 50)) / 10.0;
volume = rawvalue;
}
if (volume < 10) {
if (volume < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, volume);
}
else if (volume < 100) {
else if (volume < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, volume);
}
else if (volume >= 100) {
else if (volume >= limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_100, volume);
}
result.unit = "%";
@@ -748,10 +753,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 75.2 + float(random(0, 50)) / 10.0;
volume = rawvalue;
}
if (volume < 10) {
if (volume < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, volume);
}
else if (volume < 100) {
else if (volume < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, volume);
}
else {
@@ -771,10 +776,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 7.5 + float(random(0, 20)) / 10.0;
flow = rawvalue;
}
if (flow < 10) {
if (flow < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, flow);
}
else if (flow < 100) {
else if (flow < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, flow);
}
else {
@@ -794,10 +799,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 18.5 + float(random(0, 20)) / 10.0;
generic = rawvalue;
}
if (generic < 10) {
if (generic < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, generic);
}
else if (generic < 100) {
else if (generic < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, generic);
}
else {
@@ -817,10 +822,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 55.3 + float(random(0, 20)) / 10.0;
dplace = rawvalue;
}
if (dplace < 10) {
if (dplace < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, dplace);
}
else if (dplace < 100) {
else if (dplace < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, dplace);
}
else {
@@ -861,10 +866,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
rawvalue = 2505 + random(0, 20);
rpm = rawvalue;
}
if (rpm < 10) {
if (rpm < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, rpm);
}
else if (rpm < 100) {
else if (rpm < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, rpm);
}
else {
@@ -877,10 +882,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool
// Default format
//########################################################
else {
if (value->value < 10) {
if (value->value < limit_dec_10) {
snprintf(buffer, bsize, fmt_dec_1, value->value);
}
else if (value->value < 100) {
else if (value->value < limit_dec_100) {
snprintf(buffer, bsize, fmt_dec_10, value->value);
}
else {

View File

@@ -23,8 +23,8 @@
#define AS5600_I2C_ADDR 0x36 // Addr. 0x36 (fix)
// INA219
#define SHUNT_VOLTAGE 0.075 // Shunt voltage in V by max. current (75mV)
#define INA219_I2C_ADDR1 0x40 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
#define INA219_I2C_ADDR2 0x41 // Addr. 0x44 (fix A0 = GND, A1 = 5V) for solar panels
#define INA219_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
#define INA219_I2C_ADDR2 0x44 // Addr. 0x44 (fix A0 = GND, A1 = 5V) for solar panels
#define INA219_I2C_ADDR3 0x45 // Addr. 0x45 (fix A0 = 5V, A1 = 5V) for generator
// INA226
#define INA226_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
@@ -103,8 +103,8 @@
#define AS5600_I2C_ADDR 0x36 // Addr. 0x36 (fix)
// INA219
#define SHUNT_VOLTAGE 0.075 // Shunt voltage in V by max. current (75mV)
#define INA219_I2C_ADDR1 0x40 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
#define INA219_I2C_ADDR2 0x41 // Addr. 0x44 (fix A0 = GND, A1 = 5V) for solar panels
#define INA219_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery
#define INA219_I2C_ADDR2 0x44 // Addr. 0x44 (fix A0 = GND, A1 = 5V) for solar panels
#define INA219_I2C_ADDR3 0x45 // Addr. 0x45 (fix A0 = 5V, A1 = 5V) for generator
// INA226
#define INA226_I2C_ADDR1 0x41 // Addr. 0x41 (fix A0 = 5V, A1 = GND) for battery

View File

@@ -1,5 +1,221 @@
#include "OBPDataOperations.h"
#include "BoatDataCalibration.h" // Functions lib for data instance calibration
//#include "BoatDataCalibration.h" // Functions lib for data instance calibration
// --- Class CalibrationData ---------------
CalibrationData::CalibrationData(GwLog* log)
{
logger = log;
}
void CalibrationData::readConfig(GwConfigHandler* config)
// Initial load of calibration data into internal list
// This method is called once at init phase of <obp60task> to read the configuration values
{
std::string instance;
double offset;
double slope;
double smooth;
String calInstance = "";
String calOffset = "";
String calSlope = "";
String calSmooth = "";
// Load user format configuration values
String lengthFormat = config->getString(config->lengthFormat); // [m|ft]
String distanceFormat = config->getString(config->distanceFormat); // [m|km|nm]
String speedFormat = config->getString(config->speedFormat); // [m/s|km/h|kn]
String windspeedFormat = config->getString(config->windspeedFormat); // [m/s|km/h|kn|bft]
String tempFormat = config->getString(config->tempFormat); // [K|C|F]
// Read calibration settings for data instances
for (int i = 0; i < MAX_CALIBRATION_DATA; i++) {
calInstance = "calInstance" + String(i + 1);
calOffset = "calOffset" + String(i + 1);
calSlope = "calSlope" + String(i + 1);
calSmooth = "calSmooth" + String(i + 1);
instance = std::string(config->getString(calInstance, "---").c_str());
if (instance == "---") {
LOG_DEBUG(GwLog::LOG, "No calibration data for instance no. %d", i + 1);
continue;
}
calibrationMap[instance] = { 0.0f, 1.0f, 1.0f, 0.0f, false };
offset = (config->getString(calOffset, "")).toDouble();
slope = (config->getString(calSlope, "")).toDouble();
smooth = (config->getString(calSmooth, "")).toInt(); // user input is int; further math is done with double
if (slope == 0.0) {
slope = 1.0; // eliminate adjustment if user selected "0" -> that would set the calibrated value to "0"
}
// Convert calibration values from user input format to internal standard SI format
if (instance == "AWS" || instance == "TWS") {
if (windspeedFormat == "m/s") {
// No conversion needed
} else if (windspeedFormat == "km/h") {
offset /= 3.6; // Convert km/h to m/s
} else if (windspeedFormat == "kn") {
offset /= 1.94384; // Convert kn to m/s
} else if (windspeedFormat == "bft") {
offset *= 2 + (offset / 2); // Convert Bft to m/s (approx) -> to be improved
}
} else if (instance == "AWA" || instance == "COG" || instance == "HDM" || instance == "HDT" || instance == "PRPOS" || instance == "RPOS" || instance == "TWA" || instance == "TWD") {
offset *= DEG_TO_RAD; // Convert deg to rad
} else if (instance == "DBS" || instance == "DBT") {
if (lengthFormat == "m") {
// No conversion needed
} else if (lengthFormat == "ft") {
offset /= 3.28084; // Convert ft to m
}
} else if (instance == "SOG" || instance == "STW") {
if (speedFormat == "m/s") {
// No conversion needed
} else if (speedFormat == "km/h") {
offset /= 3.6; // Convert km/h to m/s
} else if (speedFormat == "kn") {
offset /= 1.94384; // Convert kn to m/s
}
} else if (instance == "WTemp") {
if (tempFormat == "K" || tempFormat == "C") {
// No conversion needed
} else if (tempFormat == "F") {
offset *= 9.0 / 5.0; // Convert °F to K
slope *= 9.0 / 5.0; // Convert °F to K
}
}
// transform smoothing factor from [0.01..10] to [0.3..0.95] and invert for exponential smoothing formula
if (smooth <= 0) {
smooth = 0;
} else {
if (smooth > 10) {
smooth = 10;
}
smooth = 0.3 + ((smooth - 0.01) * (0.95 - 0.3) / (10 - 0.01));
}
smooth = 1 - smooth;
calibrationMap[instance].offset = offset;
calibrationMap[instance].slope = slope;
calibrationMap[instance].smooth = smooth;
calibrationMap[instance].isCalibrated = false;
LOG_DEBUG(GwLog::LOG, "Calibration data type added: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(),
calibrationMap[instance].offset, calibrationMap[instance].slope, calibrationMap[instance].smooth);
}
// LOG_DEBUG(GwLog::LOG, "All calibration data read");
}
// Handle calibrationMap and calibrate all boat data values
void CalibrationData::handleCalibration(BoatValueList* boatValueList)
{
GwApi::BoatValue* bValue;
for (const auto& cMap : calibrationMap) {
std::string instance = cMap.first.c_str();
bValue = boatValueList->findValueOrCreate(String(instance.c_str()));
calibrateInstance(bValue);
smoothInstance(bValue);
}
}
// Calibrate single boat data value
// Return calibrated boat value or DBL_MAX, if no calibration was performed
bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue)
{
std::string instance = boatDataValue->getName().c_str();
double offset = 0;
double slope = 1.0;
double dataValue = 0;
std::string format = "";
// we test this earlier, but for safety reason ...
if (calibrationMap.find(instance) == calibrationMap.end()) {
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
return false;
}
calibrationMap[instance].isCalibrated = false; // reset calibration flag until properly calibrated
if (!boatDataValue->valid) { // no valid boat data value, so we don't want to apply calibration data
return false;
}
offset = calibrationMap[instance].offset;
slope = calibrationMap[instance].slope;
dataValue = boatDataValue->value;
format = boatDataValue->getFormat().c_str();
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: value: %f, format: %s", instance.c_str(), dataValue, format.c_str());
if (format == "formatWind") { // instance is of type angle
dataValue = (dataValue * slope) + offset;
// dataValue = WindUtils::toPI(dataValue);
dataValue = WindUtils::to2PI(dataValue); // we should call <toPI> for format of [-180..180], but pages cannot display negative values properly yet
} else if (format == "formatCourse") { // instance is of type direction
dataValue = (dataValue * slope) + offset;
dataValue = WindUtils::to2PI(dataValue);
} else if (format == "kelvinToC") { // instance is of type temperature
dataValue = ((dataValue - 273.15) * slope) + offset + 273.15;
} else {
dataValue = (dataValue * slope) + offset;
}
boatDataValue->value = dataValue; // update boat data value with calibrated value
calibrationMap[instance].value = dataValue; // store the calibrated value in the list
calibrationMap[instance].isCalibrated = true;
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: Offset: %f, Slope: %f, Result: %f", instance.c_str(), offset, slope, calibrationMap[instance].value);
return true;
}
// Smooth single boat data value
// Return smoothed boat value or DBL_MAX, if no smoothing was performed
bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue)
{
std::string instance = boatDataValue->getName().c_str();
double oldValue = 0;
double dataValue = boatDataValue->value;
double smoothFactor = 0;
// we test this earlier, but for safety reason ...
if (calibrationMap.find(instance) == calibrationMap.end()) {
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s not in calibration list", instance.c_str());
return false;
}
calibrationMap[instance].isCalibrated = false; // reset calibration flag until properly calibrated
if (!boatDataValue->valid) { // no valid boat data value, so we don't need to do anything
return false;
}
smoothFactor = calibrationMap[instance].smooth;
if (lastValue.find(instance) != lastValue.end()) {
oldValue = lastValue[instance];
dataValue = oldValue + (smoothFactor * (dataValue - oldValue)); // exponential smoothing algorithm
}
lastValue[instance] = dataValue; // store the new value for next cycle; first time, store only the current value and return
boatDataValue->value = dataValue; // update boat data value with smoothed value
calibrationMap[instance].value = dataValue; // store the smoothed value in the list
calibrationMap[instance].isCalibrated = true;
LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value);
return true;
}
// --- End Class CalibrationData ---------------
// --- Class HstryBuf ---------------
HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log)
@@ -43,12 +259,15 @@ void HstryBuf::handle(bool useSimuData, CommonData& common)
if (boatValue->valid) {
// Calibrate boat value before adding it to history buffer
calibrationData.calibrateInstance(tmpBVal.get(), logger);
add(tmpBVal->value);
//calibrationData.calibrateInstance(tmpBVal.get(), logger);
//add(tmpBVal->value);
add(boatValue->value);
} else if (useSimuData) { // add simulated value to history buffer
double simValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>
add(simValue);
double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at <formatValue>; here: retreive SI value
add(simSIValue);
} else {
// here we will add invalid (DBL_MAX) value; this will mark periods of missing data in buffer together with a timestamp
}
}
// --- End Class HstryBuf ---------------
@@ -299,7 +518,8 @@ bool WindUtils::addWinds()
twsBVal->valid = true;
}
if (!twaBVal->valid) {
twaBVal->value = twa;
//twaBVal->value = twa;
twaBVal->value = to2PI(twa); // convert to [0..360], because pages cannot display negative values properly yet
twaBVal->valid = true;
}
if (!awdBVal->valid) {

View File

@@ -1,9 +1,36 @@
// Function lib for history buffer handling, true wind calculation, and other operations on boat data
// Function lib for boat data calibration, history buffer handling, true wind calculation, and other operations on boat data
#pragma once
#include "OBPRingBuffer.h"
#include "Pagedata.h"
#include "obp60task.h"
#include <map>
#include <unordered_map>
// Calibration of boat data values, when user setting available
// supported boat data types are: AWA, AWS, COG, DBS, DBT, HDM, HDT, PRPOS, RPOS, SOG, STW, TWA, TWS, TWD, WTemp
class CalibrationData {
private:
typedef struct {
double offset; // calibration offset
double slope; // calibration slope
double smooth; // smoothing factor
double value; // calibrated data value (for future use)
bool isCalibrated; // is data instance value calibrated? (for future use)
} tCalibrationData;
std::unordered_map<std::string, tCalibrationData> calibrationMap; // list of calibration data instances
std::unordered_map<std::string, double> lastValue; // array for last smoothed values of boat data values
GwLog* logger;
static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances
public:
CalibrationData(GwLog* log);
void readConfig(GwConfigHandler* config);
void handleCalibration(BoatValueList* boatValues); // Handle calibrationMap and calibrate all boat data values
bool calibrateInstance(GwApi::BoatValue* boatDataValue); // Calibrate single boat data value
bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value
};
class HstryBuf {
private:

View File

@@ -0,0 +1,263 @@
#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3
#include "Pagedata.h"
#include "OBP60Extensions.h"
// These constants have to match the declaration below in :
// PageDescription registerPageAutopilot(
// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
const int HowManyValues = 9;
const int AverageValues = 4;
const int ShowHDM = 0;
const int ShowHDT = 1;
const int ShowCOG = 2;
const int ShowSTW = 3;
const int ShowSOG = 4;
const int ShowDBT = 5;
const int ShowXTE = 6;
const int ShowDTW = 7;
const int ShowBTW = 8;
const int Compass_X0 = 200; // X center point of compass band
const int Compass_Y0 = 220; // Y position of compass lines
const int Compass_LineLength = 22; // Length of compass lines
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
class PageAutopilot : public Page
{
int WhichDataCompass = ShowHDM; // Start value
int WhichDataDisplay = ShowHDM; // Start value
public:
PageAutopilot(CommonData &common){
commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageAutopilot");
}
virtual void setupKeys(){
Page::setupKeys();
commonData->keydata[0].label = "CMP";
commonData->keydata[1].label = "SRC";
}
virtual int handleKey(int key){
// Code for keylock
if ( key == 1 ) {
WhichDataCompass += 1;
if ( WhichDataCompass > ShowCOG)
WhichDataCompass = ShowHDM;
return 0;
}
if ( key == 2 ) {
WhichDataDisplay += 1;
if ( WhichDataDisplay > ShowDBT)
WhichDataDisplay = ShowHDM;
}
if(key == 11){
commonData->keylock = !commonData->keylock;
return 0; // Commit the key
}
return key;
}
int displayPage(PageData &pageData){
GwConfigHandler *config = commonData->config;
GwLog *logger = commonData->logger;
// Old values for hold function
static String OldDataText[HowManyValues] = {"", "", "","", "", "","", "", ""};
static String OldDataUnits[HowManyValues] = {"", "", "","", "", "","", "", ""};
// Get config data
String lengthformat = config->getString(config->lengthFormat);
// bool simulation = config->getBool(config->useSimuData);
bool holdvalues = config->getBool(config->holdvalues);
String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight);
GwApi::BoatValue *bvalue;
String DataName[HowManyValues];
double DataValue[HowManyValues];
bool DataValid[HowManyValues];
String DataText[HowManyValues];
String DataUnits[HowManyValues];
String DataFormat[HowManyValues];
FormattedData TheFormattedData;
for (int i = 0; i < HowManyValues; i++){
bvalue = pageData.values[i];
TheFormattedData = formatValue(bvalue, *commonData);
DataName[i] = xdrDelete(bvalue->getName());
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
DataUnits[i] = formatValue(bvalue, *commonData).unit;
DataText[i] = TheFormattedData.svalue; // Formatted value as string including unit conversion and switching decimal places
DataValue[i] = TheFormattedData.value; // Value as double in SI unit
DataValid[i] = bvalue->valid;
DataFormat[i] = bvalue->getFormat(); // Unit of value
LOG_DEBUG(GwLog::LOG,"Drawing at PageAutopilot: %d %s %f %s %s", i, DataName[i], DataValue[i], DataFormat[i], DataText[i] );
}
// Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){
setBlinkingLED(false);
setFlashLED(false);
}
if (bvalue == NULL) return PAGE_OK; // WTF why this statement?
//***********************************************************
// Set display in partial refresh mode
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
// Horizontal line 2 pix top & bottom
// Print data on top half
getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold20pt8b);
getdisplay().setCursor(10, 70);
getdisplay().print(DataName[WhichDataDisplay]); // Page name
// Show unit
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().setCursor(10, 120);
getdisplay().print(DataUnits[WhichDataDisplay]);
getdisplay().setCursor(190, 120);
getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b);
if(holdvalues == false){
getdisplay().print(DataText[WhichDataDisplay]); // Real value as formated string
}
else{
getdisplay().print(OldDataText[WhichDataDisplay]); // Old value as formated string
}
if(DataValid[WhichDataDisplay] == true){
OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value
OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit
}
// Now draw compass band
// Get the data
double TheAngle = DataValue[WhichDataCompass];
static double AvgAngle = 0;
AvgAngle = ( AvgAngle * AverageValues + TheAngle ) / (AverageValues + 1 );
int TheTrend = round( ( TheAngle - AvgAngle) * 180.0 / M_PI );
static const int bsize = 30;
char buffer[bsize+1];
buffer[0]=0;
getdisplay().setFont(&Ubuntu_Bold16pt8b);
getdisplay().setCursor(10, Compass_Y0-60);
getdisplay().print(DataName[WhichDataCompass]); // Page name
// Draw compass base line and pointer
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor);
// Draw trendlines
for ( int i = 1; i < abs(TheTrend) / 2; i++){
int x1;
if ( TheTrend < 0 )
x1 = Compass_X0 + 20 * i;
else
x1 = Compass_X0 - 20 * ( i + 1 );
getdisplay().fillRect(x1, Compass_Y0 -55, 10, 6, commonData->fgcolor);
}
// Central line + satellite lines
double NextSector = round(TheAngle / ( M_PI / 9 )) * ( M_PI / 9 ); // Get the next 20degree value
double Offset = - ( NextSector - TheAngle); // Offest of the center line compared to TheAngle in Radian
int Delta_X = int ( Offset * 180.0 / M_PI * Compass_LineDelta );
for ( int i = 0; i <=4; i++ ){
int x0;
x0 = Compass_X0 + Delta_X + 2 * i * 5 * Compass_LineDelta;
getdisplay().fillRect(x0-2, Compass_Y0 - 2 * Compass_LineLength, 5, 2 * Compass_LineLength, commonData->fgcolor);
x0 = Compass_X0 + Delta_X + ( 2 * i + 1 ) * 5 * Compass_LineDelta;
getdisplay().fillRect(x0-1, Compass_Y0 - Compass_LineLength, 3, Compass_LineLength, commonData->fgcolor);
x0 = Compass_X0 + Delta_X - 2 * i * 5 * Compass_LineDelta;
getdisplay().fillRect(x0-2, Compass_Y0 - 2 * Compass_LineLength, 5, 2 * Compass_LineLength, commonData->fgcolor);
x0 = Compass_X0 + Delta_X - ( 2 * i + 1 ) * 5 * Compass_LineDelta;
getdisplay().fillRect(x0-1, Compass_Y0 - Compass_LineLength, 3, Compass_LineLength, commonData->fgcolor);
}
getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor);
// Add the numbers to the compass band
int x0;
float AngleToDisplay = NextSector * 180.0 / M_PI;
x0 = Compass_X0 + Delta_X;
getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b);
do {
getdisplay().setCursor(x0 - 40, Compass_Y0 + 40);
snprintf(buffer,bsize,"%03.0f", AngleToDisplay);
getdisplay().print(buffer);
AngleToDisplay += 20;
if ( AngleToDisplay >= 360.0 )
AngleToDisplay -= 360.0;
x0 -= 4 * 5 * Compass_LineDelta;
} while ( x0 >= 0 - 60 );
AngleToDisplay = NextSector * 180.0 / M_PI - 20;
if ( AngleToDisplay < 0 )
AngleToDisplay += 360.0;
x0 = Compass_X0 + Delta_X + 4 * 5 * Compass_LineDelta;
do {
getdisplay().setCursor(x0 - 40, Compass_Y0 + 40);
snprintf(buffer,bsize,"%03.0f", AngleToDisplay);
// Quick and dirty way to prevent wrapping text in next line
if ( ( x0 - 40 ) > 380 )
buffer[0] = 0;
else if ( ( x0 - 40 ) > 355 )
buffer[1] = 0;
else if ( ( x0 - 40 ) > 325 )
buffer[2] = 0;
getdisplay().print(buffer);
AngleToDisplay -= 20;
if ( AngleToDisplay < 0 )
AngleToDisplay += 360.0;
x0 += 4 * 5 * Compass_LineDelta;
} while (x0 < ( 400 - 20 -40 ) );
// static int x_test = 320;
// x_test += 2;
// snprintf(buffer,bsize,"%03d", x_test);
// getdisplay().setCursor(x_test, Compass_Y0 - 60);
// getdisplay().print(buffer);
// if ( x_test > 390)
// x_test = 320;
return PAGE_UPDATE;
};
};
static Page *createPage(CommonData &common){
return new PageAutopilot(common);
}/**
* with the code below we make this page known to the PageTask
* we give it a type (name) that can be selected in the config
* we define which function is to be called
* and we provide the number of user parameters we expect
* this will be number of BoatValue pointers in pageData.values
*/
PageDescription registerPageAutopilot(
"Autopilot", // Page name
createPage, // Action
0, // Number of bus values depends on selection in Web configuration
{"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page
true // Show display header on/off
);
#endif

View File

@@ -17,10 +17,10 @@ const int ShowSTW = 3;
const int ShowSOG = 4;
const int ShowDBS = 5;
const int Compass_X0 = 200; // center point of compass band
const int Compass_Y0 = 220; // position of compass lines
const int Compass_LineLength = 22; // length of compass lines
const float Compass_LineDelta = 8.0;// compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
const int Compass_X0 = 200; // X center point of compass band
const int Compass_Y0 = 220; // Y position of compass lines
const int Compass_LineLength = 22; // Length of compass lines
const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels
class PageCompass : public Page
{

View File

@@ -22,12 +22,22 @@ bool button3 = false;
bool button4 = false;
bool button5 = false;
public:
public:
PageDigitalOut(CommonData &common){
commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageDigitalOut");
}
// Set botton labels
virtual void setupKeys(){
Page::setupKeys();
commonData->keydata[0].label = "1";
commonData->keydata[1].label = "2";
commonData->keydata[2].label = "3";
commonData->keydata[3].label = "4";
commonData->keydata[4].label = "5";
}
virtual int handleKey(int key){
// Code for keylock
if(key == 11){
@@ -37,31 +47,31 @@ public:
// Code for button 1
if(key == 1){
button1 = !button1;
setPCF8574PortPin(0, button1 ? 0 : 1); // Attention! Inverse logic for PCF8574
setPCF8574PortPinModul1(0, button1 ? 0 : 1); // Attention! Inverse logic for PCF8574
return 0; // Commit the key
}
// Code for button 2
if(key == 2){
button2 = !button2;
setPCF8574PortPin(1, button2 ? 0 : 1); // Attention! Inverse logic for PCF8574
setPCF8574PortPinModul1(1, button2 ? 0 : 1); // Attention! Inverse logic for PCF8574
return 0; // Commit the key
}
// Code for button 3
if(key == 3){
button3 = !button3;
setPCF8574PortPin(2, button3 ? 0 : 1); // Attention! Inverse logic for PCF8574
setPCF8574PortPinModul1(2, button3 ? 0 : 1); // Attention! Inverse logic for PCF8574
return 0; // Commit the key
}
// Code for button 4
if(key == 4){
button4 = !button4;
setPCF8574PortPin(3, button4 ? 0 : 1); // Attention! Inverse logic for PCF8574
setPCF8574PortPinModul1(3, button4 ? 0 : 1); // Attention! Inverse logic for PCF8574
return 0; // Commit the key
}
// Code for button 5
if(key == 5){
button5 = !button5;
setPCF8574PortPin(4, button5 ? 0 : 1); // Attention! Inverse logic for PCF8574
setPCF8574PortPinModul1(4, button5 ? 0 : 1); // Attention! Inverse logic for PCF8574
return 0; // Commit the key
}
return key;
@@ -77,6 +87,11 @@ public:
bool holdvalues = config->getBool(config->holdvalues);
String flashLED = config->getString(config->flashLED);
String backlightMode = config->getString(config->backlight);
String name1 = config->getString(config->mod1Out1);
String name2 = config->getString(config->mod1Out2);
String name3 = config->getString(config->mod1Out3);
String name4 = config->getString(config->mod1Out4);
String name5 = config->getString(config->mod1Out5);
// Optical warning by limit violation (unused)
if(String(flashLED) == "Limit Violation"){
@@ -94,17 +109,23 @@ public:
getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update
getdisplay().setTextColor(commonData->fgcolor);
getdisplay().setFont(&Ubuntu_Bold12pt8b);
getdisplay().fillRoundRect(200, 250 , 200, 25, 5, commonData->fgcolor); // Black rect
getdisplay().fillRoundRect(202, 252 , 196, 21, 5, commonData->bgcolor); // White rect
getdisplay().setCursor(210, 270);
getdisplay().print("Map server lost");
// Set botton labels
commonData->keydata[0].label = "BTN 1";
commonData->keydata[1].label = "BTN 2";
commonData->keydata[2].label = "BTN 3";
commonData->keydata[3].label = "BTN 4";
commonData->keydata[4].label = "BTN 5";
// Write text
getdisplay().setCursor(100, 50 + 8);
getdisplay().print(name1);
getdisplay().setCursor(100, 100 + 8);
getdisplay().print(name2);
getdisplay().setCursor(100, 150 + 8);
getdisplay().print(name3);
getdisplay().setCursor(100,200 + 8);
getdisplay().print(name4);
getdisplay().setCursor(100, 250 + 8);
getdisplay().print(name5);
// Draw bottons
drawButtonCenter(50, 50, 40, 27, "1", commonData->fgcolor, commonData->bgcolor, button1);
drawButtonCenter(50, 100, 40, 27, "2", commonData->fgcolor, commonData->bgcolor, button2);
drawButtonCenter(50, 150, 40, 27, "3", commonData->fgcolor, commonData->bgcolor, button3);
drawButtonCenter(50, 200, 40, 27, "4", commonData->fgcolor, commonData->bgcolor, button4);
drawButtonCenter(50, 250, 40, 27, "5", commonData->fgcolor, commonData->bgcolor, button5);
return PAGE_UPDATE;
};

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageFourValues : public Page
{
@@ -46,7 +45,6 @@ class PageFourValues : public Page
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -56,7 +54,6 @@ class PageFourValues : public Page
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -66,7 +63,6 @@ class PageFourValues : public Page
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -76,7 +72,6 @@ class PageFourValues : public Page
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
String name4 = xdrDelete(bvalue4->getName()); // Value name
name4 = name4.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageFourValues2 : public Page
{
@@ -46,7 +45,6 @@ class PageFourValues2 : public Page
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -56,7 +54,6 @@ class PageFourValues2 : public Page
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list (only one value by PageOneValue)
String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -66,7 +63,6 @@ class PageFourValues2 : public Page
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue)
String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -76,7 +72,6 @@ class PageFourValues2 : public Page
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue)
String name4 = xdrDelete(bvalue4->getName()); // Value name
name4 = name4.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -19,20 +19,28 @@ bool firstRun = true; // Detect the first page run
int zoom = 15; // Default zoom level
bool showValues = false; // Show values HDT, SOG, DBT in navigation map
private:
private:
uint8_t* imageBackupData = nullptr;
int imageBackupWidth = 0;
int imageBackupHeight = 0;
size_t imageBackupSize = 0;
bool hasImageBackup = false;
public:
public:
PageNavigation(CommonData &common){
commonData = &common;
common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation");
imageBackupData = (uint8_t*)heap_caps_malloc((GxEPD_WIDTH * GxEPD_HEIGHT), MALLOC_CAP_SPIRAM);
}
// Set botton labels
virtual void setupKeys(){
Page::setupKeys();
commonData->keydata[0].label = "ZOOM -";
commonData->keydata[1].label = "ZOOM +";
commonData->keydata[4].label = "VALUES";
}
virtual int handleKey(int key){
// Code for keylock
if(key == 11){
@@ -475,12 +483,7 @@ public:
getdisplay().setCursor(70, 85);
getdisplay().print(svalue6);
}
// Set botton labels
commonData->keydata[0].label = "ZOOM -";
commonData->keydata[1].label = "ZOOM +";
commonData->keydata[4].label = "VALUES";
return PAGE_UPDATE;
};
};

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
#include "OBPDataOperations.h"
#include "OBPcharts.h"
@@ -88,7 +87,6 @@ private:
String name1 = xdrDelete(bValue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bValue1, logger); // Check if boat data value is to be calibrated
double value1 = bValue1->value; // Value as double in SI unit
bool valid1 = bValue1->valid; // Valid information
String sValue1 = formatValue(bValue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageRudderPosition : public Page
{
@@ -41,7 +40,6 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list
String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
value1 = bvalue1->value; // Raw value without unit convertion
bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
const int SixValues_x1 = 5;
const int SixValues_DeltaX = 200;
@@ -57,7 +56,6 @@ class PageSixValues : public Page
bvalue = pageData.values[i];
DataName[i] = xdrDelete(bvalue->getName());
DataName[i] = DataName[i].substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated
DataValue[i] = bvalue->value; // Value as double in SI unit
DataValid[i] = bvalue->valid;
DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageThreeValues : public Page
{
@@ -44,7 +43,6 @@ class PageThreeValues : public Page
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -54,7 +52,6 @@ class PageThreeValues : public Page
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -64,7 +61,6 @@ class PageThreeValues : public Page
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
#include "OBPDataOperations.h"
#include "OBPcharts.h"
@@ -69,7 +68,6 @@ private:
int yOffset = YOFFSET * i;
String name = xdrDelete(bValue[i]->getName()); // Value name
name = name.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bValue[i], logger); // Check if boat data value is to be calibrated
double value = bValue[i]->value; // Value as double in SI unit
bool valid = bValue[i]->valid; // Valid information
String sValue = formatValue(bValue[i], *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -3,7 +3,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "N2kMessages.h"
#include "BoatDataCalibration.h"
#define front_width 120
#define front_height 162
@@ -324,7 +323,6 @@ public:
}
String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit
// bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -338,7 +336,6 @@ public:
}
String name2 = bvalue2->getName().c_str(); // Value name
name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit
// bool valid2 = bvalue2->valid; // Valid information
if (simulation) {

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageWindRose : public Page
{
@@ -52,7 +51,6 @@ public:
GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue)
String name1 = xdrDelete(bvalue1->getName()); // Value name
name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information
value1 = formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer
@@ -67,7 +65,6 @@ public:
GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list
String name2 = xdrDelete(bvalue2->getName()); // Value name
name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information
String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -81,7 +78,6 @@ public:
GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list
String name3 = xdrDelete(bvalue3->getName()); // Value name
name3 = name3.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -95,7 +91,6 @@ public:
GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list
String name4 = xdrDelete(bvalue4->getName()); // Value name
name4 = name4.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -109,7 +104,6 @@ public:
GwApi::BoatValue *bvalue5 = pageData.values[4]; // Fifth element in list
String name5 = xdrDelete(bvalue5->getName()); // Value name
name5 = name5.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
double value5 = bvalue5->value; // Value as double in SI unit
bool valid5 = bvalue5->valid; // Valid information
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -123,7 +117,6 @@ public:
GwApi::BoatValue *bvalue6 = pageData.values[5]; // Sixth element in list
String name6 = xdrDelete(bvalue6->getName()); // Value name
name6 = name6.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
double value6 = bvalue6->value; // Value as double in SI unit
bool valid6 = bvalue6->valid; // Valid information
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -2,7 +2,6 @@
#include "Pagedata.h"
#include "OBP60Extensions.h"
#include "BoatDataCalibration.h"
class PageWindRoseFlex : public Page
{
@@ -79,7 +78,6 @@ public:
}
String name1 = bvalue1->getName().c_str(); // Value name
name1 = name1.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated
double value1 = bvalue1->value; // Value as double in SI unit
bool valid1 = bvalue1->valid; // Valid information
String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -97,7 +95,6 @@ public:
}
String name2 = bvalue2->getName().c_str(); // Value name
name2 = name2.substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated
double value2 = bvalue2->value; // Value as double in SI unit
bool valid2 = bvalue2->valid; // Valid information
if (simulation) {
@@ -122,7 +119,6 @@ public:
else{
name3font=Ubuntu_Bold12pt8b;
}
calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated
double value3 = bvalue3->value; // Value as double in SI unit
bool valid3 = bvalue3->valid; // Valid information
String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -142,7 +138,6 @@ public:
else{
name4font=Ubuntu_Bold12pt8b;
}
calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated
double value4 = bvalue4->value; // Value as double in SI unit
bool valid4 = bvalue4->valid; // Valid information
String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -162,7 +157,6 @@ public:
else{
name5font=Ubuntu_Bold12pt8b;
}
calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated
double value5 = bvalue5->value; // Value as double in SI unit
bool valid5 = bvalue5->valid; // Valid information
String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
@@ -182,7 +176,6 @@ public:
else{
name6font=Ubuntu_Bold8pt8b;
}
calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated
double value6 = bvalue6->value; // Value as double in SI unit
bool valid6 = bvalue6->valid; // Valid information
String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places

View File

@@ -661,6 +661,61 @@
"obp60":"true"
}
},
{
"name": "mod1Out1",
"label": "Name1",
"type": "string",
"default": "text1",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp60":"true"
}
},
{
"name": "mod1Out2",
"label": "Name2",
"type": "string",
"default": "text2",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp60":"true"
}
},
{
"name": "mod1Out3",
"label": "Name3",
"type": "string",
"default": "text3",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp60":"true"
}
},
{
"name": "mod1Out4",
"label": "Name4",
"type": "string",
"default": "text4",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp60":"true"
}
},
{
"name": "mod1Out5",
"label": "Name5",
"type": "string",
"default": "text5",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp60":"true"
}
},
{
"name": "tSensitivity",
"label": "Touch Sensitivity [%]",
@@ -708,8 +763,10 @@
"AWA",
"AWS",
"COG",
"DBS",
"DBT",
"HDM",
"HDT",
"PRPOS",
"RPOS",
"SOG",
@@ -735,7 +792,7 @@
"obp60":"true"
},
"condition": [
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -743,13 +800,13 @@
"label": "Data Instance 1 Calibration Slope",
"type": "number",
"default": "1.00",
"description": "Slope for data instance 1",
"description": "Slope for data instance 1; Default: 1(!)",
"category": "OBP60 Calibrations",
"capabilities": {
"obp60":"true"
},
"condition": [
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -766,7 +823,7 @@
"obp60":"true"
},
"condition": [
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -780,8 +837,10 @@
"AWA",
"AWS",
"COG",
"DBS",
"DBT",
"HDM",
"HDT",
"PRPOS",
"RPOS",
"SOG",
@@ -807,7 +866,7 @@
"obp60":"true"
},
"condition": [
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -815,13 +874,13 @@
"label": "Data Instance 2 Calibration Slope",
"type": "number",
"default": "1.00",
"description": "Slope for data instance 2",
"description": "Slope for data instance 2; Default: 1(!)",
"category": "OBP60 Calibrations",
"capabilities": {
"obp60":"true"
},
"condition": [
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -838,7 +897,7 @@
"obp60":"true"
},
"condition": [
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -852,8 +911,10 @@
"AWA",
"AWS",
"COG",
"DBS",
"DBT",
"HDM",
"HDT",
"PRPOS",
"RPOS",
"SOG",
@@ -879,7 +940,7 @@
"obp60":"true"
},
"condition": [
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -887,13 +948,13 @@
"label": "Data Instance 3 Calibration Slope",
"type": "number",
"default": "1.00",
"description": "Slope for data instance 3",
"description": "Slope for data instance 3; Default: 1(!)",
"category": "OBP60 Calibrations",
"capabilities": {
"obp60":"true"
},
"condition": [
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -910,7 +971,81 @@
"obp60":"true"
},
"condition": [
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
"name": "calInstance4",
"label": "Calibration Data Instance 4",
"type": "list",
"default": "---",
"description": "Data instance for calibration",
"list": [
"---",
"AWA",
"AWS",
"COG",
"DBS",
"DBT",
"HDM",
"HDT",
"PRPOS",
"RPOS",
"SOG",
"STW",
"TWA",
"TWS",
"TWD",
"WTemp"
],
"category": "OBP60 Calibrations",
"capabilities": {
"obp60":"true"
}
},
{
"name": "calOffset4",
"label": "Data Instance 4 Calibration Offset",
"type": "number",
"default": "0.00",
"description": "Offset for data instance 4",
"category": "OBP60 Calibrations",
"capabilities": {
"obp60":"true"
},
"condition": [
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
"name": "calSlope4",
"label": "Data Instance 4 Calibration Slope",
"type": "number",
"default": "1.00",
"description": "Slope for data instance 3; Default: 1(!)",
"category": "OBP60 Calibrations",
"capabilities": {
"obp60":"true"
},
"condition": [
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
"name": "calSmooth4",
"label": "Data Instance 4 Smoothing",
"type": "number",
"default": "0",
"check": "checkMinMax",
"min": 0,
"max": 10,
"description": "Smoothing factor [0..10]; 0 = no smoothing",
"category": "OBP60 Calibrations",
"capabilities": {
"obp60":"true"
},
"condition": [
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -1218,9 +1353,9 @@
"type": "number",
"default": "50",
"check": "checkMinMax",
"min": 20,
"min": 5,
"max": 100,
"description": "Backlight brightness [20...100%]",
"description": "Backlight brightness [5...100%]",
"category": "OBP60 Display",
"capabilities": {
"obp60":"true"

View File

@@ -672,6 +672,61 @@
"obp40": "true"
}
},
{
"name": "mod1Out1",
"label": "Name1",
"type": "string",
"default": "text1",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "mod1Out2",
"label": "Name2",
"type": "string",
"default": "text2",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "mod1Out3",
"label": "Name3",
"type": "string",
"default": "text3",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "mod1Out4",
"label": "Name4",
"type": "string",
"default": "text4",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "mod1Out5",
"label": "Name5",
"type": "string",
"default": "text5",
"description": "Button name",
"category": "OBP60 IO-Modul1",
"capabilities": {
"obp40":"true"
}
},
{
"name": "tSensitivity",
"label": "Touch Sensitivity [%]",
@@ -719,8 +774,10 @@
"AWA",
"AWS",
"COG",
"DBS",
"DBT",
"HDM",
"HDT",
"PRPOS",
"RPOS",
"SOG",
@@ -746,7 +803,7 @@
"obp40":"true"
},
"condition": [
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -754,13 +811,13 @@
"label": "Data Instance 1 Calibration Slope",
"type": "number",
"default": "1.00",
"description": "Slope for data instance 1",
"description": "Slope for data instance 1, Default: 1(!)",
"category": "OBP40 Calibrations",
"capabilities": {
"obp40":"true"
},
"condition": [
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -777,7 +834,7 @@
"obp40":"true"
},
"condition": [
{ "calInstance1": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance1": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -791,8 +848,10 @@
"AWA",
"AWS",
"COG",
"DBS",
"DBT",
"HDM",
"HDT",
"PRPOS",
"RPOS",
"SOG",
@@ -818,7 +877,7 @@
"obp40":"true"
},
"condition": [
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -826,13 +885,13 @@
"label": "Data Instance 2 Calibration Slope",
"type": "number",
"default": "1.00",
"description": "Slope for data instance 2",
"description": "Slope for data instance 2; Default: 1(!)",
"category": "OBP40 Calibrations",
"capabilities": {
"obp40":"true"
},
"condition": [
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -849,7 +908,7 @@
"obp40":"true"
},
"condition": [
{ "calInstance2": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance2": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -863,8 +922,10 @@
"AWA",
"AWS",
"COG",
"DBS",
"DBT",
"HDM",
"HDT",
"PRPOS",
"RPOS",
"SOG",
@@ -890,7 +951,7 @@
"obp40":"true"
},
"condition": [
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -898,13 +959,13 @@
"label": "Data Instance 3 Calibration Slope",
"type": "number",
"default": "1.00",
"description": "Slope for data instance 3",
"description": "Slope for data instance 3, Default: 1(!)",
"category": "OBP40 Calibrations",
"capabilities": {
"obp40":"true"
},
"condition": [
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -921,7 +982,81 @@
"obp40":"true"
},
"condition": [
{ "calInstance3": ["AWA", "AWS", "COG", "DBT", "HDM", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
{ "calInstance3": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
"name": "calInstance4",
"label": "Calibration Data Instance 4",
"type": "list",
"default": "---",
"description": "Data instance for calibration",
"list": [
"---",
"AWA",
"AWS",
"COG",
"DBS",
"DBT",
"HDM",
"HDT",
"PRPOS",
"RPOS",
"SOG",
"STW",
"TWA",
"TWS",
"TWD",
"WTemp"
],
"category": "OBP40 Calibrations",
"capabilities": {
"obp40": "true"
}
},
{
"name": "calOffset4",
"label": "Data Instance 4 Calibration Offset",
"type": "number",
"default": "0.00",
"description": "Offset for data instance 4",
"category": "OBP40 Calibrations",
"capabilities": {
"obp40":"true"
},
"condition": [
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
"name": "calSlope4",
"label": "Data Instance 4 Calibration Slope",
"type": "number",
"default": "1.00",
"description": "Slope for data instance 4, Default: 1(!)",
"category": "OBP40 Calibrations",
"capabilities": {
"obp40":"true"
},
"condition": [
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
"name": "calSmooth4",
"label": "Data Instance 4 Smoothing",
"type": "number",
"default": "0",
"check": "checkMinMax",
"min": 0,
"max": 10,
"description": "Smoothing factor [0..10]; 0 = no smoothing",
"category": "OBP40 Calibrations",
"capabilities": {
"obp40":"true"
},
"condition": [
{ "calInstance4": ["AWA", "AWS", "COG", "DBS", "DBT", "HDM", "HDT", "PRPOS", "RPOS", "SOG", "STW", "TWA", "TWS", "TWD", "WTemp" ] }
]
},
{
@@ -1230,9 +1365,9 @@
"type": "number",
"default": "50",
"check": "checkMinMax",
"min": 20,
"min": 5,
"max": 100,
"description": "Backlight brightness [20...100%]",
"description": "Backlight brightness [5...100%]",
"category": "OBP40 Display",
"capabilities": {
"obp40": "false"

View File

@@ -12,7 +12,6 @@
#include <GxEPD2_BW.h> // GxEPD2 lib for b/w E-Ink displays
#include "OBP60Extensions.h" // Functions lib for extension board
#include "OBP60Keypad.h" // Functions for keypad
#include "BoatDataCalibration.h" // Functions lib for data instance calibration
#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation
#ifdef BOARD_OBP40S3
@@ -147,7 +146,6 @@ void keyboardTask(void *param){
vTaskDelete(NULL);
}
// Scorgan: moved class declaration to header file <obp60task.h> to make class available to other functions
// --- Class BoatValueList --------------
bool BoatValueList::addValueToList(GwApi::BoatValue *v){
for (int i=0;i<numValues;i++){
@@ -172,7 +170,7 @@ GwApi::BoatValue *BoatValueList::findValueOrCreate(String name){
addValueToList(rt);
return rt;
}
// --- Class BoatValueList --------------
// --- End Class BoatValueList --------------
//we want to have a list that has all our page definitions
//this way each page can easily be added here
@@ -264,6 +262,8 @@ void registerAllPages(PageList &list){
list.add(&registerPageNavigation);
extern PageDescription registerPageDigitalOut;
list.add(&registerPageDigitalOut);
extern PageDescription registerPageAutopilot;
list.add(&registerPageAutopilot);
}
// Undervoltage detection for shutdown display
@@ -435,10 +435,9 @@ void OBP60Task(GwApi *api){
int lastPage=-1; // initialize with an impiossible value, so we can detect wether we are during startup and no page has been displayed yet
BoatValueList boatValues; //all the boat values for the api query
HstryBuffers hstryBufList(1920, &boatValues, logger); // Create empty list of boat data history buffers
HstryBuffers hstryBufferList(1920, &boatValues, logger); // Create empty list of boat data history buffers (1.920 values = seconds = 32 min.)
WindUtils trueWind(&boatValues, logger); // Create helper object for true wind calculation
//commonData.distanceformat=config->getString(xxx);
//add all necessary data to common data
CalibrationData calibrationDataList(logger); // all boat data types which are supposed to be calibrated
//fill the page data from config
numPages=config->getInt(config->visiblePages,1);
@@ -480,26 +479,25 @@ void OBP60Task(GwApi *api){
pages[i].parameters.values.push_back(value);
}
// Read the specified boat data type of relevant pages and create a history buffer for each type
// Read the specified boat data types of relevant pages and create a history buffer for each type
if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") {
for (auto pVal : pages[i].parameters.values) {
hstryBufList.addBuffer(pVal->getName());
hstryBufferList.addBuffer(pVal->getName());
}
}
// Add list of history buffers to page parameters
pages[i].parameters.hstryBuffers = &hstryBufList;
pages[i].parameters.hstryBuffers = &hstryBufferList;
}
// add out of band system page (always available)
Page *syspage = allPages.pages[0]->creator(commonData);
// Check user settings for true wind calculation
// Read user settings from config file
bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false);
bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false);
// Read all calibration data settings from config
calibrationData.readConfig(config, logger);
// Read user calibration data settings from config file
calibrationDataList.readConfig(config);
// Display screenshot handler for HTTP request
// http://192.168.15.1/api/user/OBP60Task/screenshot
@@ -531,7 +529,7 @@ void OBP60Task(GwApi *api){
commonData.backlight.mode = backlightMapping(config->getConfigItem(config->backlight,true)->asString());
commonData.backlight.color = colorMapping(config->getConfigItem(config->blColor,true)->asString());
commonData.backlight.brightness = 2.55 * uint(config->getConfigItem(config->blBrightness,true)->asInt());
commonData.backlight.brightness = uint(config->getConfigItem(config->blBrightness,true)->asInt());
commonData.powermode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
bool uvoltage = config->getConfigItem(config->underVoltage, true)->asBoolean();
@@ -658,7 +656,7 @@ void OBP60Task(GwApi *api){
// if(String(backlight) == "Control by Key"){
if(keyboardMessage == 6){
LOG_DEBUG(GwLog::LOG,"Toggle Backlight LED");
toggleBacklightLED(commonData.backlight.brightness, commonData.backlight.color);
stepsBacklightLED(commonData.backlight.brightness, commonData.backlight.color);
}
}
#ifdef BOARD_OBP40S3
@@ -814,10 +812,10 @@ void OBP60Task(GwApi *api){
api->getStatus(commonData.status);
if (calcTrueWnds) {
trueWind.addWinds();
trueWind.addWinds(); // calculate true wind data from apparent wind values
}
// Handle history buffers for certain boat data for windplot page and other usage
hstryBufList.handleHstryBufs(useSimuData, commonData);
calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in <calibrationDataList>
hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for windplot page and other usage
// Clear display
// getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);

View File

@@ -3,7 +3,7 @@
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.
version 2 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

View File

@@ -3,7 +3,7 @@
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.
version 2 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
@@ -93,6 +93,7 @@ class GwSensorConfig{
}
bool readConfig(T* s,GwConfigHandler *cfg){
if (s == nullptr) return false;
if (prefix != s->prefix) return false;
configReader(s,cfg);
return s->ok;
}

View File

@@ -66,18 +66,8 @@ GwSerial::~GwSerial()
if (lock != nullptr) vSemaphoreDelete(lock);
}
String GwSerial::getMode(){
switch (type){
case GWSERIAL_TYPE_UNI:
return "UNI";
case GWSERIAL_TYPE_BI:
return "BI";
case GWSERIAL_TYPE_RX:
return "RX";
case GWSERIAL_TYPE_TX:
return "TX";
}
return "UNKNOWN";
int GwSerial::getType() {
return type;
}
bool GwSerial::isInitialized() { return initialized; }

View File

@@ -42,7 +42,7 @@ class GwSerial : public GwChannelInterface{
virtual Stream *getStream(bool partialWrites);
bool getAvailableWrite(){return availableWrite;}
virtual void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1)=0;
virtual String getMode() override;
virtual int getType() override;
friend GwSerialStream;
};
@@ -122,6 +122,7 @@ template<typename T>
setError(serial,logger);
};
};

View File

@@ -185,4 +185,4 @@ int GwSocketServer::numClients()
}
GwSocketServer::~GwSocketServer()
{
}
}

View File

@@ -291,4 +291,4 @@ void GwTcpClient::setResolved(IPAddress addr, bool valid){
GwTcpClient::ResolvedAddress GwTcpClient::getResolved(){
GWSYNCHRONIZED(locker);
return resolvedAddress;
}
}

View File

@@ -6,7 +6,6 @@
#include "GwSocketHelper.h"
#include "GWWifi.h"
GwUdpReader::GwUdpReader(const GwConfigHandler *config, GwLog *logger, int minId)
{
this->config = config;

View File

@@ -200,4 +200,4 @@ size_t GwUdpWriter::sendToClients(const char *buf, int source,bool partial)
GwUdpWriter::~GwUdpWriter()
{
}
}

View File

@@ -3,7 +3,7 @@
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.
version 2 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

View File

@@ -3,7 +3,7 @@
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.
version 2 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

View File

@@ -3,7 +3,7 @@
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.
version 2 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

View File

@@ -3,7 +3,7 @@
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.
version 2 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

View File

@@ -3,7 +3,7 @@
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.
version 2 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

View File

@@ -431,7 +431,8 @@ GwXDRFoundMapping GwXDRMappings::getMapping(String xName,String xType,String xUn
}
return selectMapping(&(it->second),instance,n183Key.c_str());
}
GwXDRFoundMapping GwXDRMappings::getMapping(GwXDRCategory category,int selector,int field,int instance){
GwXDRFoundMapping GwXDRMappings::getMapping(double value,GwXDRCategory category,int selector,int field,int instance){
if (value == N2kDoubleNA) return GwXDRFoundMapping(); //do not add to unknown mappings
unsigned long n2kKey=GwXDRMappingDef::n2kKey(category,selector,field);
auto it=n2kMap.find(n2kKey);
if (it == n2kMap.end()){

View File

@@ -244,7 +244,7 @@ class GwXDRMappings{
//get the mappings
//the returned mapping will exactly contain one mapping def
GwXDRFoundMapping getMapping(String xName,String xType,String xUnit);
GwXDRFoundMapping getMapping(GwXDRCategory category,int selector,int field=0,int instance=-1);
GwXDRFoundMapping getMapping(double value,GwXDRCategory category,int selector,int field=0,int instance=-1);
String getXdrEntry(String mapping, double value,int instance=0);
const char * getUnMapped();
const GwXDRType * findType(const String &typeString, const String &unitString) const;

View File

@@ -18,7 +18,7 @@ extra_configs=
[basedeps]
lib_deps =
ttlappalainen/NMEA2000-library @ 4.22.0
ttlappalainen_NMEA2000=https://github.com/wellenvogel/NMEA2000.git#20251126
ttlappalainen/NMEA0183 @ 1.10.1
ArduinoJson @ 6.18.5
AsyncTCP-esphome @ 2.0.1
@@ -29,6 +29,18 @@ lib_deps =
WiFi
Update
[devdeps]
lib_deps=
ttlappalainen_NMEA2000=symlink://../NMEA2000
ttlappalainen/NMEA0183 @ 1.10.1
ArduinoJson @ 6.18.5
AsyncTCP-esphome @ 2.0.1
ottowinter/ESPAsyncWebServer-esphome@2.0.1
FS
Preferences
ESPmDNS
WiFi
Update
[env]
platform = espressif32 @ 6.8.1
framework = arduino
@@ -67,6 +79,17 @@ lib_deps =
adafruit/Adafruit BusIO @ 1.14.5
adafruit/Adafruit Unified Sensor @ 1.1.13
[env:m5stack-atom-dev]
board = m5stack-atom
lib_deps =
${devdeps.lib_deps}
fastled/FastLED @ 3.6.0
build_flags =
-D BOARD_M5ATOM
${env.build_flags}
upload_port = /dev/esp32
upload_protocol = esptool
[env:m5stack-atom]
board = m5stack-atom
lib_deps = ${env.lib_deps}
@@ -185,3 +208,14 @@ build_flags =
${env.build_flags}
upload_port = /dev/esp32
upload_protocol = esptool
[env:s3devkitm-generic]
extends = sensors
board = esp32-s3-devkitm-1
lib_deps =
${env.lib_deps}
${sensors.lib_deps}
build_flags =
${env.build_flags}
upload_port = /dev/esp32
upload_protocol = esptool

View File

@@ -2,6 +2,7 @@ Import("env", "projenv")
import os
import glob
import shutil
import re
print("##post script running")
HDROFFSET=288
@@ -39,6 +40,7 @@ def post(source,target,env):
appoffset=env.subst("$ESP32_APP_OFFSET")
firmware=env.subst("$BUILD_DIR/${PROGNAME}.bin")
(fwname,version)=getFirmwareInfo(firmware)
fwname=re.sub(r"[^0-9A-Za-z_.-]*","",fwname)
print("found fwname=%s, fwversion=%s"%(fwname,version))
python=env.subst("$PYTHONEXE")
print("base=%s,esptool=%s,appoffset=%s,uploaderflags=%s"%(base,esptool,appoffset,uploaderflags))
@@ -70,10 +72,12 @@ def post(source,target,env):
print("running %s"%" ".join(cmd))
env.Execute(" ".join(cmd),"#testpost")
ofversion="-"+version
versionedFile=os.path.join(outdir,"%s%s-update.bin"%(base,ofversion))
versionedFile=os.path.join(outdir,"%s%s-update.bin"%(fwname,ofversion))
shutil.copyfile(firmware,versionedFile)
versioneOutFile=os.path.join(outdir,"%s%s-all.bin"%(base,ofversion))
print(f"wrote {versionedFile}")
versioneOutFile=os.path.join(outdir,"%s%s-all.bin"%(fwname,ofversion))
shutil.copyfile(outfile,versioneOutFile)
print(f"wrote {versioneOutFile}")
env.AddPostAction(
"$BUILD_DIR/${PROGNAME}.bin",
post

View File

@@ -3,7 +3,7 @@
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.
version 2 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
@@ -72,9 +72,9 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
#define MAX_NMEA2000_MESSAGE_SEASMART_SIZE 500
#define MAX_NMEA0183_MESSAGE_SIZE MAX_NMEA2000_MESSAGE_SEASMART_SIZE
//assert length of firmware name and version
CASSERT(strlen(FIRMWARE_TYPE) <= 32, "environment name (FIRMWARE_TYPE) must not exceed 32 chars");
CASSERT(strlen(VERSION) <= 32, "VERSION must not exceed 32 chars");
CASSERT(strlen(IDF_VERSION) <= 32,"IDF_VERSION must not exceed 32 chars");
CASSERT(strlen(FIRMWARE_TYPE) <= 31, "environment name (FIRMWARE_TYPE) must not exceed 32 chars");
CASSERT(strlen(VERSION) <= 31, "VERSION must not exceed 32 chars");
CASSERT(strlen(IDF_VERSION) <= 31,"IDF_VERSION must not exceed 32 chars");
//https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/app_image_format.html
//and removed the bugs in the doc...
__attribute__((section(".rodata_custom_desc"))) esp_app_desc_t custom_app_desc = {

32
tools/gen3byte.py Executable file
View File

@@ -0,0 +1,32 @@
#! /usr/bin/env python3
#generate 3 byte codes for the RGB bytes
#refer to https://controllerstech.com/ws2812-leds-using-spi/
ONE_BIT='110'
ZERO_BIT='100'
currentStr=''
def checkAndPrint(curr):
if len(curr) >= 8:
print("0b%s,"%curr[0:8],end='')
return curr[8:]
return curr
first=True
print("uint8_t colorTo3Byte[256][3]=")
print("{")
for i in range(0,256):
if not first:
print("},")
first=False
print("{/*%02d*/"%i,end='')
mask=0x80
for b in range(0,8):
if (i & mask) != 0:
currentStr+=ONE_BIT
else:
currentStr+=ZERO_BIT
mask=mask >> 1
currentStr=checkAndPrint(currentStr)
print("}")
print("};")

29
tools/getPgnType.py Executable file
View File

@@ -0,0 +1,29 @@
#! /usr/bin/env python3
import sys
import json
def err(txt):
print(txt,file=sys.stderr)
sys.exit(1)
HDR='''
PGNM_Fast=0
PGNM_Single=1
PGNM_ISO=2
PGN_MODES={
'''
FOOTER='''
}
'''
with open(sys.argv[1],"r") as ih:
data=json.load(ih)
pgns=data.get('PGNs')
if pgns is None:
err("no pgns")
print(HDR)
for p in pgns:
t=p['Type']
pgn=p['PGN']
if t and pgn:
print(f" {pgn}: PGNM_{t},")
print(FOOTER)

19
tools/sendDelay.py Executable file
View File

@@ -0,0 +1,19 @@
#! /usr/bin/env python3
import sys
import os
import time
def usage():
print(f"usage: {sys.argv[0]} file delay")
sys.exit(1)
if len(sys.argv) < 3:
usage()
delay=float(sys.argv[2])
fn=sys.argv[1]
with open (fn,"r") as fh:
for line in fh:
print(line,end="",flush=True)
time.sleep(delay)

793
tools/sendN2K.py Executable file
View File

@@ -0,0 +1,793 @@
#! /usr/bin/env python3
import re
import sys
import os
import datetime
import getopt
import time
###generated with getPgnType.py from canboat pgns.json
PGNM_Fast=0
PGNM_Single=1
PGNM_ISO=2
PGN_MODES={
59392: PGNM_Single,
59904: PGNM_Single,
60160: PGNM_Single,
60416: PGNM_Single,
60416: PGNM_Single,
60416: PGNM_Single,
60416: PGNM_Single,
60416: PGNM_Single,
60928: PGNM_Single,
61184: PGNM_Single,
61184: PGNM_Single,
61184: PGNM_Single,
65001: PGNM_Single,
65002: PGNM_Single,
65003: PGNM_Single,
65004: PGNM_Single,
65005: PGNM_Single,
65006: PGNM_Single,
65007: PGNM_Single,
65008: PGNM_Single,
65009: PGNM_Single,
65010: PGNM_Single,
65011: PGNM_Single,
65012: PGNM_Single,
65013: PGNM_Single,
65014: PGNM_Single,
65015: PGNM_Single,
65016: PGNM_Single,
65017: PGNM_Single,
65018: PGNM_Single,
65019: PGNM_Single,
65020: PGNM_Single,
65021: PGNM_Single,
65022: PGNM_Single,
65023: PGNM_Single,
65024: PGNM_Single,
65025: PGNM_Single,
65026: PGNM_Single,
65027: PGNM_Single,
65028: PGNM_Single,
65029: PGNM_Single,
65030: PGNM_Single,
65240: PGNM_ISO,
65280: PGNM_Single,
65284: PGNM_Single,
65285: PGNM_Single,
65285: PGNM_Single,
65286: PGNM_Single,
65286: PGNM_Single,
65287: PGNM_Single,
65287: PGNM_Single,
65288: PGNM_Single,
65289: PGNM_Single,
65290: PGNM_Single,
65292: PGNM_Single,
65293: PGNM_Single,
65293: PGNM_Single,
65302: PGNM_Single,
65305: PGNM_Single,
65305: PGNM_Single,
65305: PGNM_Single,
65305: PGNM_Single,
65305: PGNM_Single,
65309: PGNM_Single,
65312: PGNM_Single,
65340: PGNM_Single,
65341: PGNM_Single,
65345: PGNM_Single,
65350: PGNM_Single,
65359: PGNM_Single,
65360: PGNM_Single,
65361: PGNM_Single,
65371: PGNM_Single,
65374: PGNM_Single,
65379: PGNM_Single,
65408: PGNM_Single,
65409: PGNM_Single,
65410: PGNM_Single,
65420: PGNM_Single,
65480: PGNM_Single,
126208: PGNM_Fast,
126208: PGNM_Fast,
126208: PGNM_Fast,
126208: PGNM_Fast,
126208: PGNM_Fast,
126208: PGNM_Fast,
126208: PGNM_Fast,
126464: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126720: PGNM_Fast,
126983: PGNM_Fast,
126984: PGNM_Fast,
126985: PGNM_Fast,
126986: PGNM_Fast,
126987: PGNM_Fast,
126988: PGNM_Fast,
126992: PGNM_Single,
126993: PGNM_Single,
126996: PGNM_Fast,
126998: PGNM_Fast,
127233: PGNM_Fast,
127237: PGNM_Fast,
127245: PGNM_Single,
127250: PGNM_Single,
127251: PGNM_Single,
127252: PGNM_Single,
127257: PGNM_Single,
127258: PGNM_Single,
127488: PGNM_Single,
127489: PGNM_Fast,
127490: PGNM_Fast,
127491: PGNM_Fast,
127493: PGNM_Single,
127494: PGNM_Fast,
127495: PGNM_Fast,
127496: PGNM_Fast,
127497: PGNM_Fast,
127498: PGNM_Fast,
127500: PGNM_Single,
127501: PGNM_Single,
127502: PGNM_Single,
127503: PGNM_Fast,
127504: PGNM_Fast,
127505: PGNM_Single,
127506: PGNM_Fast,
127507: PGNM_Fast,
127508: PGNM_Single,
127509: PGNM_Fast,
127510: PGNM_Fast,
127511: PGNM_Single,
127512: PGNM_Single,
127513: PGNM_Fast,
127514: PGNM_Single,
127744: PGNM_Single,
127745: PGNM_Single,
127746: PGNM_Single,
127750: PGNM_Single,
127751: PGNM_Single,
128000: PGNM_Single,
128001: PGNM_Single,
128002: PGNM_Single,
128003: PGNM_Single,
128006: PGNM_Single,
128007: PGNM_Single,
128008: PGNM_Single,
128259: PGNM_Single,
128267: PGNM_Single,
128275: PGNM_Fast,
128520: PGNM_Fast,
128538: PGNM_Fast,
128768: PGNM_Single,
128769: PGNM_Single,
128776: PGNM_Single,
128777: PGNM_Single,
128778: PGNM_Single,
128780: PGNM_Single,
129025: PGNM_Single,
129026: PGNM_Single,
129027: PGNM_Single,
129028: PGNM_Single,
129029: PGNM_Fast,
129033: PGNM_Single,
129038: PGNM_Fast,
129039: PGNM_Fast,
129040: PGNM_Fast,
129041: PGNM_Fast,
129044: PGNM_Fast,
129045: PGNM_Fast,
129283: PGNM_Single,
129284: PGNM_Fast,
129285: PGNM_Fast,
129291: PGNM_Single,
129301: PGNM_Fast,
129302: PGNM_Fast,
129538: PGNM_Fast,
129539: PGNM_Single,
129540: PGNM_Fast,
129541: PGNM_Fast,
129542: PGNM_Fast,
129545: PGNM_Fast,
129546: PGNM_Single,
129547: PGNM_Fast,
129549: PGNM_Fast,
129550: PGNM_Fast,
129551: PGNM_Fast,
129556: PGNM_Fast,
129792: PGNM_Fast,
129793: PGNM_Fast,
129794: PGNM_Fast,
129795: PGNM_Fast,
129796: PGNM_Fast,
129797: PGNM_Fast,
129798: PGNM_Fast,
129799: PGNM_Fast,
129800: PGNM_Fast,
129801: PGNM_Fast,
129802: PGNM_Fast,
129803: PGNM_Fast,
129804: PGNM_Fast,
129805: PGNM_Fast,
129806: PGNM_Fast,
129807: PGNM_Fast,
129808: PGNM_Fast,
129808: PGNM_Fast,
129809: PGNM_Fast,
129810: PGNM_Fast,
130052: PGNM_Fast,
130053: PGNM_Fast,
130054: PGNM_Fast,
130060: PGNM_Fast,
130061: PGNM_Fast,
130064: PGNM_Fast,
130065: PGNM_Fast,
130066: PGNM_Fast,
130067: PGNM_Fast,
130068: PGNM_Fast,
130069: PGNM_Fast,
130070: PGNM_Fast,
130071: PGNM_Fast,
130072: PGNM_Fast,
130073: PGNM_Fast,
130074: PGNM_Fast,
130306: PGNM_Single,
130310: PGNM_Single,
130311: PGNM_Single,
130312: PGNM_Single,
130313: PGNM_Single,
130314: PGNM_Single,
130315: PGNM_Single,
130316: PGNM_Single,
130320: PGNM_Fast,
130321: PGNM_Fast,
130322: PGNM_Fast,
130323: PGNM_Fast,
130324: PGNM_Fast,
130330: PGNM_Fast,
130560: PGNM_Single,
130561: PGNM_Fast,
130562: PGNM_Fast,
130563: PGNM_Fast,
130564: PGNM_Fast,
130565: PGNM_Fast,
130566: PGNM_Fast,
130567: PGNM_Fast,
130569: PGNM_Fast,
130570: PGNM_Fast,
130571: PGNM_Fast,
130572: PGNM_Fast,
130573: PGNM_Fast,
130574: PGNM_Fast,
130576: PGNM_Single,
130577: PGNM_Fast,
130578: PGNM_Fast,
130579: PGNM_Single,
130580: PGNM_Fast,
130581: PGNM_Fast,
130582: PGNM_Single,
130583: PGNM_Fast,
130584: PGNM_Fast,
130585: PGNM_Single,
130586: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130816: PGNM_Fast,
130817: PGNM_Fast,
130817: PGNM_Fast,
130818: PGNM_Fast,
130819: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130820: PGNM_Fast,
130821: PGNM_Fast,
130821: PGNM_Fast,
130822: PGNM_Fast,
130823: PGNM_Fast,
130824: PGNM_Fast,
130824: PGNM_Fast,
130825: PGNM_Fast,
130827: PGNM_Fast,
130828: PGNM_Fast,
130831: PGNM_Fast,
130832: PGNM_Fast,
130833: PGNM_Fast,
130834: PGNM_Fast,
130835: PGNM_Fast,
130836: PGNM_Fast,
130836: PGNM_Fast,
130837: PGNM_Fast,
130837: PGNM_Fast,
130838: PGNM_Fast,
130839: PGNM_Fast,
130840: PGNM_Fast,
130842: PGNM_Fast,
130842: PGNM_Fast,
130842: PGNM_Fast,
130843: PGNM_Fast,
130843: PGNM_Fast,
130845: PGNM_Fast,
130845: PGNM_Fast,
130846: PGNM_Fast,
130846: PGNM_Fast,
130847: PGNM_Fast,
130850: PGNM_Fast,
130850: PGNM_Fast,
130850: PGNM_Fast,
130851: PGNM_Fast,
130856: PGNM_Fast,
130860: PGNM_Fast,
130880: PGNM_Fast,
130881: PGNM_Fast,
130944: PGNM_Fast,
}
def logError(fmt,*args,keep=False):
print("ERROR:" +fmt%(args),file=sys.stderr)
if not keep:
sys.exit(1)
def dataToSep(data,maxbytes=None):
pd=None
dl=int(len(data)/2)
if maxbytes is not None and maxbytes < dl:
dl=maxbytes
for p in range(0,dl):
i=2*p
if pd is None:
pd=data[i:i+2]
else:
pd+=","+data[i:i+2]
return pd
class Format:
F_PLAIN=0
N_PLAIN='plain'
F_ACT=1
N_ACT='actisense'
F_PASS=2
N_PASS='pass'
F_SEASMART=3
N_SEASMART="seasmart"
def __init__(self,name,key,merge=True):
self.key=key
self.name=name
self.merge=merge
class CanFrame:
DUMP_PAT=re.compile(r'\(([^)]*)\) *([^ ]*) *([^#]*)#(.*)')
def __init__(self,ts,pgn,src=1,dst=255,prio=1,dev=None,hdr=None,data=None):
self.pgn=pgn
self.mode=PGN_MODES.get(pgn)
self.ts=ts
self.src=src
self.dst=dst
self.data=data
self.prio=prio
self.sequence=None
self.frame=None
self.len=8
self.dev=dev
self.hdr=hdr
if self.mode == PGNM_Fast and data is not None and len(self.data) >= 2:
fb=int(data[0:2],16)
self.frame=fb & 0x1f
self.sequence=fb >> 5
def key(self):
if self.sequence is None or self.pgn == 0:
return None
return f"{self.pgn}-{self.sequence}-{self.src}"
def getFPNum(self,bytes=False):
if self.frame != 0:
return None
if len(self.data) < 4:
return None
numbytes=int(self.data[2:4],16)
if bytes:
return numbytes
frames=int((numbytes-6-1)/7)+1+1 if numbytes > 6 else 1
return frames
def _formatTs(self):
dt=datetime.datetime.fromtimestamp(self.ts,tz=datetime.UTC)
return dt.strftime("%F-%T.")+dt.strftime("%f")[0:3]
def __str__(self):
return f"{self._formatTs()},{self.prio},{self.pgn},{self.src},{self.dst},{self.len},{dataToSep(self.data)}"
def printOut(self,format:Format):
if format.key == Format.F_PASS:
return f"({self.ts:.6f}) {self.dev} {self.hdr}#{self.data}"
else:
return str(self)
@classmethod
def fromDump(cls,line:str):
'''(1658058069.867835) can0 09F80103#ACAF6C20B79AAC06'''
if line is None or line == '':
return None
match=cls.DUMP_PAT.search(line)
if match is None:
logError("no dump pattern in line %s",line,keep=True)
return
ts=match[1]
data=match[4]
hdr=match[3]
hdrval=int(hdr,16)
#see candump2analyzer
src=hdrval & 0xff
prio=(hdrval >> 26) & 0x7
PF=(hdrval >> 16) & 0xff
PS=(hdrval >> 8) & 0xff
RDP=(hdrval >> 24) & 3
pgn=0
if PF < 240:
dst=PS
pgn=(RDP << 16) + (PF << 8)
else:
dst=0xff
pgn=(RDP << 16) + (PF << 8)+PS
return CanFrame(float(ts),pgn,src=src,dst=dst,prio=prio,data=data,dev=match[2],hdr=hdr)
class MultiFrame(CanFrame):
def __init__(self,firstFrame: CanFrame):
super().__init__(firstFrame.ts,firstFrame.pgn,
src=firstFrame.src,dst=firstFrame.dst,prio=firstFrame.prio,
dev=firstFrame.dev,hdr=firstFrame.hdr)
self.data=""
self.numFrames=firstFrame.getFPNum(bytes=False)
self.len=firstFrame.getFPNum(bytes=True)
self.finished=False
self.addFrame(firstFrame)
def addFrame(self,frame:CanFrame):
if self.finished:
return False
if frame.frame is None:
return False
if frame.frame == 0:
self.data+=frame.data[4:]
else:
self.data+=frame.data[2:]
if frame.frame >= (self.numFrames-1):
self.finished=True
return True
def __str__(self):
return f"{self._formatTs()},{self.prio},{self.pgn},{self.src},{self.dst},{self.len},{dataToSep(self.data,self.len)}"
def usage():
print(f"usage: {sys.argv[0]} [-q] [-p pgn,pgn,...] [-w waitsec] [ -f plain|actisense] file",file=sys.stderr)
sys.exit(1)
FORMATS=[
Format(Format.N_PLAIN,Format.F_PLAIN),
Format(Format.N_ACT,Format.F_ACT),
Format(Format.N_PASS,Format.F_PASS,False),
Format(Format.N_SEASMART,Format.F_SEASMART)
]
MAX_ACT=400
ACT_ESC=0x10
ACT_START=0x2
ACT_N2K=0x93
ACT_END=0x3
class ActBuffer:
def __init__(self):
self.buf=bytearray(MAX_ACT)
self.sum=0
self.idx=0
self.clear()
def clear(self):
self.sum=0
self.idx=2
self.buf[0:2]=(ACT_ESC,ACT_START)
def add(self,val):
#TODO: len check?
val=val & 0xff
self.buf[self.idx]=val
self.sum = (self.sum + val) & 0xff
self.idx+=1
if val == ACT_ESC:
self.buf[self.idx]=ACT_ESC
self.idx+=1
def finalize(self):
self.sum=self.sum % 256
self.sum = 256 - self.sum if self.sum != 0 else 0
self.add(self.sum)
self.buf[self.idx]=ACT_ESC
self.idx+=1
self.buf[self.idx]=ACT_END
self.idx+=1
actBuffer=ActBuffer()
LB=b'0000000000000'
B_STAR=0x2a
class SeasmartBuffer:
def __init__(self):
self.buf=bytearray(500)
self.idx=0
self.clear()
def clear(self):
self.idx=0
def addB(self,bv,mlen=None):
l=len(bv)
if mlen is not None and mlen < l:
l=mlen
self.buf[self.idx:self.idx+l]=memoryview(bv)[0:l]
else:
self.buf[self.idx:self.idx+l]=bv
self.idx+=l
def addVal(self,val,blen=2):
hs=hex(val)[2:].encode()
if len(hs) != blen:
hs=(LB+hs)[-blen:]
self.addB(hs)
def finalize(self):
sum=0
self.buf[self.idx]=B_STAR
self.idx+=1
for b in memoryview(self.buf)[1:]:
if b == B_STAR:
break
sum ^= b
sum = sum & 0xff
self.addVal(sum)
self.addB(b'\x0d\x0a')
seasmartBuffer=SeasmartBuffer()
def send_act(frame_like:CanFrame,quiet,stream):
try:
actBuffer.clear()
actBuffer.add(ACT_N2K)
actBuffer.add(frame_like.len+11)
actBuffer.add(frame_like.prio)
pgn=frame_like.pgn
actBuffer.add(pgn)
pgn = pgn >> 8
actBuffer.add(pgn)
pgn = pgn >> 8;
actBuffer.add(pgn)
actBuffer.add(frame_like.dst)
actBuffer.add(frame_like.src)
#Time
ts=int(frame_like.ts)
actBuffer.add(ts>>24)
actBuffer.add(ts>>16)
actBuffer.add(ts>>8)
actBuffer.add(ts)
actBuffer.add(frame_like.len)
for i in range(0,frame_like.len*2,2):
actBuffer.add(int(frame_like.data[i:i+2],16))
actBuffer.finalize()
written=stream.write(memoryview(actBuffer.buf)[0:actBuffer.idx])
if (written != actBuffer.idx):
if not quiet:
logError(f"actisense not all bytes written {written}/{actBuffer.idx} for pgn={frame_like.pgn} ts={frame_like.ts}",keep=True)
stream.flush()
return True
except Exception as e:
if not quiet:
print(f"Error writing actisense for pgn {frame_like.pgn}, idx={actBuffer.idx}: {e}",file=sys.stderr)
return False
BK=b','
def send_seasmart(frame_like:CanFrame,quiet,stream):
try:
seasmartBuffer.clear()
seasmartBuffer.addB(b'$PCDIN,')
seasmartBuffer.addVal(frame_like.pgn,6)
seasmartBuffer.addB(BK)
seasmartBuffer.addVal(int(frame_like.ts),8)
seasmartBuffer.addB(BK)
seasmartBuffer.addVal(frame_like.src)
seasmartBuffer.addB(BK)
seasmartBuffer.addB(frame_like.data.encode(),mlen=frame_like.len*2)
seasmartBuffer.finalize()
written=stream.write(memoryview(seasmartBuffer.buf)[0:seasmartBuffer.idx])
if (written != seasmartBuffer.idx):
if not quiet:
raise Exception(f"seasmart not all bytes written {written}/{seasmartBuffer.idx} for pgn={frame_like.pgn} ts={frame_like.ts}")
stream.flush()
return True
except Exception as e:
if not quiet:
logError(f"writing seasmart for pgn {frame_like.pgn}, idx={seasmartBuffer.idx}: {e}",keep=True)
return False
class Counters:
C_OK=1
C_FAIL=2
C_FRAME=3
TITLES={
C_OK:'OK',
C_FAIL:'FAIL',
C_FRAME:'FRAMES'
}
def __init__(self):
self.counters={}
for i in self.TITLES.keys():
self.counters[i]=0
def add(self,idx:int):
if idx not in self.TITLES.keys():
return
self.counters[idx]+=1
def __str__(self):
rt=None
for i in self.TITLES.keys():
v=f"{self.TITLES[i]}:{self.counters[i]}"
if rt is None:
rt=v
else:
rt+=","+v
return rt
def writeOut(frame:CanFrame,format:Format,quiet:bool,counters:Counters):
rt=False
if format.key == Format.F_ACT:
rt= send_act(frame,quiet,sys.stdout.buffer)
elif format.key == Format.F_SEASMART:
rt= send_seasmart(frame,quiet,sys.stdout.buffer)
else:
print(frame.printOut(format))
rt=True
counters.add(Counters.C_OK if rt else Counters.C_FAIL)
return rt
def findFormat(name:str)->Format:
for f in FORMATS:
if f.name == name:
return f
return None
if __name__ == '__main__':
try:
opts,args=getopt.getopt(sys.argv[1:],"hp:qw:f:")
except getopt.GetoptError as e:
logError(e)
pgnlist=[]
quiet=False
delay=0.0
format=findFormat(Format.N_PLAIN)
for o,a in opts:
if o == '-h':
usage()
elif o == '-q':
quiet=True
elif o == '-p':
pgns=(int(x) for x in a.split(","))
pgnlist.extend(pgns)
elif o == '-w':
delay=float(a)
elif o == '-f':
format=findFormat(a)
if format is None:
logError(f"invalid format {a}, allowed {','.join(x.name for x in FORMATS)}")
if len(args) < 1:
usage()
hasFilter=len(pgnlist) > 0
if not quiet and hasFilter:
print(f"PGNs: {','.join(str(x) for x in pgnlist)}",file=sys.stderr)
counters=Counters()
with open (args[0],"r") as fh:
buffer={}
lnr=0
for line in fh:
lnr+=1
frame=CanFrame.fromDump(line)
if frame is None:
continue
if hasFilter and not frame.pgn in pgnlist:
continue
counters.add(Counters.C_FRAME)
if frame.sequence is None or not format.merge:
writeOut(frame,format,quiet,counters=counters)
if delay > 0:
time.sleep(delay)
else:
key=frame.key()
mf=buffer.get(key)
mustDelete=False
if mf is None:
if frame.frame != 0:
if not quiet:
print(f"floating multi frame in line {lnr}: {frame}",file=sys.stderr)
continue
mf=MultiFrame(frame)
if not mf.finished:
buffer[key]=mf
else:
mf.addFrame(frame)
mustDelete=True
if mf.finished:
writeOut(mf,format,quiet,counters=counters)
if mustDelete:
del buffer[key]
if delay > 0:
time.sleep(delay)
if not quiet:
print(f"STATISTICS: {counters}",file=sys.stderr)

View File

@@ -53,16 +53,16 @@ types:
- value: M5_ENV3#grv#
key: true
resource: qmp69881#grv#1,sht3x#grv#1
# - label: "M5 ENV4"
# type: checkbox
# key: m5env4#grv#
# target: define
# url: "https://docs.m5stack.com/en/unit/ENV%E2%85%A3%20Unit"
# description: "M5 sensor module temperature, humidity, pressure"
# values:
# - value: M5_ENV4#grv#
# key: true
# resource: bmp280#grv#1,sht3x#grv#1
- label: "M5 ENV4"
type: checkbox
key: m5env4#grv#
target: define
url: "https://docs.m5stack.com/en/unit/ENV%E2%85%A3%20Unit"
description: "M5 sensor module temperature, humidity, pressure"
values:
- value: M5_ENV4#grv#
key: true
resource: bmp280#grv#1,sht4x#grv#1
- type: checkbox
label: SHT3X-1
description: "SHT30 temperature and humidity sensor 0x44"
@@ -179,6 +179,11 @@ types:
description: "M5 Gps Unit"
url: "https://docs.m5stack.com/en/unit/gps"
resource: serial
- label: "Gps Unit v1.1"
value: M5_GPSV11_UNIT#grv#
description: "M5 Gps Unit v1.1"
url: "https://docs.m5stack.com/en/unit/Unit-GPS%20v1.1"
resource: serial
- label: "RS232/RS422"
value: SERIAL_GROOVE_232#grv#
description: "Generic RS232/RS422 Unit (bidirectional)"
@@ -234,6 +239,46 @@ types:
- 37
- 38
- &gpiopinvs3
- {label: unset,value:}
- {label: "0: boot mode control",value: 0}
- 1
- 2
- {label: "3: JTAG control", value: 3}
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 17
- 18
- 19
- 20
- 21
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- {label: "45: strapping pin", value: 45}
- {label: "46: strapping pin", value: 46}
- 47
- 48
- &gpioinput
type: dropdown
resource: "gpio:"
@@ -275,12 +320,39 @@ types:
- &protogpio
- {label: unset,value:}
- PPIN19
- PPIN21
- PPIN22
- PPIN23
- PPIN25
- PPIN33
- {label: "PPIN19(left-2,gpio 19)", value: PPIN19}
- {label: "PPIN21(right-1,gpio 21)", value: PPIN21}
- {label: "PPIN22(left-1,gpio 22)", value: PPIN22}
- {label: "PPIN23(left-3,gpio 23)", value: PPIN23}
- {label: "PPIN25(right-2,gpio 25)", value: PPIN25}
- {label: "PPIN33(left-4,gpio 33)", value: PPIN33}
- &protogpios3
- {label: unset,value:}
- {label: "PPIN19(left-2,gpio 6)", value: PPIN19}
- {label: "PPIN21(right-1,gpio 39)", value: PPIN21}
- {label: "PPIN22(left-1,gpio 5)", value: PPIN22}
- {label: "PPIN23(left-3,gpio 7)", value: PPIN23}
- {label: "PPIN25(right-2,gpio 38)", value: PPIN25}
- {label: "PPIN33(left-4,gpio 8)", value: PPIN33}
- &baudselect
type: dropdown
help: 'Select the baud rate'
values:
- {label: unset,value:}
- 1200
- 2400
- 4800
- 9600
- 14400
- 19200
- 28800
- 38400
- 57600
- 115200
- 230400
- 460800
- &serialRX
<<: *gpioinput
@@ -294,6 +366,33 @@ types:
help: 'number of the GPIO pin for the transmit function'
target: "define:#serial#TX"
mandatory: true
- &serialEnablePin
<<: *gpiopin
key: ENA
label: "enable pin"
help: "GPIO pin for output enable"
target: "define:#serial#ENA"
mandatory: false
- &serialEnableLow
type: checkbox
key: ELOW
label: "enable low"
target: "define:#serial#ELO"
default: false
help: "set: low on enable pin for output, unset: high on enable pin for output"
values:
- key: true
value: 1
- key: false
value: 0
- &serialFixedBaud
<<: *baudselect
key: fixedBaud
label: "fixed baud"
help: "you can set a fixed baud rate here, this disables changing the baud rate in the UI"
target: "define:#serial#BAUD"
- &serialValues
- key: true
children:
@@ -310,6 +409,9 @@ types:
children:
- *serialRX
- *serialTX
- *serialEnablePin
- *serialEnableLow
- *serialFixedBaud
- key: bi
value: 2
label: "BiDir"
@@ -318,18 +420,25 @@ types:
children:
- *serialRX
- *serialTX
- *serialFixedBaud
- key: rx
value: 3
label: "RX"
description: "Input only"
children:
- *serialRX
- *serialEnablePin
- *serialEnableLow
- *serialFixedBaud
- key: tx
value: 1
label: "TX"
description: "output only"
children:
- *serialTX
- *serialEnablePin
- *serialEnableLow
- *serialFixedBaud
- &serial1
type: checkbox
label: 'Serial 1'
@@ -664,13 +773,18 @@ types:
label: "Gps Base"
url: "https://docs.m5stack.com/en/atom/atomicgps"
resource: serial
- value: M5_GPSV2_KIT
description: "M5 Stack Gps Base v2.0"
label: "Gps Base"
url: "https://docs.m5stack.com/en/atom/Atomic_GPS_Base_v2.0"
resource: serial
- value: M5_PROTO_HUB
description: "M5 Stack HUB PROTO"
url: "https://docs.m5stack.com/en/atom/atomhub"
label: "Hub Proto"
base:
gpioinputv: *protogpio
gpiopinv: *protogpio
gpioinputv: "#protogpio#"
gpiopinv: "#protogpio#"
children:
*m5protochildren
- value: M5_PORTABC
@@ -689,6 +803,13 @@ resources:
config:
children:
- type: string
label: 'Build Name'
key: buildname
target: "define:GWBUILD_NAME"
help: "Set a name to identify your build. Will also become the name for the generated files and the firmware type in the image. Max 31 characters."
max: 31
allowed: "0-9A-Za-z_-"
- type: select
target: environment
label: 'Board'
@@ -697,6 +818,7 @@ config:
gpiopinv: *gpiopinv
gpioinputv: *gpioinputv
grv: ""
protogpio: *protogpio
values:
- value: m5stack-atom-generic
label: m5stack-atom
@@ -711,6 +833,8 @@ config:
description: "M5 Stack AtomS3 light"
url: "http://docs.m5stack.com/en/core/AtomS3%20Lite"
resource: *esp32default
base:
protogpio: *protogpios3
children:
- *m5base
- *m5groove
@@ -725,7 +849,7 @@ config:
- value: nodemcu-generic
label: nodemcu
description: "Node mcu esp32"
description: "Node mcu esp32,4MB flash, no PSRAM"
url: "https://docs.platformio.org/en/stable/boards/espressif32/nodemcu-32s.html"
resource: *esp32default
children:
@@ -746,6 +870,38 @@ config:
base:
busname: "1"
bus: "1"
- <<: *spisensors
base:
busname: "2"
bus: "2"
- value: s3devkitm-generic
label: s3devkitm
description: "esp32 s3 generic, 8MB flash, no PSRAM "
url: "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitm-1.html"
resource: *esp32default
base:
gpiopinv: *gpiopinvs3
gpioinputv: *gpiopinvs3
protogpio: *protogpios3
children:
- *serial1
- *serial2
- *can
- *resetButton
- *led
- <<: *iicsensors
base:
busname: "1"
bus: ""
- <<: *iicsensors
base:
busname: "2"
bus: "2"
- <<: *spisensors
base:
busname: "1"
bus: "1"
- <<: *spisensors
base:
busname: "2"

View File

@@ -167,8 +167,17 @@ class PipelineInfo{
updateStatus();
if (gitSha !== undefined) param.tag=gitSha;
param.config=JSON.stringify(config);
let buildname=config['root:buildname']
if (buildname){
param.suffix="-"+buildname
}
if (buildVersion !== undefined){
param.suffix="-"+buildVersion;
if (param.suffix){
param.suffix+="-"+buildVersion;
}
else{
param.suffix="-"+buildVersion;
}
}
fetchJson(API,Object.assign({
api:'start'},param))
@@ -234,7 +243,11 @@ class PipelineInfo{
}
const downloadConfig=()=>{
let name=configName;
if (isModified) name=name.replace(/[0-9]*$/,'')+formatDate(undefined,true);
const buildname=config["root:buildname"]
if (buildname && name != buildname){
name+="-"+buildname+"-";
}
name=name.replace(/[0-9]*$/,'')+formatDate(undefined,true);
name+=".json";
fileDownload(JSON.stringify(config),name);
}
@@ -521,6 +534,38 @@ class PipelineInfo{
addDescription(config,inputFrame);
initialConfig=expandedValues[0];
}
if (config.type === 'string'){
let ip=addEl('input','t'+config.type,inputFrame);
addDescription(config,inputFrame);
const buildChild=(value)=>{
if (value) {
if (config.max) {
if (value && value.length > config.max) {
value = value.substring(0, config.max);
}
}
if (config.allowed) {
let check = new RegExp("[^" + config.allowed + "]", "g");
let nv = value.replace(check, "");
if (nv != value) {
value = nv;
}
}
}
return Object.assign({},config,{key: value,value:value});
}
initialConfig=buildChild(current);
ip.value=initialConfig.value||"";
ip.addEventListener('change',(ev)=>{
let value=ev.target.value;
let cbv=buildChild(value);
if (cbv.value != value){
ev.target.value=cbv.value;
}
callback(cbv,false);
});
}
let childFrame=addEl('div','childFrame',frame);
if (initialConfig !== undefined){
callback(initialConfig,true,childFrame);

View File

@@ -0,0 +1 @@
{"root:board":"m5stack-atom-generic","root:board:m5lightbase":"M5_GPSV2_KIT","root:board:m5groove":"CAN","root:board:m5groove:m5groovecan":"M5_CANUNIT"}