Merge branch 'wellenvogel:master' into master
This commit is contained in:
commit
55862efe37
|
@ -59,5 +59,5 @@ jobs:
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ steps.version.outputs.version}}
|
tag: ${{ steps.version.outputs.version}}
|
||||||
file: ./.pio/build/*/*-all.bin
|
file: ./.pio/build/*/*-{all,update}.bin
|
||||||
file_glob: true
|
file_glob: true
|
||||||
|
|
81
Readme.md
81
Readme.md
|
@ -8,11 +8,13 @@ Based on the work of
|
||||||
and a couple of other open source projects.
|
and a couple of other open source projects.
|
||||||
Many thanks for all the great work.
|
Many thanks for all the great work.
|
||||||
|
|
||||||
|
This project is part of [OpenBoatProjects](https://open-boat-projects.org/de/nmea2000-gateway-mit-m5stack-atom/).
|
||||||
|
|
||||||
Goal
|
Goal
|
||||||
----
|
----
|
||||||
Have a simple ready-to-go ESP32 binary that can be flashed onto a [M5 Atom CAN](https://docs.m5stack.com/en/atom/atom_can), potentially extended by an [Atom Tail485](https://shop.m5stack.com/collections/atom-series/products/atom-tail485?variant=32169041559642) for NMEA0183 connection and power supply.
|
Have a simple ready-to-go ESP32 binary that can be flashed onto a [M5 Atom CAN](https://docs.m5stack.com/en/atom/atom_can), potentially extended by an [Atom Tail485](https://shop.m5stack.com/collections/atom-series/products/atom-tail485?variant=32169041559642) for NMEA0183 connection and power supply.
|
||||||
|
|
||||||
But will also run on other ESP32 boards.
|
But will also run on other ESP32 boards see [Hardware](doc/Hardware.md).
|
||||||
|
|
||||||
What is included
|
What is included
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
@ -38,15 +40,59 @@ The software is prepared to run on different kinds of ESP32 based modules and ac
|
||||||
For the list of hardware set ups refer to [Hardware](doc/Hardware.md).
|
For the list of hardware set ups refer to [Hardware](doc/Hardware.md).
|
||||||
|
|
||||||
|
|
||||||
Pre Build Binaries
|
Installation
|
||||||
------------------
|
------------
|
||||||
In the [release section](https://github.com/wellenvogel/esp32-nmea2000/releases) you can find a couple of pre-build binaries that can easily be flashed on your ESP32 board using [ESPTool](https://github.com/espressif/esptool).
|
In the [release section](../../releases) you can find a couple of pre-build binaries.<br>
|
||||||
|
They are devided into binaries for an initial flash (xxx-all.bin) and binaries for updating an existing device (xxx-update.bin).
|
||||||
|
|
||||||
|
Initial Flash
|
||||||
|
*************
|
||||||
|
To initially flash a deviceyou can use [ESPTool](https://github.com/espressif/esptool).
|
||||||
The flash command must be (example for m5stack-atom):
|
The flash command must be (example for m5stack-atom):
|
||||||
|
|
||||||
```
|
```
|
||||||
esptool.py --port XXXX --chip esp32 write_flash 0x1000 m5stack-atom-all.bin
|
esptool.py --port XXXX --chip esp32 write_flash 0x1000 m5stack-atom-20211217-all.bin
|
||||||
```
|
```
|
||||||
For the meaning of the board names have a look at [Hardware](doc/Hardware.md). For details refer to the code in [platformio.ini](platformio.ini) and look for the hardware definitions in [GwHardware.h](lib/hardware/GwHardware.h).
|
For the meaning of the board names have a look at [Hardware](doc/Hardware.md). For details refer to the code in [platformio.ini](platformio.ini) and look for the hardware definitions in [GwHardware.h](lib/hardware/GwHardware.h).
|
||||||
|
Additionally there is a small GUI for the esptool included here at [tools/flashtool.py](tools/flashtool.py)
|
||||||
|
|
||||||
|
__linux users__<br>
|
||||||
|
You can typically install the esptool (once you have python 3 installed) with
|
||||||
|
```
|
||||||
|
sudo pip install esptool
|
||||||
|
```
|
||||||
|
To use the flashtool just copy flashtool.py and esptool.py from [tools](tools) to an empty directory.
|
||||||
|
```
|
||||||
|
sudo pip install tkinter
|
||||||
|
sudo pip install pyserial
|
||||||
|
```
|
||||||
|
Afterwards run flashtool.py (potentially making it executable before).
|
||||||
|
|
||||||
|
__windows users__<br>
|
||||||
|
You can find a prebuild executable in tools: [esptool.exe](tools/esptool.exe).
|
||||||
|
Just create an empty directory on your machine, download the esptool to this directory and also download the binary (xxx-all.bin) from [releases](../../releases).
|
||||||
|
Afterwards you need to install the driver for the serial port to connect your ESP32 board. For a modern windows the driver at [FTDI](https://ftdichip.com/drivers/d2xx-drivers/) should be working.
|
||||||
|
After installing the driver check with your device manager for the com port that is assigned to your connected esp device.
|
||||||
|
Open a command prompt and change into the directory you downloaded the esptool.exe and the firmware binary.
|
||||||
|
Flash with the command
|
||||||
|
```
|
||||||
|
esptool.exe --port COM3 0x1000 xxxxx-xxxx-all.bin
|
||||||
|
```
|
||||||
|
Replace COM3 with the port shown in the device manager and the xxx with the name of the downloaded binary.
|
||||||
|
If you do not want to use the command line you can download the precompiled [flashtool.exe](../../raw/master/tools/flashtool.exe).
|
||||||
|
Just start the downloaded exe. Unfortunately some virus scanners seem to consider the exe a virus or trojan. There is not much I can do against this - the exe is simply build from flashtool.py - see [tools readme](tools/readme-esptool-win.txt).
|
||||||
|
|
||||||
|
|
||||||
|
Update
|
||||||
|
******
|
||||||
|
To update a device you can use the Web-UI (Update tab). In principle you could also update a device using the initial flash command (and an xxx-all.bin) firmware but this would erase all your configuration.
|
||||||
|
So for normal operation just download a xxx-update.bin from the [release](../../releases) page and use the UI to install it.
|
||||||
|
|
||||||
|
.
|
||||||
|
|
||||||
|
When you choose a file for the update the UI will check if it is a valid firmware file and will reject invalid ones.
|
||||||
|
To really execute the update click the "Upload" button. You will have a progress indicator and get a notification about the update result.
|
||||||
|
Please reload the page in your browser after the "connected" state is green as the new version could have changes thatv otherwise will not work.
|
||||||
|
|
||||||
Starting
|
Starting
|
||||||
---------
|
---------
|
||||||
|
@ -59,6 +105,13 @@ To store your changes you will be asked for an admin password. The initial one i
|
||||||
Be careful to notice the password - you can only recover from a lost password with a factory reset of the device (long press the led button until it goes blue->red->green).
|
Be careful to notice the password - you can only recover from a lost password with a factory reset of the device (long press the led button until it goes blue->red->green).
|
||||||
On the data page you will have a small dashboard for the currently received data.
|
On the data page you will have a small dashboard for the currently received data.
|
||||||
On the status page you can check the number of messages flowing in and out.
|
On the status page you can check the number of messages flowing in and out.
|
||||||
|
To help you recover lost passwords the Wifi access point passowrd and the admin password will be output at the USB port when the device starts up. So by connecting a terminal program you can retrieve those passwords.
|
||||||
|
|
||||||
|
Security Hints
|
||||||
|
--------------
|
||||||
|
You should only connect the Wifi of the device to trusted networks. There is only some very limited protection against network sniffing of denial of service attacks. Never connect the device directly to the internet without a firewall in between (like e.g. your Wifi or LTE router). Especially be careful when connecting to open port networks.
|
||||||
|
When making changes you will be asked for the admin password - and this one is always send somehow encrypted. But when you change the Wifi access point password or the Wifi client password it will be sent in clear text.
|
||||||
|
When you enable the "remember me" for the admin password it will be stored in clear text in your browser (use ForgetPassword to remove it from there).
|
||||||
|
|
||||||
Conversion from and to NMEA0183 XDR
|
Conversion from and to NMEA0183 XDR
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
@ -74,6 +127,22 @@ Extending the Software
|
||||||
To give room for adding own software and still being able to keep in sync with this master part there is a concept of user tasks that will allow you to add your own hardware definitions and to add code that should be executed without the need to change parts of the existing software.
|
To give room for adding own software and still being able to keep in sync with this master part there is a concept of user tasks that will allow you to add your own hardware definitions and to add code that should be executed without the need to change parts of the existing software.
|
||||||
For details refer to the [example description](lib/exampletask/Readme.md).
|
For details refer to the [example description](lib/exampletask/Readme.md).
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
---------
|
||||||
|
[20211218](../../releases/tag/20211218)
|
||||||
|
********
|
||||||
|
* 1st real release
|
||||||
|
* use the initial flash if you had the pre-release installed
|
||||||
|
* most of the N2K <-> 0183 conversions working, see [Conversions](doc/Conversions.pdf)
|
||||||
|
* display of received data
|
||||||
|
* xdr record mapping (see [XdrMappings](doc/XdrMappings.md))
|
||||||
|
* OTA update included in the UI
|
||||||
|
* description updated
|
||||||
|
* extension API
|
||||||
|
|
||||||
|
[20211113](../../releases/tag/20211113)
|
||||||
|
********
|
||||||
|
* Pre-release
|
||||||
|
* basic functions are working
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ M5 Atom CAN with M5 Tail485
|
||||||
* Build Define: BOARD_M5ATOM
|
* Build Define: BOARD_M5ATOM
|
||||||
* Power: 12V via Tail485
|
* Power: 12V via Tail485
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
With this set up you can use the device as a gateway between NMEA2000 and NMEA0183. The NMEA0183 connection can only work as either a sender or receiver. Additionally you can connect other devices via Wifi.
|
With this set up you can use the device as a gateway between NMEA2000 and NMEA0183. The NMEA0183 connection can only work as either a sender or receiver. Additionally you can connect other devices via Wifi.
|
||||||
This way you get a simple 12V powered NMEA2000-NMEA0183 Wifi gateway.
|
This way you get a simple 12V powered NMEA2000-NMEA0183 Wifi gateway.
|
||||||
|
@ -52,17 +52,29 @@ With this set up you get basically all the features from the plain AtomCAN and t
|
||||||
|
|
||||||
M5 Stack Atom Canunit
|
M5 Stack Atom Canunit
|
||||||
---------------------
|
---------------------
|
||||||
* Hardware:
|
* Hardware: [M5_ATOM](http://docs.m5stack.com/en/core/atom_lite) + [CAN Unit](http://docs.m5stack.com/en/unit/can)
|
||||||
* Prebuild Binary: m5stack-atom-canunit-all.bin
|
* Prebuild Binary: m5stack-atom-canunit-all.bin
|
||||||
* Build Define: BOARD_M5ATOM_CANUNIT
|
* Build Define: BOARD_M5ATOM_CANUNIT
|
||||||
* Power:
|
* Power: Via USB
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Can be used e.g. as an NMEA2000 Adapter for a laptop running e.g. OpenCPN with the NMEA2000 Data converted to NMEA0183.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
M5 Stick C Canunit
|
M5 Stick C Canunit
|
||||||
------------------
|
------------------
|
||||||
* Hardware:
|
* Hardware: [M5_StickC+](http://docs.m5stack.com/en/core/m5stickc_plus) + [CAN Unit](http://docs.m5stack.com/en/unit/can)
|
||||||
* Prebuild Binary: m5stickc-atom-canunit-all.bin
|
* Prebuild Binary: m5stickc-atom-canunit-all.bin
|
||||||
* Build Define: BOARD_M5STICK_CANUNIT
|
* Build Define: BOARD_M5STICK_CANUNIT
|
||||||
* Power:
|
* Power: Via USB
|
||||||
|
* LCD: not yet implemented
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Can be used e.g. as an NMEA2000 Adapter for a laptop running e.g. OpenCPN with the NMEA2000 Data converted to NMEA0183.
|
||||||
|
|
||||||
Node MCU32s - [Homberger Board](https://github.com/AK-Homberger/NMEA2000WifiGateway-with-ESP32)
|
Node MCU32s - [Homberger Board](https://github.com/AK-Homberger/NMEA2000WifiGateway-with-ESP32)
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
|
@ -86,10 +86,11 @@ def writeFileIfChanged(fileName,data):
|
||||||
old=ih.read()
|
old=ih.read()
|
||||||
ih.close()
|
ih.close()
|
||||||
if old == data:
|
if old == data:
|
||||||
return
|
return False
|
||||||
print("#generating %s"%fileName)
|
print("#generating %s"%fileName)
|
||||||
with open(fileName,"w") as oh:
|
with open(fileName,"w") as oh:
|
||||||
oh.write(data)
|
oh.write(data)
|
||||||
|
return True
|
||||||
|
|
||||||
def mergeConfig(base,other):
|
def mergeConfig(base,other):
|
||||||
for bdir in other:
|
for bdir in other:
|
||||||
|
@ -242,6 +243,10 @@ def prebuild(env):
|
||||||
if not checkDir():
|
if not checkDir():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
userTaskDirs=getUserTaskDirs()
|
userTaskDirs=getUserTaskDirs()
|
||||||
|
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
||||||
|
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
||||||
|
compressFile(mergedConfig,mergedConfig+".gz")
|
||||||
|
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE))
|
||||||
embedded=getEmbeddedFiles(env)
|
embedded=getEmbeddedFiles(env)
|
||||||
filedefs=[]
|
filedefs=[]
|
||||||
for ef in embedded:
|
for ef in embedded:
|
||||||
|
@ -261,10 +266,6 @@ def prebuild(env):
|
||||||
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
print("#WARNING: infile %s for %s not found"%(inFile,ef))
|
||||||
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
generateEmbedded(filedefs,os.path.join(outPath(),EMBEDDED_INCLUDE))
|
||||||
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
|
||||||
mergedConfig=os.path.join(outPath(),os.path.basename(CFG_FILE))
|
|
||||||
generateMergedConfig(os.path.join(basePath(),CFG_FILE),mergedConfig,userTaskDirs)
|
|
||||||
compressFile(mergedConfig,mergedConfig+".gz")
|
|
||||||
generateCfg(mergedConfig,os.path.join(outPath(),CFG_INCLUDE))
|
|
||||||
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
|
||||||
version="dev"+datetime.now().strftime("%Y%m%d")
|
version="dev"+datetime.now().strftime("%Y%m%d")
|
||||||
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
|
||||||
|
|
|
@ -15,6 +15,7 @@ class GwApi{
|
||||||
public:
|
public:
|
||||||
double value=0;
|
double value=0;
|
||||||
bool valid=false;
|
bool valid=false;
|
||||||
|
bool changed=false; //will be set by getBoatDataValues
|
||||||
BoatValue(){}
|
BoatValue(){}
|
||||||
BoatValue(const String &n):name(n){
|
BoatValue(const String &n):name(n){
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#define GWSTR(x) #x
|
||||||
|
#define GWSTRINGIFY(x) GWSTR(x)
|
||||||
|
#ifdef GWRELEASEVERSION
|
||||||
|
#define VERSION GWSTRINGIFY(GWRELEASEVERSION)
|
||||||
|
#define LOGLEVEL GwLog::ERROR
|
||||||
|
#else
|
||||||
|
#ifdef GWDEVVERSION
|
||||||
|
#define VERSION GWSTRINGIFY(GWDEVVERSION)
|
||||||
|
#define LOGLEVEL GwLog::DEBUG
|
||||||
|
#endif
|
||||||
|
#ifndef VERSION
|
||||||
|
#define VERSION "0.9.9"
|
||||||
|
#define LOGLEVEL GwLog::DEBUG
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define FIRMWARE_TYPE GWSTRINGIFY(PIO_ENV_BUILD)
|
|
@ -193,7 +193,7 @@ class GwBoatData{
|
||||||
GWBOATDATA(double,Altitude,4000,formatFixed0)
|
GWBOATDATA(double,Altitude,4000,formatFixed0)
|
||||||
GWBOATDATA(double,WaterDepth,4000,formatDepth)
|
GWBOATDATA(double,WaterDepth,4000,formatDepth)
|
||||||
GWBOATDATA(double,DepthTransducer,4000,formatDepth)
|
GWBOATDATA(double,DepthTransducer,4000,formatDepth)
|
||||||
GWBOATDATA(double,SecondsSinceMidnight,4000,formatTime)
|
GWBOATDATA(double,GpsTime,4000,formatTime)
|
||||||
GWBOATDATA(double,WaterTemperature,4000,kelvinToC)
|
GWBOATDATA(double,WaterTemperature,4000,kelvinToC)
|
||||||
GWBOATDATA(double,XTE,4000,formatXte)
|
GWBOATDATA(double,XTE,4000,formatXte)
|
||||||
GWBOATDATA(double,DTW,4000,mtr2nm)
|
GWBOATDATA(double,DTW,4000,mtr2nm)
|
||||||
|
@ -202,7 +202,7 @@ class GwBoatData{
|
||||||
GWBOATDATA(double,WPLongitude,4000,formatLongitude)
|
GWBOATDATA(double,WPLongitude,4000,formatLongitude)
|
||||||
GWBOATDATA(uint32_t,Log,16000,mtr2nm)
|
GWBOATDATA(uint32_t,Log,16000,mtr2nm)
|
||||||
GWBOATDATA(uint32_t,TripLog,16000,mtr2nm)
|
GWBOATDATA(uint32_t,TripLog,16000,mtr2nm)
|
||||||
GWBOATDATA(uint32_t,DaysSince1970,4000,formatDate)
|
GWBOATDATA(uint32_t,GpsDate,4000,formatDate)
|
||||||
GWBOATDATA(int16_t,Timezone,8000,formatFixed0)
|
GWBOATDATA(int16_t,Timezone,8000,formatFixed0)
|
||||||
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::lifeTime,formatFixed0);
|
GWSPECBOATDATA(GwBoatDataSatList,SatInfo,GwSatInfoList::lifeTime,formatFixed0);
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -39,7 +39,7 @@ String GwConfigHandler::toJson() const{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serializeJson(jdoc,rt);
|
serializeJson(jdoc,rt);
|
||||||
logger->logString("configJson: %s",rt.c_str());
|
LOG_DEBUG(GwLog::DEBUG,"configJson: %s",rt.c_str());
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +71,11 @@ bool GwConfigHandler::saveConfig(){
|
||||||
if (it != changedValues.end()){
|
if (it != changedValues.end()){
|
||||||
val=it->second;
|
val=it->second;
|
||||||
}
|
}
|
||||||
logger->logString("saving %s=%s",configs[i]->getName().c_str(),val.c_str());
|
LOG_DEBUG(GwLog::LOG,"saving %s=%s",configs[i]->getName().c_str(),val.c_str());
|
||||||
prefs.putString(configs[i]->getName().c_str(),val);
|
prefs.putString(configs[i]->getName().c_str(),val);
|
||||||
}
|
}
|
||||||
prefs.end();
|
prefs.end();
|
||||||
logger->logString("saved config");
|
LOG_DEBUG(GwLog::LOG,"saved config");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ bool GwConfigHandler::updateValue(String name, String value){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool GwConfigHandler::reset(bool save){
|
bool GwConfigHandler::reset(bool save){
|
||||||
logger->logString("reset config");
|
LOG_DEBUG(GwLog::LOG,"reset config");
|
||||||
for (int i=0;i<getNumConfig();i++){
|
for (int i=0;i<getNumConfig();i++){
|
||||||
changedValues[configs[i]->getName()]=configs[i]->getDefault();
|
changedValues[configs[i]->getName()]=configs[i]->getDefault();
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,8 +88,6 @@ void exampleTask(GwApi *api){
|
||||||
GwApi::BoatValue *latitude=new GwApi::BoatValue(F("Latitude"));
|
GwApi::BoatValue *latitude=new GwApi::BoatValue(F("Latitude"));
|
||||||
GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName);
|
GwApi::BoatValue *testValue=new GwApi::BoatValue(boatItemName);
|
||||||
GwApi::BoatValue *valueList[]={longitude,latitude,testValue};
|
GwApi::BoatValue *valueList[]={longitude,latitude,testValue};
|
||||||
double lastTestValue=0;
|
|
||||||
bool lastTestValueValid=false;
|
|
||||||
while(true){
|
while(true){
|
||||||
delay(1000);
|
delay(1000);
|
||||||
/*
|
/*
|
||||||
|
@ -155,16 +153,13 @@ void exampleTask(GwApi *api){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (testValue->valid){
|
if (testValue->valid){
|
||||||
if (! lastTestValueValid || lastTestValue != testValue->value){
|
if (testValue->changed){
|
||||||
LOG_DEBUG(GwLog::LOG,"%s new value %s",testValue->getName().c_str(),formatValue(testValue).c_str());
|
LOG_DEBUG(GwLog::LOG,"%s new value %s",testValue->getName().c_str(),formatValue(testValue).c_str());
|
||||||
lastTestValueValid=true;
|
|
||||||
lastTestValue=testValue->value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if (lastTestValueValid){
|
if (testValue->changed){
|
||||||
LOG_DEBUG(GwLog::LOG,"%s now invalid",testValue->getName().c_str());
|
LOG_DEBUG(GwLog::LOG,"%s now invalid",testValue->getName().c_str());
|
||||||
lastTestValueValid=false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -365,23 +365,23 @@ private:
|
||||||
}
|
}
|
||||||
void convertRMC(const SNMEA0183Msg &msg)
|
void convertRMC(const SNMEA0183Msg &msg)
|
||||||
{
|
{
|
||||||
double SecondsSinceMidnight=0, Latitude=0, Longitude=0, COG=0, SOG=0, Variation=0;
|
double GpsTime=0, Latitude=0, Longitude=0, COG=0, SOG=0, Variation=0;
|
||||||
unsigned long DaysSince1970=0;
|
unsigned long GpsDate=0;
|
||||||
time_t DateTime;
|
time_t DateTime;
|
||||||
char status;
|
char status;
|
||||||
if (!NMEA0183ParseRMC_nc(msg, SecondsSinceMidnight, status, Latitude, Longitude, COG, SOG, DaysSince1970, Variation, &DateTime))
|
if (!NMEA0183ParseRMC_nc(msg, GpsTime, status, Latitude, Longitude, COG, SOG, GpsDate, Variation, &DateTime))
|
||||||
{
|
{
|
||||||
LOG_DEBUG(GwLog::DEBUG, "failed to parse RMC %s", msg.line);
|
LOG_DEBUG(GwLog::DEBUG, "failed to parse RMC %s", msg.line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
if (
|
if (
|
||||||
UD(SecondsSinceMidnight) &&
|
UD(GpsTime) &&
|
||||||
UI(DaysSince1970)
|
UI(GpsDate)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
SetN2kSystemTime(n2kMsg, 1, DaysSince1970, SecondsSinceMidnight);
|
SetN2kSystemTime(n2kMsg, 1, GpsDate, GpsTime);
|
||||||
send(n2kMsg);
|
send(n2kMsg);
|
||||||
}
|
}
|
||||||
if (UD(Latitude) &&
|
if (UD(Latitude) &&
|
||||||
|
@ -395,7 +395,7 @@ private:
|
||||||
}
|
}
|
||||||
if (UD(Variation)){
|
if (UD(Variation)){
|
||||||
SetN2kMagneticVariation(n2kMsg,1,N2kmagvar_Calc,
|
SetN2kMagneticVariation(n2kMsg,1,N2kmagvar_Calc,
|
||||||
getUint32(boatData->DaysSince1970), Variation);
|
getUint32(boatData->GpsDate), Variation);
|
||||||
send(n2kMsg);
|
send(n2kMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -739,9 +739,9 @@ private:
|
||||||
uint32_t DaysSince1970=tNMEA0183Msg::elapsedDaysSince1970(DateTime);
|
uint32_t DaysSince1970=tNMEA0183Msg::elapsedDaysSince1970(DateTime);
|
||||||
tmElements_t parts;
|
tmElements_t parts;
|
||||||
tNMEA0183Msg::breakTime(DateTime,parts);
|
tNMEA0183Msg::breakTime(DateTime,parts);
|
||||||
double SecondsSinceMidnight=parts.tm_sec+60*parts.tm_min+3600*parts.tm_hour;
|
double GpsTime=parts.tm_sec+60*parts.tm_min+3600*parts.tm_hour;
|
||||||
if (! boatData->DaysSince1970->update(DaysSince1970,msg.sourceId)) return;
|
if (! boatData->GpsDate->update(DaysSince1970,msg.sourceId)) return;
|
||||||
if (! boatData->SecondsSinceMidnight->update(SecondsSinceMidnight,msg.sourceId)) return;
|
if (! boatData->GpsTime->update(GpsTime,msg.sourceId)) return;
|
||||||
bool timezoneValid=false;
|
bool timezoneValid=false;
|
||||||
if (msg.FieldLen(4) > 0 && msg.FieldLen(5)>0){
|
if (msg.FieldLen(4) > 0 && msg.FieldLen(5)>0){
|
||||||
Timezone=Timezone/60; //N2K has offset in minutes
|
Timezone=Timezone/60; //N2K has offset in minutes
|
||||||
|
@ -750,10 +750,10 @@ private:
|
||||||
}
|
}
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
if (timezoneValid){
|
if (timezoneValid){
|
||||||
SetN2kLocalOffset(n2kMsg,DaysSince1970,SecondsSinceMidnight,Timezone);
|
SetN2kLocalOffset(n2kMsg,DaysSince1970,GpsTime,Timezone);
|
||||||
send(n2kMsg);
|
send(n2kMsg);
|
||||||
}
|
}
|
||||||
SetN2kSystemTime(n2kMsg,1,DaysSince1970,SecondsSinceMidnight);
|
SetN2kSystemTime(n2kMsg,1,DaysSince1970,GpsTime);
|
||||||
send(n2kMsg);
|
send(n2kMsg);
|
||||||
}
|
}
|
||||||
void convertGGA(const SNMEA0183Msg &msg){
|
void convertGGA(const SNMEA0183Msg &msg){
|
||||||
|
@ -773,16 +773,16 @@ private:
|
||||||
LOG_DEBUG(GwLog::DEBUG, "failed to parse GGA %s", msg.line);
|
LOG_DEBUG(GwLog::DEBUG, "failed to parse GGA %s", msg.line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (! updateDouble(boatData->SecondsSinceMidnight,GPSTime,msg.sourceId)) return;
|
if (! updateDouble(boatData->GpsTime,GPSTime,msg.sourceId)) return;
|
||||||
if (! updateDouble(boatData->Latitude,Latitude,msg.sourceId)) return;
|
if (! updateDouble(boatData->Latitude,Latitude,msg.sourceId)) return;
|
||||||
if (! updateDouble(boatData->Longitude,Longitude,msg.sourceId)) return;
|
if (! updateDouble(boatData->Longitude,Longitude,msg.sourceId)) return;
|
||||||
if (! updateDouble(boatData->Altitude,Altitude,msg.sourceId)) return;
|
if (! updateDouble(boatData->Altitude,Altitude,msg.sourceId)) return;
|
||||||
if (! updateDouble(boatData->HDOP,HDOP,msg.sourceId)) return;
|
if (! updateDouble(boatData->HDOP,HDOP,msg.sourceId)) return;
|
||||||
if (! boatData->DaysSince1970->isValid()) return;
|
if (! boatData->GpsDate->isValid()) return;
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
tN2kGNSSmethod method=N2kGNSSm_noGNSS;
|
tN2kGNSSmethod method=N2kGNSSm_noGNSS;
|
||||||
if (GPSQualityIndicator <=5 ) method= (tN2kGNSSmethod)GPSQualityIndicator;
|
if (GPSQualityIndicator <=5 ) method= (tN2kGNSSmethod)GPSQualityIndicator;
|
||||||
SetN2kGNSS(n2kMsg,1, boatData->DaysSince1970->getData(),
|
SetN2kGNSS(n2kMsg,1, boatData->GpsDate->getData(),
|
||||||
GPSTime, Latitude, Longitude, Altitude,
|
GPSTime, Latitude, Longitude, Altitude,
|
||||||
N2kGNSSt_GPS, method,
|
N2kGNSSt_GPS, method,
|
||||||
SatelliteCount, HDOP, boatData->PDOP->getDataWithDefault(N2kDoubleNA), 0,
|
SatelliteCount, HDOP, boatData->PDOP->getDataWithDefault(N2kDoubleNA), 0,
|
||||||
|
@ -882,7 +882,7 @@ private:
|
||||||
if (GLL.status != 'A') return;
|
if (GLL.status != 'A') return;
|
||||||
if (! updateDouble(boatData->Latitude,GLL.latitude,msg.sourceId)) return;
|
if (! updateDouble(boatData->Latitude,GLL.latitude,msg.sourceId)) return;
|
||||||
if (! updateDouble(boatData->Longitude,GLL.longitude,msg.sourceId)) return;
|
if (! updateDouble(boatData->Longitude,GLL.longitude,msg.sourceId)) return;
|
||||||
if (! updateDouble(boatData->SecondsSinceMidnight,GLL.GPSTime,msg.sourceId)) return;
|
if (! updateDouble(boatData->GpsTime,GLL.GPSTime,msg.sourceId)) return;
|
||||||
tN2kMsg n2kMsg;
|
tN2kMsg n2kMsg;
|
||||||
SetN2kLatLonRapid(n2kMsg,GLL.latitude,GLL.longitude);
|
SetN2kLatLonRapid(n2kMsg,GLL.latitude,GLL.longitude);
|
||||||
send(n2kMsg);
|
send(n2kMsg);
|
||||||
|
|
|
@ -142,7 +142,7 @@ private:
|
||||||
|
|
||||||
virtual unsigned long *handledPgns()
|
virtual unsigned long *handledPgns()
|
||||||
{
|
{
|
||||||
logger->logString("CONV: # %d handled PGNS", converters.numConverters());
|
LOG_DEBUG(GwLog::LOG,"CONV: # %d handled PGNS", converters.numConverters());
|
||||||
return converters.handledPgns();
|
return converters.handledPgns();
|
||||||
}
|
}
|
||||||
virtual String handledKeys(){
|
virtual String handledKeys(){
|
||||||
|
@ -242,7 +242,7 @@ private:
|
||||||
ParseN2kMagneticVariation(N2kMsg, SID, Source, DaysSince1970, Variation);
|
ParseN2kMagneticVariation(N2kMsg, SID, Source, DaysSince1970, Variation);
|
||||||
updateDouble(boatData->Variation, Variation);
|
updateDouble(boatData->Variation, Variation);
|
||||||
if (DaysSince1970 != N2kUInt16NA && DaysSince1970 != 0)
|
if (DaysSince1970 != N2kUInt16NA && DaysSince1970 != 0)
|
||||||
boatData->DaysSince1970->update(DaysSince1970,sourceId);
|
boatData->GpsDate->update(DaysSince1970,sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//*****************************************************************************
|
//*****************************************************************************
|
||||||
|
@ -377,23 +377,23 @@ private:
|
||||||
double Longitude;
|
double Longitude;
|
||||||
double Altitude;
|
double Altitude;
|
||||||
uint16_t DaysSince1970;
|
uint16_t DaysSince1970;
|
||||||
double SecondsSinceMidnight;
|
double GpsTime;
|
||||||
if (ParseN2kGNSS(N2kMsg, SID, DaysSince1970, SecondsSinceMidnight, Latitude, Longitude, Altitude, GNSStype, GNSSmethod,
|
if (ParseN2kGNSS(N2kMsg, SID, DaysSince1970, GpsTime, Latitude, Longitude, Altitude, GNSStype, GNSSmethod,
|
||||||
nSatellites, HDOP, PDOP, GeoidalSeparation,
|
nSatellites, HDOP, PDOP, GeoidalSeparation,
|
||||||
nReferenceStations, ReferenceStationType, ReferenceSationID, AgeOfCorrection))
|
nReferenceStations, ReferenceStationType, ReferenceSationID, AgeOfCorrection))
|
||||||
{
|
{
|
||||||
updateDouble(boatData->Latitude, Latitude);
|
updateDouble(boatData->Latitude, Latitude);
|
||||||
updateDouble(boatData->Longitude, Longitude);
|
updateDouble(boatData->Longitude, Longitude);
|
||||||
updateDouble(boatData->Altitude, Altitude);
|
updateDouble(boatData->Altitude, Altitude);
|
||||||
updateDouble(boatData->SecondsSinceMidnight, SecondsSinceMidnight);
|
updateDouble(boatData->GpsTime, GpsTime);
|
||||||
updateDouble(boatData->HDOP,HDOP);
|
updateDouble(boatData->HDOP,HDOP);
|
||||||
updateDouble(boatData->PDOP,PDOP);
|
updateDouble(boatData->PDOP,PDOP);
|
||||||
if (DaysSince1970 != N2kUInt16NA && DaysSince1970 != 0)
|
if (DaysSince1970 != N2kUInt16NA && DaysSince1970 != 0)
|
||||||
boatData->DaysSince1970->update(DaysSince1970,sourceId);
|
boatData->GpsDate->update(DaysSince1970,sourceId);
|
||||||
int quality=0;
|
int quality=0;
|
||||||
if ((int)GNSSmethod <= 5) quality=(int)GNSSmethod;
|
if ((int)GNSSmethod <= 5) quality=(int)GNSSmethod;
|
||||||
tNMEA0183AISMsg nmeaMsg;
|
tNMEA0183AISMsg nmeaMsg;
|
||||||
if (NMEA0183SetGGA(nmeaMsg,SecondsSinceMidnight,Latitude,Longitude,
|
if (NMEA0183SetGGA(nmeaMsg,GpsTime,Latitude,Longitude,
|
||||||
quality,nSatellites,HDOP,Altitude,GeoidalSeparation,AgeOfCorrection,
|
quality,nSatellites,HDOP,Altitude,GeoidalSeparation,AgeOfCorrection,
|
||||||
ReferenceSationID,talkerId)){
|
ReferenceSationID,talkerId)){
|
||||||
SendMessage(nmeaMsg);
|
SendMessage(nmeaMsg);
|
||||||
|
@ -555,12 +555,12 @@ private:
|
||||||
tNMEA0183Msg NMEA0183Msg;
|
tNMEA0183Msg NMEA0183Msg;
|
||||||
if (NMEA0183SetRMC(NMEA0183Msg,
|
if (NMEA0183SetRMC(NMEA0183Msg,
|
||||||
|
|
||||||
boatData->SecondsSinceMidnight->getDataWithDefault(NMEA0183DoubleNA),
|
boatData->GpsTime->getDataWithDefault(NMEA0183DoubleNA),
|
||||||
boatData->Latitude->getDataWithDefault(NMEA0183DoubleNA),
|
boatData->Latitude->getDataWithDefault(NMEA0183DoubleNA),
|
||||||
boatData->Longitude->getDataWithDefault(NMEA0183DoubleNA),
|
boatData->Longitude->getDataWithDefault(NMEA0183DoubleNA),
|
||||||
boatData->COG->getDataWithDefault(NMEA0183DoubleNA),
|
boatData->COG->getDataWithDefault(NMEA0183DoubleNA),
|
||||||
boatData->SOG->getDataWithDefault(NMEA0183DoubleNA),
|
boatData->SOG->getDataWithDefault(NMEA0183DoubleNA),
|
||||||
boatData->DaysSince1970->getDataWithDefault(NMEA0183UInt32NA),
|
boatData->GpsDate->getDataWithDefault(NMEA0183UInt32NA),
|
||||||
boatData->Variation->getDataWithDefault(NMEA0183DoubleNA),
|
boatData->Variation->getDataWithDefault(NMEA0183DoubleNA),
|
||||||
talkerId))
|
talkerId))
|
||||||
{
|
{
|
||||||
|
@ -574,16 +574,16 @@ private:
|
||||||
void HandleLog(const tN2kMsg &N2kMsg)
|
void HandleLog(const tN2kMsg &N2kMsg)
|
||||||
{
|
{
|
||||||
uint16_t DaysSince1970;
|
uint16_t DaysSince1970;
|
||||||
double SecondsSinceMidnight;
|
double GpsTime;
|
||||||
uint32_t Log, TripLog;
|
uint32_t Log, TripLog;
|
||||||
if (ParseN2kDistanceLog(N2kMsg, DaysSince1970, SecondsSinceMidnight, Log, TripLog))
|
if (ParseN2kDistanceLog(N2kMsg, DaysSince1970, GpsTime, Log, TripLog))
|
||||||
{
|
{
|
||||||
if (Log != N2kUInt32NA)
|
if (Log != N2kUInt32NA)
|
||||||
boatData->Log->update(Log,sourceId);
|
boatData->Log->update(Log,sourceId);
|
||||||
if (TripLog != N2kUInt32NA)
|
if (TripLog != N2kUInt32NA)
|
||||||
boatData->TripLog->update(TripLog,sourceId);
|
boatData->TripLog->update(TripLog,sourceId);
|
||||||
if (DaysSince1970 != N2kUInt16NA && DaysSince1970 != 0)
|
if (DaysSince1970 != N2kUInt16NA && DaysSince1970 != 0)
|
||||||
boatData->DaysSince1970->update(DaysSince1970,sourceId);
|
boatData->GpsDate->update(DaysSince1970,sourceId);
|
||||||
tNMEA0183Msg NMEA0183Msg;
|
tNMEA0183Msg NMEA0183Msg;
|
||||||
|
|
||||||
if (!NMEA0183Msg.Init("VLW", talkerId))
|
if (!NMEA0183Msg.Init("VLW", talkerId))
|
||||||
|
@ -996,27 +996,27 @@ private:
|
||||||
void HandleSystemTime(const tN2kMsg &msg){
|
void HandleSystemTime(const tN2kMsg &msg){
|
||||||
unsigned char sid=-1;
|
unsigned char sid=-1;
|
||||||
uint16_t DaysSince1970=N2kUInt16NA;
|
uint16_t DaysSince1970=N2kUInt16NA;
|
||||||
double SecondsSinceMidnight=N2kDoubleNA;
|
double GpsTime=N2kDoubleNA;
|
||||||
tN2kTimeSource TimeSource;
|
tN2kTimeSource TimeSource;
|
||||||
|
|
||||||
if (! ParseN2kSystemTime(msg,sid,DaysSince1970,SecondsSinceMidnight,TimeSource)){
|
if (! ParseN2kSystemTime(msg,sid,DaysSince1970,GpsTime,TimeSource)){
|
||||||
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateDouble(boatData->SecondsSinceMidnight,SecondsSinceMidnight);
|
updateDouble(boatData->GpsTime,GpsTime);
|
||||||
if (DaysSince1970 != N2kUInt16NA) boatData->DaysSince1970->update(DaysSince1970,sourceId);
|
if (DaysSince1970 != N2kUInt16NA) boatData->GpsDate->update(DaysSince1970,sourceId);
|
||||||
if (boatData->DaysSince1970->isValid() && boatData->SecondsSinceMidnight->isValid()){
|
if (boatData->GpsDate->isValid() && boatData->GpsTime->isValid()){
|
||||||
tNMEA0183Msg nmeaMsg;
|
tNMEA0183Msg nmeaMsg;
|
||||||
nmeaMsg.Init("ZDA",talkerId);
|
nmeaMsg.Init("ZDA",talkerId);
|
||||||
char utc[7];
|
char utc[7];
|
||||||
double seconds=boatData->SecondsSinceMidnight->getData();
|
double seconds=boatData->GpsTime->getData();
|
||||||
int hours=floor(seconds/3600.0);
|
int hours=floor(seconds/3600.0);
|
||||||
int minutes=floor(seconds/60) - hours *60;
|
int minutes=floor(seconds/60) - hours *60;
|
||||||
int sec=floor(seconds)-60*minutes-3600*hours;
|
int sec=floor(seconds)-60*minutes-3600*hours;
|
||||||
snprintf(utc,7,"%02d%02d%02d",hours,minutes,sec);
|
snprintf(utc,7,"%02d%02d%02d",hours,minutes,sec);
|
||||||
nmeaMsg.AddStrField(utc);
|
nmeaMsg.AddStrField(utc);
|
||||||
tmElements_t timeParts;
|
tmElements_t timeParts;
|
||||||
tNMEA0183Msg::breakTime(tNMEA0183Msg::daysToTime_t(boatData->DaysSince1970->getData()),timeParts);
|
tNMEA0183Msg::breakTime(tNMEA0183Msg::daysToTime_t(boatData->GpsDate->getData()),timeParts);
|
||||||
nmeaMsg.AddUInt32Field(tNMEA0183Msg::GetDay(timeParts));
|
nmeaMsg.AddUInt32Field(tNMEA0183Msg::GetDay(timeParts));
|
||||||
nmeaMsg.AddUInt32Field(tNMEA0183Msg::GetMonth(timeParts));
|
nmeaMsg.AddUInt32Field(tNMEA0183Msg::GetMonth(timeParts));
|
||||||
nmeaMsg.AddUInt32Field(tNMEA0183Msg::GetYear(timeParts));
|
nmeaMsg.AddUInt32Field(tNMEA0183Msg::GetYear(timeParts));
|
||||||
|
@ -1036,14 +1036,14 @@ private:
|
||||||
|
|
||||||
void HandleTimeOffset(const tN2kMsg &msg){
|
void HandleTimeOffset(const tN2kMsg &msg){
|
||||||
uint16_t DaysSince1970 =N2kUInt16NA;
|
uint16_t DaysSince1970 =N2kUInt16NA;
|
||||||
double SecondsSinceMidnight=N2kDoubleNA;
|
double GpsTime=N2kDoubleNA;
|
||||||
int16_t LocalOffset=N2kInt16NA;
|
int16_t LocalOffset=N2kInt16NA;
|
||||||
if (!ParseN2kLocalOffset(msg,DaysSince1970,SecondsSinceMidnight,LocalOffset)){
|
if (!ParseN2kLocalOffset(msg,DaysSince1970,GpsTime,LocalOffset)){
|
||||||
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
LOG_DEBUG(GwLog::DEBUG,"unable to parse PGN %d",msg.PGN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateDouble(boatData->SecondsSinceMidnight,SecondsSinceMidnight);
|
updateDouble(boatData->GpsTime,GpsTime);
|
||||||
if (DaysSince1970 != N2kUInt16NA) boatData->DaysSince1970->update(DaysSince1970,sourceId);
|
if (DaysSince1970 != N2kUInt16NA) boatData->GpsDate->update(DaysSince1970,sourceId);
|
||||||
if (LocalOffset != N2kInt16NA) boatData->Timezone->update(LocalOffset,sourceId);
|
if (LocalOffset != N2kInt16NA) boatData->Timezone->update(LocalOffset,sourceId);
|
||||||
}
|
}
|
||||||
void HandleROT(const tN2kMsg &msg){
|
void HandleROT(const tN2kMsg &msg){
|
||||||
|
|
|
@ -163,7 +163,7 @@ void GwSocketServer::begin(){
|
||||||
}
|
}
|
||||||
server=new WiFiServer(config->getInt(config->serverPort),maxClients+1);
|
server=new WiFiServer(config->getInt(config->serverPort),maxClients+1);
|
||||||
server->begin();
|
server->begin();
|
||||||
logger->logString("Socket server created, port=%d",
|
LOG_DEBUG(GwLog::LOG,"Socket server created, port=%d",
|
||||||
config->getInt(config->serverPort));
|
config->getInt(config->serverPort));
|
||||||
MDNS.addService("_nmea-0183","_tcp",config->getInt(config->serverPort));
|
MDNS.addService("_nmea-0183","_tcp",config->getInt(config->serverPort));
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite)
|
||||||
|
|
||||||
if (client)
|
if (client)
|
||||||
{
|
{
|
||||||
logger->logString("new client connected from %s",
|
LOG_DEBUG(GwLog::LOG,"new client connected from %s",
|
||||||
client.remoteIP().toString().c_str());
|
client.remoteIP().toString().c_str());
|
||||||
fcntl(client.fd(), F_SETFL, O_NONBLOCK);
|
fcntl(client.fd(), F_SETFL, O_NONBLOCK);
|
||||||
bool canHandle = false;
|
bool canHandle = false;
|
||||||
|
@ -184,7 +184,7 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite)
|
||||||
if (!clients[i]->hasClient())
|
if (!clients[i]->hasClient())
|
||||||
{
|
{
|
||||||
clients[i]->setClient(wiFiClientPtr(new WiFiClient(client)));
|
clients[i]->setClient(wiFiClientPtr(new WiFiClient(client)));
|
||||||
logger->logString("set client as number %d", i);
|
LOG_DEBUG(GwLog::LOG,"set client as number %d", i);
|
||||||
canHandle = true;
|
canHandle = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -219,7 +219,7 @@ void GwSocketServer::loop(bool handleRead,bool handleWrite)
|
||||||
|
|
||||||
if (!client->client->connected())
|
if (!client->client->connected())
|
||||||
{
|
{
|
||||||
logger->logString("client %d disconnect %s", i, client->remoteIp.c_str());
|
LOG_DEBUG(GwLog::LOG,"client %d disconnect %s", i, client->remoteIp.c_str());
|
||||||
client->client->stop();
|
client->client->stop();
|
||||||
client->setClient(NULL);
|
client->setClient(NULL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){
|
||||||
this->fixedApPass=fixedApPass;
|
this->fixedApPass=fixedApPass;
|
||||||
}
|
}
|
||||||
void GwWifi::setup(){
|
void GwWifi::setup(){
|
||||||
logger->logString("Wifi setup");
|
LOG_DEBUG(GwLog::LOG,"Wifi setup");
|
||||||
|
|
||||||
IPAddress AP_local_ip(192, 168, 15, 1); // Static address for AP
|
IPAddress AP_local_ip(192, 168, 15, 1); // Static address for AP
|
||||||
IPAddress AP_gateway(192, 168, 15, 1);
|
IPAddress AP_gateway(192, 168, 15, 1);
|
||||||
|
@ -26,7 +26,7 @@ void GwWifi::setup(){
|
||||||
}
|
}
|
||||||
delay(100);
|
delay(100);
|
||||||
WiFi.softAPConfig(AP_local_ip, AP_gateway, AP_subnet);
|
WiFi.softAPConfig(AP_local_ip, AP_gateway, AP_subnet);
|
||||||
logger->logString("WifiAP created: ssid=%s,adress=%s",
|
LOG_DEBUG(GwLog::LOG,"WifiAP created: ssid=%s,adress=%s",
|
||||||
ssid,
|
ssid,
|
||||||
WiFi.softAPIP().toString().c_str()
|
WiFi.softAPIP().toString().c_str()
|
||||||
);
|
);
|
||||||
|
@ -34,7 +34,7 @@ void GwWifi::setup(){
|
||||||
lastApAccess=millis();
|
lastApAccess=millis();
|
||||||
apShutdownTime=config->getConfigItem(config->stopApTime)->asInt() * 60;
|
apShutdownTime=config->getConfigItem(config->stopApTime)->asInt() * 60;
|
||||||
if (apShutdownTime < 120 && apShutdownTime != 0) apShutdownTime=120; //min 2 minutes
|
if (apShutdownTime < 120 && apShutdownTime != 0) apShutdownTime=120; //min 2 minutes
|
||||||
logger->logString("GWWIFI: AP auto shutdown %s (%ds)",apShutdownTime> 0?"enabled":"disabled",apShutdownTime);
|
LOG_DEBUG(GwLog::LOG,"GWWIFI: AP auto shutdown %s (%ds)",apShutdownTime> 0?"enabled":"disabled",apShutdownTime);
|
||||||
apShutdownTime=apShutdownTime*1000; //ms
|
apShutdownTime=apShutdownTime*1000; //ms
|
||||||
clientIsConnected=false;
|
clientIsConnected=false;
|
||||||
connectInternal();
|
connectInternal();
|
||||||
|
@ -42,7 +42,7 @@ void GwWifi::setup(){
|
||||||
bool GwWifi::connectInternal(){
|
bool GwWifi::connectInternal(){
|
||||||
if (wifiClient->asBoolean()){
|
if (wifiClient->asBoolean()){
|
||||||
clientIsConnected=false;
|
clientIsConnected=false;
|
||||||
logger->logString("creating wifiClient ssid=%s",wifiSSID->asString().c_str());
|
LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str());
|
||||||
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
|
wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString());
|
||||||
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
|
LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt);
|
||||||
lastConnectStart=millis();
|
lastConnectStart=millis();
|
||||||
|
@ -76,7 +76,7 @@ void GwWifi::loop(){
|
||||||
lastApAccess=millis();
|
lastApAccess=millis();
|
||||||
}
|
}
|
||||||
if ((lastApAccess + apShutdownTime) < millis()){
|
if ((lastApAccess + apShutdownTime) < millis()){
|
||||||
logger->logString("GWWIFI: shutdown AP");
|
LOG_DEBUG(GwLog::LOG,"GWWIFI: shutdown AP");
|
||||||
WiFi.softAPdisconnect(true);
|
WiFi.softAPdisconnect(true);
|
||||||
apActive=false;
|
apActive=false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# Name, Type, SubType, Offset, Size, Flags
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000
|
||||||
otadata, data, ota, 0xe000, 0x2000,
|
otadata, data, ota, 0xe000, 0x2000,
|
||||||
app0, app, ota_0, 0x10000, 0x140000,
|
app0, app, ota_0, 0x10000, 0x1f0000,
|
||||||
app1, app, ota_1, 0x150000,0x130000,
|
app1, app, ota_1, 0x200000,0x1f0000,
|
||||||
nvs, data, nvs, 0x280000, 0x10000,
|
|
||||||
spiffs, data, spiffs, 0x290000,0x170000,
|
|
||||||
|
|
|
42
post.py
42
post.py
|
@ -1,7 +1,35 @@
|
||||||
Import("env", "projenv")
|
Import("env", "projenv")
|
||||||
import os
|
import os
|
||||||
|
import glob
|
||||||
|
import shutil
|
||||||
|
|
||||||
print("##post script running")
|
print("##post script running")
|
||||||
|
HDROFFSET=288
|
||||||
|
VERSIONOFFSET=16
|
||||||
|
NAMEOFFSET=48
|
||||||
|
MINSIZE=HDROFFSET+NAMEOFFSET+32
|
||||||
|
CHECKBYTES={
|
||||||
|
0: 0xe9, #image magic
|
||||||
|
288: 0x32, #app header magic
|
||||||
|
289: 0x54,
|
||||||
|
290: 0xcd,
|
||||||
|
291: 0xab
|
||||||
|
}
|
||||||
|
def getString(buffer,offset,len):
|
||||||
|
return buffer[offset:offset+len].rstrip(b'\0').decode('utf-8')
|
||||||
|
|
||||||
|
def getFirmwareInfo(imageFile):
|
||||||
|
with open(imageFile,"rb") as ih:
|
||||||
|
buffer=ih.read(MINSIZE)
|
||||||
|
if len(buffer) != MINSIZE:
|
||||||
|
raise Exception("invalid image file %s, to short",imageFile)
|
||||||
|
for k,v in CHECKBYTES.items():
|
||||||
|
if buffer[k] != v:
|
||||||
|
raise Exception("invalid magic in %s at %d, expected %d got %d"
|
||||||
|
%(imageFile,k,v,buffer[k]))
|
||||||
|
name=getString(buffer,HDROFFSET+NAMEOFFSET,32)
|
||||||
|
version=getString(buffer,HDROFFSET+VERSIONOFFSET,32)
|
||||||
|
return (name,version)
|
||||||
|
|
||||||
def post(source,target,env):
|
def post(source,target,env):
|
||||||
#print(env.Dump())
|
#print(env.Dump())
|
||||||
|
@ -10,6 +38,8 @@ def post(source,target,env):
|
||||||
base=env.subst("$PIOENV")
|
base=env.subst("$PIOENV")
|
||||||
appoffset=env.subst("$ESP32_APP_OFFSET")
|
appoffset=env.subst("$ESP32_APP_OFFSET")
|
||||||
firmware=env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
firmware=env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||||
|
(fwname,version)=getFirmwareInfo(firmware)
|
||||||
|
print("found fwname=%s, fwversion=%s"%(fwname,version))
|
||||||
python=env.subst("$PYTHONEXE")
|
python=env.subst("$PYTHONEXE")
|
||||||
print("base=%s,esptool=%s,appoffset=%s,uploaderflags=%s"%(base,esptool,appoffset,uploaderflags))
|
print("base=%s,esptool=%s,appoffset=%s,uploaderflags=%s"%(base,esptool,appoffset,uploaderflags))
|
||||||
uploadparts=uploaderflags.split(" ")
|
uploadparts=uploaderflags.split(" ")
|
||||||
|
@ -23,7 +53,17 @@ def post(source,target,env):
|
||||||
print("file %s for combine not found"%uploadfiles[i])
|
print("file %s for combine not found"%uploadfiles[i])
|
||||||
return
|
return
|
||||||
offset=uploadfiles[0]
|
offset=uploadfiles[0]
|
||||||
outfile=os.path.join(env.subst("$BUILD_DIR"),base+"-all.bin")
|
#cleanup old versioned files
|
||||||
|
outdir=env.subst("$BUILD_DIR")
|
||||||
|
for f in glob.glob(os.path.join(outdir,base+"*.bin")):
|
||||||
|
print("removing old file %s"%f)
|
||||||
|
os.unlink(f)
|
||||||
|
ofversion=''
|
||||||
|
if not version.startswith('dev'):
|
||||||
|
ofversion="-"+version
|
||||||
|
versionedFile=os.path.join(outdir,"%s%s-update.bin"%(base,ofversion))
|
||||||
|
shutil.copyfile(firmware,versionedFile)
|
||||||
|
outfile=os.path.join(outdir,"%s%s-all.bin"%(base,ofversion))
|
||||||
cmd=[python,esptool,"--chip","esp32","merge_bin","--target-offset",offset,"-o",outfile]
|
cmd=[python,esptool,"--chip","esp32","merge_bin","--target-offset",offset,"-o",outfile]
|
||||||
cmd+=uploadfiles
|
cmd+=uploadfiles
|
||||||
cmd+=[appoffset,firmware]
|
cmd+=[appoffset,firmware]
|
||||||
|
|
43
src/main.cpp
43
src/main.cpp
|
@ -11,21 +11,7 @@
|
||||||
License along with this library; if not, write to the Free Software
|
License along with this library; if not, write to the Free Software
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
#define GWSTR(x) #x
|
#include "GwAppInfo.h"
|
||||||
#define GWSTRINGIFY(x) GWSTR(x)
|
|
||||||
#ifdef GWRELEASEVERSION
|
|
||||||
#define VERSION GWSTRINGIFY(GWRELEASEVERSION)
|
|
||||||
#define LOGLEVEL GwLog::ERROR
|
|
||||||
#endif
|
|
||||||
#ifdef GWDEVVERSION
|
|
||||||
#define VERSION GWSTRINGIFY(GWDEVVERSION)
|
|
||||||
#define LOGLEVEL GwLog::DEBUG
|
|
||||||
#endif
|
|
||||||
#ifndef VERSION
|
|
||||||
#define VERSION "0.7.0"
|
|
||||||
#define LOGLEVEL GwLog::DEBUG
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// #define GW_MESSAGE_DEBUG_ENABLED
|
// #define GW_MESSAGE_DEBUG_ENABLED
|
||||||
//#define FALLBACK_SERIAL
|
//#define FALLBACK_SERIAL
|
||||||
const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||||
|
@ -76,9 +62,6 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||||
#include "GwStatistics.h"
|
#include "GwStatistics.h"
|
||||||
#include "GwUpdate.h"
|
#include "GwUpdate.h"
|
||||||
|
|
||||||
#ifndef FIRMWARE_TYPE
|
|
||||||
#define FIRMWARE_TYPE PIO_ENV_BUILD
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//NMEA message channels
|
//NMEA message channels
|
||||||
#define N2K_CHANNEL_ID 0
|
#define N2K_CHANNEL_ID 0
|
||||||
|
@ -95,7 +78,7 @@ const unsigned long HEAP_REPORT_TIME=2000; //set to 0 to disable heap reporting
|
||||||
#define _impl_PASTE(a,b) a##b
|
#define _impl_PASTE(a,b) a##b
|
||||||
#define _impl_CASSERT_LINE(predicate, line) typedef char _impl_PASTE(assertion_failed_CASSERT_,line)[(predicate)?1:-1];
|
#define _impl_CASSERT_LINE(predicate, line) typedef char _impl_PASTE(assertion_failed_CASSERT_,line)[(predicate)?1:-1];
|
||||||
//assert length of firmware name and version
|
//assert length of firmware name and version
|
||||||
CASSERT(strlen(GWSTRINGIFY(FIRMWARE_TYPE)) <= 32, "environment name (FIRMWARE_TYPE) must not exceed 32 chars");
|
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(VERSION) <= 32, "VERSION must not exceed 32 chars");
|
||||||
//https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/app_image_format.html
|
//https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/app_image_format.html
|
||||||
//and removed the bugs in the doc...
|
//and removed the bugs in the doc...
|
||||||
|
@ -104,7 +87,7 @@ __attribute__((section(".rodata_custom_desc"))) esp_app_desc_t custom_app_desc =
|
||||||
1,
|
1,
|
||||||
{0,0},
|
{0,0},
|
||||||
VERSION,
|
VERSION,
|
||||||
GWSTRINGIFY(FIRMWARE_TYPE),
|
FIRMWARE_TYPE,
|
||||||
"00:00:00",
|
"00:00:00",
|
||||||
"2021/12/13",
|
"2021/12/13",
|
||||||
"0000",
|
"0000",
|
||||||
|
@ -113,7 +96,7 @@ __attribute__((section(".rodata_custom_desc"))) esp_app_desc_t custom_app_desc =
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
String firmwareType(GWSTRINGIFY(FIRMWARE_TYPE));
|
String firmwareType(FIRMWARE_TYPE);
|
||||||
|
|
||||||
typedef std::map<String,String> StringMap;
|
typedef std::map<String,String> StringMap;
|
||||||
|
|
||||||
|
@ -412,12 +395,20 @@ public:
|
||||||
virtual void getBoatDataValues(int numValues,BoatValue **list){
|
virtual void getBoatDataValues(int numValues,BoatValue **list){
|
||||||
for (int i=0;i<numValues;i++){
|
for (int i=0;i<numValues;i++){
|
||||||
GwBoatItemBase *item=boatData.getBase(list[i]->getName());
|
GwBoatItemBase *item=boatData.getBase(list[i]->getName());
|
||||||
|
list[i]->changed=false;
|
||||||
if (item){
|
if (item){
|
||||||
list[i]->valid=item->isValid();
|
bool newValid=item->isValid();
|
||||||
if (list[i]->valid) list[i]->value=item->getDoubleValue();
|
if (newValid != list[i]->valid) list[i]->changed=true;
|
||||||
|
list[i]->valid=newValid;
|
||||||
|
if (newValid){
|
||||||
|
double newValue=item->getDoubleValue();
|
||||||
|
if (newValue != list[i]->value) list[i]->changed=true;
|
||||||
|
list[i]->value=newValue;
|
||||||
|
}
|
||||||
list[i]->setFormat(item->getFormat());
|
list[i]->setFormat(item->getFormat());
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
if (list[i]->valid) list[i]->changed=true;
|
||||||
list[i]->valid=false;
|
list[i]->valid=false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -604,7 +595,7 @@ protected:
|
||||||
bool rt = config.updateValue(it->first, it->second);
|
bool rt = config.updateValue(it->first, it->second);
|
||||||
if (!rt)
|
if (!rt)
|
||||||
{
|
{
|
||||||
logger.logString("ERR: unable to update %s to %s", it->first.c_str(), it->second.c_str());
|
logger.logDebug(GwLog::ERROR,"ERR: unable to update %s to %s", it->first.c_str(), it->second.c_str());
|
||||||
ok = false;
|
ok = false;
|
||||||
error += it->first;
|
error += it->first;
|
||||||
error += "=";
|
error += "=";
|
||||||
|
@ -615,7 +606,7 @@ protected:
|
||||||
if (ok)
|
if (ok)
|
||||||
{
|
{
|
||||||
result = JSON_OK;
|
result = JSON_OK;
|
||||||
logger.logString("update config and restart");
|
logger.logDebug(GwLog::ERROR,"update config and restart");
|
||||||
config.saveConfig();
|
config.saveConfig();
|
||||||
delayedRestart();
|
delayedRestart();
|
||||||
}
|
}
|
||||||
|
@ -643,7 +634,7 @@ protected:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
config.reset(true);
|
config.reset(true);
|
||||||
logger.logString("reset config, restart");
|
logger.logDebug(GwLog::ERROR,"reset config, restart");
|
||||||
result = JSON_OK;
|
result = JSON_OK;
|
||||||
delayedRestart();
|
delayedRestart();
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -0,0 +1,237 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
import tkinter.font as tkFont
|
||||||
|
|
||||||
|
import os
|
||||||
|
import serial.tools.list_ports
|
||||||
|
from tkinter import filedialog as FileDialog
|
||||||
|
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
VERSION="1.0, esptool 3.2"
|
||||||
|
|
||||||
|
oldprint=builtins.print
|
||||||
|
def print(*args, **kwargs):
|
||||||
|
app.addText(*args,**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
builtins.print=print
|
||||||
|
import esptool
|
||||||
|
|
||||||
|
class App:
|
||||||
|
def __init__(self, root):
|
||||||
|
root.title("ESP32 NMEA2000 Flash Tool")
|
||||||
|
root.geometry("800x600")
|
||||||
|
root.resizable(width=True, height=True)
|
||||||
|
root.configure(background='lightgrey')
|
||||||
|
root.columnconfigure(0, weight=1)
|
||||||
|
root.rowconfigure(0, weight=1)
|
||||||
|
frame=tk.Frame(root)
|
||||||
|
row=0
|
||||||
|
frame.grid(row=0,column=0,sticky='news')
|
||||||
|
#frame.configure(background='lightblue')
|
||||||
|
frame.columnconfigure(0,weight=1)
|
||||||
|
frame.columnconfigure(1, weight=3)
|
||||||
|
tk.Label(frame,text="ESP32 NMEA2000 Flash Tool").grid(row=row,column=0,columnspan=2,sticky='ew')
|
||||||
|
row+=1
|
||||||
|
tk.Label(frame, text=VERSION).grid(row=row,column=0,columnspan=2,sticky="ew",pady=10)
|
||||||
|
row+=1
|
||||||
|
self.mode=tk.IntVar()
|
||||||
|
self.mode.set(1)
|
||||||
|
rdFrame=tk.Frame(frame)
|
||||||
|
rdFrame.grid(row=row,column=1,sticky="ew",pady=20)
|
||||||
|
tk.Radiobutton(rdFrame,text="Initial Flash",value=1,variable=self.mode,command=self.changeMode).grid(row=0,column=0)
|
||||||
|
tk.Radiobutton(rdFrame, text="Update Flash", value=2, variable=self.mode,command=self.changeMode).grid(row=0,column=1)
|
||||||
|
row+=1
|
||||||
|
tk.Label(frame, text="Com Port").grid(row=row,column=0,sticky='ew')
|
||||||
|
self.port=ttk.Combobox(frame)
|
||||||
|
self.port.grid(row=row,column=1,sticky="ew")
|
||||||
|
row+=1
|
||||||
|
tk.Label(frame,text="Select Firmware").grid(row=row,column=0,sticky='ew')
|
||||||
|
self.filename=tk.StringVar()
|
||||||
|
fn=tk.Entry(frame,textvariable=self.filename)
|
||||||
|
fn.grid(row=row,column=1,sticky='ew')
|
||||||
|
fn.bind("<1>",self.fileDialog)
|
||||||
|
row+=1
|
||||||
|
self.fileInfo=tk.StringVar()
|
||||||
|
tk.Label(frame,textvariable=self.fileInfo).grid(row=row,column=0,columnspan=2,sticky="ew")
|
||||||
|
row+=1
|
||||||
|
self.flashInfo=tk.StringVar()
|
||||||
|
self.flashInfo.set("Address 0x1000")
|
||||||
|
tk.Label(frame,textvariable=self.flashInfo).grid(row=row,column=0,columnspan=2,sticky='ew',pady=10)
|
||||||
|
row+=1
|
||||||
|
btFrame=tk.Frame(frame)
|
||||||
|
btFrame.grid(row=row,column=0,columnspan=2,pady=15)
|
||||||
|
self.actionButtons=[]
|
||||||
|
bt=tk.Button(btFrame,text="Check",command=self.buttonCheck)
|
||||||
|
bt.grid(row=0,column=0)
|
||||||
|
self.actionButtons.append(bt)
|
||||||
|
bt=tk.Button(btFrame, text="Flash", command=self.buttonFlash)
|
||||||
|
bt.grid(row=0, column=1)
|
||||||
|
self.actionButtons.append(bt)
|
||||||
|
self.cancelButton=tk.Button(btFrame,text="Cancel",state=tk.DISABLED,command=self.buttonCancel)
|
||||||
|
self.cancelButton.grid(row=0,column=2)
|
||||||
|
row+=1
|
||||||
|
self.text_widget = tk.Text(frame)
|
||||||
|
frame.rowconfigure(row,weight=1)
|
||||||
|
self.text_widget.grid(row=row,column=0,columnspan=2,sticky='news')
|
||||||
|
self.readDevices()
|
||||||
|
self.interrupt=False
|
||||||
|
|
||||||
|
def updateFlashInfo(self):
|
||||||
|
if self.mode.get() == 1:
|
||||||
|
#full
|
||||||
|
self.flashInfo.set("Address 0x1000")
|
||||||
|
else:
|
||||||
|
self.flashInfo.set("Erase(otadata): 0xe000...0xffff, Address 0x10000")
|
||||||
|
def changeMode(self):
|
||||||
|
m=self.mode.get()
|
||||||
|
self.updateFlashInfo()
|
||||||
|
self.filename.set('')
|
||||||
|
self.fileInfo.set('')
|
||||||
|
def fileDialog(self,ev):
|
||||||
|
fn=FileDialog.askopenfilename()
|
||||||
|
if fn:
|
||||||
|
self.filename.set(fn)
|
||||||
|
info=self.checkImageFile(fn,self.mode.get() == 1)
|
||||||
|
if info['error']:
|
||||||
|
self.fileInfo.set("***ERROR: %s"%info['info'])
|
||||||
|
else:
|
||||||
|
self.fileInfo.set(info['info'])
|
||||||
|
def readDevices(self):
|
||||||
|
self.serialDevices=[]
|
||||||
|
names=[]
|
||||||
|
for dev in serial.tools.list_ports.comports(False):
|
||||||
|
self.serialDevices.append(dev.device)
|
||||||
|
if dev.description != 'n/a':
|
||||||
|
label=dev.description+"("+dev.device+")"
|
||||||
|
else:
|
||||||
|
label=dev.name+"("+dev.device+")"
|
||||||
|
names.append(label)
|
||||||
|
self.port.configure(values=names)
|
||||||
|
def addText(self,*args,**kwargs):
|
||||||
|
first=True
|
||||||
|
for k in args:
|
||||||
|
self.text_widget.insert(tk.END,k)
|
||||||
|
if not first:
|
||||||
|
self.text_widget.insert(tk.END, ',')
|
||||||
|
first=False
|
||||||
|
if kwargs.get('end') is None:
|
||||||
|
self.text_widget.insert(tk.END,"\n")
|
||||||
|
else:
|
||||||
|
self.text_widget.insert(tk.END,kwargs.get('end'))
|
||||||
|
self.text_widget.see('end')
|
||||||
|
root.update()
|
||||||
|
if self.interrupt:
|
||||||
|
self.interrupt=False
|
||||||
|
raise Exception("User cancel")
|
||||||
|
|
||||||
|
FULLOFFSET=61440
|
||||||
|
HDROFFSET = 288
|
||||||
|
VERSIONOFFSET = 16
|
||||||
|
NAMEOFFSET = 48
|
||||||
|
MINSIZE = HDROFFSET + NAMEOFFSET + 32
|
||||||
|
CHECKBYTES = {
|
||||||
|
0: 0xe9, # image magic
|
||||||
|
288: 0x32, # app header magic
|
||||||
|
289: 0x54,
|
||||||
|
290: 0xcd,
|
||||||
|
291: 0xab
|
||||||
|
}
|
||||||
|
|
||||||
|
def getString(self,buffer, offset, len):
|
||||||
|
return buffer[offset:offset + len].rstrip(b'\0').decode('utf-8')
|
||||||
|
|
||||||
|
def getFirmwareInfo(self,ih,imageFile,offset):
|
||||||
|
buffer = ih.read(self.MINSIZE)
|
||||||
|
if len(buffer) != self.MINSIZE:
|
||||||
|
return self.setErr("invalid image file %s, to short"%imageFile)
|
||||||
|
for k, v in self.CHECKBYTES.items():
|
||||||
|
if buffer[k] != v:
|
||||||
|
return self.setErr("invalid magic at %d, expected %d got %d"
|
||||||
|
% (k+offset, v, buffer[k]))
|
||||||
|
name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32)
|
||||||
|
version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32)
|
||||||
|
return {'error':False,'info':"%s:%s"%(name,version)}
|
||||||
|
|
||||||
|
def setErr(self,err):
|
||||||
|
return {'error':True,'info':err}
|
||||||
|
def checkImageFile(self,filename,isFull):
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
return self.setErr("file %s not found"%filename)
|
||||||
|
with open(filename,"rb") as fh:
|
||||||
|
offset=0
|
||||||
|
if isFull:
|
||||||
|
b=fh.read(1)
|
||||||
|
if len(b) != 1:
|
||||||
|
return self.setErr("unable to read header")
|
||||||
|
if b[0] != 0xe9:
|
||||||
|
return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%b[0])
|
||||||
|
st=fh.seek(self.FULLOFFSET)
|
||||||
|
offset=self.FULLOFFSET
|
||||||
|
return self.getFirmwareInfo(fh,filename,offset)
|
||||||
|
|
||||||
|
def runCheck(self):
|
||||||
|
self.text_widget.delete("1.0", "end")
|
||||||
|
idx = self.port.current()
|
||||||
|
isFull = self.mode.get() == 1
|
||||||
|
if idx < 0:
|
||||||
|
self.addText("ERROR: no com port selected")
|
||||||
|
return
|
||||||
|
port = self.serialDevices[idx]
|
||||||
|
fn = self.filename.get()
|
||||||
|
if fn is None or fn == '':
|
||||||
|
self.addText("ERROR: no filename selected")
|
||||||
|
return
|
||||||
|
info = self.checkImageFile(fn, isFull)
|
||||||
|
if info['error']:
|
||||||
|
print("ERROR: %s" % info['info'])
|
||||||
|
return
|
||||||
|
return {'port':port,'isFull':isFull}
|
||||||
|
|
||||||
|
def runEspTool(self,command):
|
||||||
|
for b in self.actionButtons:
|
||||||
|
b.configure(state=tk.DISABLED)
|
||||||
|
self.cancelButton.configure(state=tk.NORMAL)
|
||||||
|
print("run esptool: %s" % " ".join(command))
|
||||||
|
root.update()
|
||||||
|
root.update_idletasks()
|
||||||
|
try:
|
||||||
|
esptool.main(command)
|
||||||
|
print("esptool done")
|
||||||
|
except Exception as e:
|
||||||
|
print("Exception in esptool %s" % e)
|
||||||
|
for b in self.actionButtons:
|
||||||
|
b.configure(state=tk.NORMAL)
|
||||||
|
self.cancelButton.configure(state=tk.DISABLED)
|
||||||
|
def buttonCheck(self):
|
||||||
|
param = self.runCheck()
|
||||||
|
if not param:
|
||||||
|
return
|
||||||
|
print("Settings OK")
|
||||||
|
command = ['--chip', 'ESP32', '--port', param['port'], 'chip_id']
|
||||||
|
self.runEspTool(command)
|
||||||
|
|
||||||
|
def buttonFlash(self):
|
||||||
|
param=self.runCheck()
|
||||||
|
if not param:
|
||||||
|
return
|
||||||
|
if param['isFull']:
|
||||||
|
command=['--chip','ESP32','--port',param['port'],'write_flash','0x1000',self.filename.get()]
|
||||||
|
self.runEspTool(command)
|
||||||
|
else:
|
||||||
|
command=['--chip','ESP32','--port',param['port'],'erase_region','0xe000','0x2000']
|
||||||
|
self.runEspTool(command)
|
||||||
|
command = ['--chip', 'ESP32', '--port', param['port'], 'write_flash', '0x10000', self.filename.get()]
|
||||||
|
self.runEspTool(command)
|
||||||
|
|
||||||
|
|
||||||
|
def buttonCancel(self):
|
||||||
|
self.interrupt=True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
root = tk.Tk()
|
||||||
|
app = App(root)
|
||||||
|
root.mainloop()
|
|
@ -0,0 +1,29 @@
|
||||||
|
How to build the bundled esp tool for windows
|
||||||
|
=============================================
|
||||||
|
(1) install python 3 on windows, add to path
|
||||||
|
(2) pip install pyinstaller
|
||||||
|
(3) create an empty dir, cd there
|
||||||
|
(4) get esptool.py (either pip install esptool, search for it or download from
|
||||||
|
https://github.com/espressif/esptool - just esptool.py) or copy it from here
|
||||||
|
(5) run: pyinstaller -F esptool.py
|
||||||
|
esptool.exe in dist\esptool
|
||||||
|
|
||||||
|
How to build the bundled flashtool.exe (on windows)
|
||||||
|
===================================================
|
||||||
|
(1) install python 3, add to path
|
||||||
|
(2) pip install pyinstaller
|
||||||
|
(3) pip install pyserial
|
||||||
|
(4) in this directory:
|
||||||
|
pyinstaller -F flashtool.py
|
||||||
|
will create flashtool.exe in dist
|
||||||
|
|
||||||
|
|
||||||
|
How to run flashtool on Linux
|
||||||
|
=============================
|
||||||
|
(1) have python 3 installed
|
||||||
|
(2) install python3-serial as package __or__ sudo pip3 install pyserial
|
||||||
|
(3) install python3-tk as package __or__ sudo pip3 install tkinter
|
||||||
|
(4) ./flashtool.py
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -116,12 +116,7 @@
|
||||||
"default": "esp32nmea2k",
|
"default": "esp32nmea2k",
|
||||||
"check": "checkApPass",
|
"check": "checkApPass",
|
||||||
"description": "set the password for the Wifi access point",
|
"description": "set the password for the Wifi access point",
|
||||||
"category": "system",
|
"category": "system"
|
||||||
"capabilities": {
|
|
||||||
"hardwareReset": [
|
|
||||||
"true"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "useAdminPass",
|
"name": "useAdminPass",
|
||||||
|
@ -136,12 +131,7 @@
|
||||||
"default": "esp32admin",
|
"default": "esp32admin",
|
||||||
"check": "checkAdminPass",
|
"check": "checkAdminPass",
|
||||||
"description": "set the password for config modifications",
|
"description": "set the password for config modifications",
|
||||||
"category": "system",
|
"category": "system"
|
||||||
"capabilities": {
|
|
||||||
"hardwareReset": [
|
|
||||||
"true"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "showInvalidData",
|
"name": "showInvalidData",
|
||||||
|
@ -455,14 +445,6 @@
|
||||||
"description": "connect to an external WIFI network",
|
"description": "connect to an external WIFI network",
|
||||||
"category": "wifi client"
|
"category": "wifi client"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "wifiPass",
|
|
||||||
"label": "wifi client password",
|
|
||||||
"type": "password",
|
|
||||||
"default": "",
|
|
||||||
"description": "the password for an external WIFI network",
|
|
||||||
"category": "wifi client"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "wifiSSID",
|
"name": "wifiSSID",
|
||||||
"label": "wifi client SSID",
|
"label": "wifi client SSID",
|
||||||
|
@ -472,6 +454,14 @@
|
||||||
"description": "the SSID for an external WIFI network",
|
"description": "the SSID for an external WIFI network",
|
||||||
"category": "wifi client"
|
"category": "wifi client"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "wifiPass",
|
||||||
|
"label": "wifi client password",
|
||||||
|
"type": "password",
|
||||||
|
"default": "",
|
||||||
|
"description": "the password for an external WIFI network",
|
||||||
|
"category": "wifi client"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "XDR1",
|
"name": "XDR1",
|
||||||
"label": "XDR1",
|
"label": "XDR1",
|
||||||
|
|
15
web/index.js
15
web/index.js
|
@ -1448,11 +1448,12 @@ function updateDashboard(data) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function uploadBin(){
|
function uploadBin(ev){
|
||||||
let el=document.getElementById("uploadFile");
|
let el=document.getElementById("uploadFile");
|
||||||
let progressEl=document.getElementById("uploadDone");
|
let progressEl=document.getElementById("uploadDone");
|
||||||
if (! el) return;
|
if (! el) return;
|
||||||
if ( el.files.length < 1) return;
|
if ( el.files.length < 1) return;
|
||||||
|
ev.target.disabled=true;
|
||||||
let file=el.files[0];
|
let file=el.files[0];
|
||||||
checkImageFile(file)
|
checkImageFile(file)
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
|
@ -1473,12 +1474,16 @@ function uploadBin(){
|
||||||
confirmText += "version in image: " + result.version;
|
confirmText += "version in image: " + result.version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!confirm(confirmText)) return;
|
if (!confirm(confirmText)) {
|
||||||
|
ev.target.disabled=false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
ensurePass()
|
ensurePass()
|
||||||
.then(function (hash) {
|
.then(function (hash) {
|
||||||
let len = file.size;
|
let len = file.size;
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.onloadend = function () {
|
req.onloadend = function () {
|
||||||
|
ev.target.disabled=false;
|
||||||
let result = "unknown error";
|
let result = "unknown error";
|
||||||
try {
|
try {
|
||||||
let jresult = JSON.parse(req.responseText);
|
let jresult = JSON.parse(req.responseText);
|
||||||
|
@ -1504,6 +1509,7 @@ function uploadBin(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.onerror = function (e) {
|
req.onerror = function (e) {
|
||||||
|
ev.target.disabled=false;
|
||||||
alert("unable to upload: " + e);
|
alert("unable to upload: " + e);
|
||||||
}
|
}
|
||||||
if (progressEl) {
|
if (progressEl) {
|
||||||
|
@ -1520,10 +1526,13 @@ function uploadBin(){
|
||||||
req.open("POST", '/api/update?_hash=' + encodeURIComponent(hash));
|
req.open("POST", '/api/update?_hash=' + encodeURIComponent(hash));
|
||||||
req.send(formData);
|
req.send(formData);
|
||||||
})
|
})
|
||||||
.catch(function (e) { });
|
.catch(function (e) {
|
||||||
|
ev.target.disabled=false;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
alert("This file is an invalid image file:\n" + e);
|
alert("This file is an invalid image file:\n" + e);
|
||||||
|
ev.target.disabled=false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let HDROFFSET=288;
|
let HDROFFSET=288;
|
||||||
|
|
Loading…
Reference in New Issue