Skip to content

N2K and NMEA0183 Wifi Server for ESP32 exposing http, tcp and udp services.

Notifications You must be signed in to change notification settings

ieb/N2KNMEA0183Wifi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

99 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

N2K and NMEA0183 Wifi server exposing http, tcp and udp services.

  • Runs on a ESP32. (Currently targetting ESP32-C2 Mini)
  • Connects to a NMEA2000 bus using the ESP CAN device and a CAN Driver as a CAN Node.
  • Parses NMEA2000 messages and stores them with history.
  • Emits NMEA0183 messages on tcp and udp.
  • Emits SeaSmart messages on tcp with PGN filtering.
  • Exposes NMEA0183 and SeaSmart streams over http (chunked encoding)
  • Exposes a http admin interface for configuration.
  • Runs either as an WiFi station or as an Access Point.

UI

There are 4 UIs available for use with this firware 3 are web based, and one is a Java Swing UI for Kindle 4 Paperwhites.

Hardware

ESP32-C3 board

ToDo

  • Setup Wifi
  • Setup MDNS registration
  • Implement webserver
  • Serve files from Flash (SPIFFS)
  • Implement data api /api/data/ where n == 0-10.
  • Expose set of standard CAN messages on /api/data
  • Implement simple UI in Javascript hosted on SPIFFS, targetting eink and normal browsers (based on SignalK eink)
  • Implement admin interface.
  • Support configuration and calibration via web browser.
  • Implement TCP server
  • Implement NMEA0183 bridge
  • Reduce Wifi power to 2dbm, also helps avoid can brownouts and hangs.
  • Test NMEA0183 with NKE, Navionics and NMea android apps.
  • Implement UDP server
  • Implement WebSocket Server
  • [-] Wire Store into NMEA0183 outputs
  • Expose message stream over websockets
  • Emit NMEA0183 messages generated from calculations
  • Verify performance messages
  • Remove N2KCollector
  • Emit data for websockets in raw form tbd.
  • Emit raw pgn messages in some form.
  • Port in web apps from Electron NMEA App in ui/v2
  • Fix messages display in v2 ui
  • Apply eslint
  • Convert to installable PWA
  • [-] Support ESP32 on https for PWA Too much effort, AsyncTCP support not there. Workaround: allow insecure content on localhost and load service worker from localhost.
  • Implement service worker cache
  • Implement Admin Login without relying on Browser, as BasicAuth popups in ServciceWorkers dont work offlin. On fixing found out: Explicitly setting headers works, but service workers use preflight on GET urls and are stricter about the cors headers than a direct fetch from a window context. Username and password are now stored in sessionStorage and logout works.
  • Urls not easy to use to nav (Workaround, open in browser, change url, open in app)
  • Add ability to set the server url for data
  • Add day night themes.
  • Fix menu system.
  • Add can debug view to v2 ui.
  • Replace layout editing and storage on esp32 with localstorage
  • Add PWA for LifePo4 battery management, standalone PWA over BLE.
  • Support upload and download of layouts for local editing.
  • Verify target VMG down wind.
  • Support debugging store behavior in debug view to verify polar and calculations.
  • Load default layouts over http.
  • Persist history in local storage
  • Port eink displays
  • Deprecate WebSockets as they are unsabled and cause segv in the hardware.
  • Implement http streams with SeaSmart format on and same on tcp, with filtering.
  • Implement bridge to JBD BMS over TTL/Uart with isolation and expose BMS registeres in custom PGNs.
  • Port BMS UI fro BLE to Http streams
  • Design ESP32-C3 board
  • Build and test new boards.
  • Update BMS app to use streaming http api
  • Add gitsha1 to firmware, press h to see on serial
  • Bench tested E2E with BMS simulator
  • Backport BMS seasmart stream handler to main ui
  • Fixed admin ui bug, buttons without type=button cause form submissions.
  • Fixed processing of temperature messages PGN 130312 and PGN 127506 battery status.
  • Implement a workable UI that can be used on a Android phone to quickly see the status of the BMS.
  • Http streams appear to be not cleaned up correctly, according to the status information - checked, working as expected.
  • Split the UIs out into seperate git repos.
  • Test BLE UI on ChromeOS.
  • Log BMS data to flash

HTTP APIs

GET /api/fs.json - admin only

lists the filesystem as json

POST /api/fs.json - admin only

Filesystem operations.

  • op=delete deletes the file defined by path
  • op=upload multipart, uploads the first file to the location defined by the path param, limited by filesystemspace.

GET /api/store

Gets the store from the ESP32 in csv format, see code for details on the values.

GET /api/devices

Gets a text version of the devices on the bus

GET /api/status.json - admin only

Gets the status of filesystem and heap

POST /api/reboot.json - admin only

Reboots the ESP32.

GET /config.txt - admin only

Gets the congiuration file

GET /**

gets the file from the filesystem, query params are ignored.

Various simulators for both the http api and the TCP service can be found in simulators/

TCP/UDP

Runs on 10110 and emits NMEA0183 sentences, see lib/NMEA0183N2KMessages.h for a list of messages, will switch to SeaSmart if the client sends a PGN filter request message, eg as below which filters the stream to only those PGNs in the message. Once in SeaSmart mode, the TCP stream will not revert to standard NMEA0183 for that client. Some PGNs are filtered by default to avoid over load (eg high volume proprietary Raymarine pilot updates)

 $PCDCM,1,7,127489,127505,127488,128259,129026,130312,128267*77

WebSocket

This was deprecated since there are bugs in the ESP32AsyncWebServer code that cause the ESP to crash when more than 1 client connects.

HTTP Stream

/api/seasmart streams SeaSmart sentences chunked encoded. Clients should read this as a ReadableStream since the http stream will continue forever. Filtering of pgns is available via by setting the pgns query parameter to a list of pgns to be filtered on.

/api/nmea0183 streams NMEA0183 sentences chunked encoded.

NMEA183 PCDIN aka Seasmart /ws/seasmart - implemented

http://www.seasmart.net/pdf/SeaSmart_HTTP_Protocol_RevG_043012.pdf

$PCDIN,01F119,00000000,0F,2AAF00D1067414FF*59
                                           ^^ checksum
                          ^^^^^^^^^^^^^^^^ data (whole message)
                       ^^ source
              ^^^^^^^^ Hex time
       ^^^^^^ PGN
^^^^^^ NMEA0183 id

Decoding hex time is done as

((parseInt(value,32)/1024)+1262304000)*1000 

result in ms since 1/1/1970. No idea what base 32 looks like, but tests don't make sense. Should probably avoid, however it does have the advantage of being able to mix with NMEA0183 traffic provided the bandwidth is enough. Looks like it was designed to send over http.

There is also a command protocol, PCDIC

It is unlikely that I want to use applications supporting this protocol, but it is used by a few. D

Seasmart compact, can be expanded by the client into seasmart by adding the missing fields.

01F119,0F,2AAF00D1067414FF
          ^^^^^^^^^^^^^^^^ base64 data
      ^^ source
^^^^^^ PGN

Other formats that were tried and dropped while using WebSockets

Candump version 3 modified, /ws/candump3m - not implemented yet

89f10d16#01f997fc3cfeffff\n
         ^^^^^^^^^^^^^^^^ data
^^^^^^^^ can id

Bare minimim, should be easy to covert into candump3 on the client, needs access to the raw stream before fastpackets are handled, or needs to reconstruct fast packets where the length is > 8.

Candump version 3, /ws/candump3 - implemented

(1713293415.384000) slcan0 89f10d16#01f997fc3cfeffff
                                    ^^^^^^^^^^^^^^^^ frame
                           ^^^^^^^^ can id
                    ^^^^^^ device
^^^^^^^^^^^^^^^^^^^ time in seconds

Very low cost to output, but high volume and the frame has to be processed for fast packets in the client.

Yacht Devces Raw /ws/raw - no plans to implement

19F51323 01 02<CR><LF>
         ^^^^^ frame data hex space separated 
^^^^^^^^ canID

17:33:21.107 R 19F51323 01 2F 30 70 00 2F 30 70
may also be prefixed by a timestamp and direction

Very low cost to output, but high volume and the frame has to be processed for fast packets in the client. Senders need to slice up the data, PGN 126720 (proprietary fast packet) gets special treatment why tbd.

This is close or the same as the log files produced by Raymarine e7 MFDs and other devices.

NMEA183 MXPGN /ws/nmea0183 - not implemented

https://opencpn.org/wiki/dokuwiki/lib/exe/fetch.php?media=opencpn:software:mxpgn_sentence.pdf

$MXPGN,01F801,2801,C1308AC40C5DE343*19
                                    ^^ checksum
                   ^^^^^^^^^^^^^^^^ data (frame only, since lenght limited to 8 max)
                ^^ src
              ^^ data length + priority encoded 
       ^^^^^^ PGN
^^^^^^ NMEA0183 id

data length bits 28 -> hex, padded to 8 eg  
0b00101000
      ^^^^ DLC  1-8 length or 9-15 class 2 transmission ID, length always 8 in this case.
   ^^^ priority eg 2
  ^ send/rec eg rec

NMEA184 iKonvert /ws/nmea0183 - not supported

!PDGY,126992,3,2,255,0.563,d2009e45b3b8821d*23
                                            ^^ checksum
                           ^^^^^^^^^^^^^^^^ data base64 encoded.
                     ^^^^^ timer seems it might be optional.
                 ^^^ dest
               ^ source
             ^ priority
      ^^^^^^ PGN decimal
^^^^^ NMEA183 id, ! probably means binary

candump1 /ws/candump1 - not implemented

<0x18eeff01> [8] 05 a0 be 1c 00 a0 a0 c0
                 ^^^^^^^^^^^^^^^^^^^^^^^ frame
             ^^^ len 
^^^^^^^^^^^^ canID

candump2 /ws/candump3 - not implemented

can0  09F8027F   [8]  00 FC FF FF 00 00 FF FF
                      ^^^^^^^^^^^^^^^^^^^^^^^ frame
                  ^ len
      ^^^^^^^^ canid
^^^^ device

Actisense /ws/actisense - not implemented

2016-02-28T19:57:02.364Z,2,127250,7,255,8,ff,10,3b,ff,7f,ce,f5,fc
                                          ^^^^^^^^^^^^^^^^^^^^^^^ data
                                        ^ length
                                    ^^^ destination
                                  ^ source
                           ^^^^^^ pgn
                         ^ priority
^^^^^^^^^^^^^^^^^^^^^^^^ ISO date

Easy to read, a bit of a standard, but also verbose. For date we would need to capture the date from GPS.

Actisense N2K ASCII - not supported

A764027.880 05FF7 1EF00 E59861060202FFFFFFFF03030000FFFFFFFFFFFFFFFFFFFF0000FFFFFF7FFFFFFF7FFFFFFF7F0000FF7F
                  ^^^^^^^^^^^ full can message, upto 400 chars
                ^ priority
              ^^ dest
            ^^ src
 ^^^^^^^^^^ timestamp

Full messages are problematic with memory.

Data Link encoded - not implemented

Binary format see apenedix F in https://www.yachtd.com/downloads/ydnu02.pdf

Below is a copy

    APPENDIX F. Format of Messages in N2K Mode
    In N2K mode, messages are encoded in a binary format. This format is based on Data Link Escape encoding
    partially compatible with ActiSense NGT format and widely support by modern marine applications.
    This format is very similar to Garmin Serial Protocol (see section 3.1 of Garmin Device Interface
    Specification 001-00063-00 for details).
    All data are transferred in byte-oriented packets. A packet contains a four-byte header (DLE, STX, ID,
    and Size), followed by a variable number of data bytes, followed by a three-byte trailer (Checksum, DLE,
    and ETX). The following table shows the format of a packet:
    
    Table 1. Packet format
    
    Byte Number Byte Description Note
    ---------------------------------
    0           Data Link Escape ASCII DLE character (16 decimal)
    1           Start of Text ASCII STX character (02 decimal)
    2           Packet ID Identifies the type of packet
    3           Size of Packet Data Number of bytes of packet data (bytes 4 to n-4)
    4 to n—4    Packet Data 0 to 255 bytes, see Table 2
    n—3         Checksum 2's complement of the sum of all bytes from byte 1 to byte n-4
    n—2         Data Link Escape ASCII DLE character (16 decimal)
    n—1         End of Text ASCII ETX character (03 decimal)
    
    If any byte in the Size, Packet Data, or Checksum fields is equal to DLE, then a second DLE is inserted
    immediately following the byte. This extra DLE is not included in the size or checksum calculation.
    This procedure allows the DLE character to be used to delimit the boundaries of a packet.
    Packets with NMEA 2000 messages transmitted to the PC application (incoming) have ID 0x93
    (147 decimal), packets with NMEA 2000 messages sent by the application to the Device (outgoing)
    have ID 0x94 (148 decimal).
   
    Byte Number Byte Description Note
    ----------------------------------
    0           Message Priority Bits 0 - 2 are significant, other bits are ignored
    1 to 3      NMEA 2000 PGN Least significant byte is first
    4           Destination Address Or 255 decimal for global addressed messages
    5           Source Address Ignored for outgoing messages, Device address is used
    6—9         Time Stamp Device’s time in milliseconds, ignored in outgoing messages
    10          Size of Payload Number of bytes in NMEA 2000 message payload (1..232)
    11+         Message Payload 1 to 232 bytes of message payload
    
    The format of NMEA 2000 messages is available in Appendix B of NMEA 2000 Standard, which
    can be purchased on the site www.nmea.org.

Probably too much of a pain to implement.

Archived functionality

The project was forked from CanDiagnose https://github.com/ieb/CanDiagnose wip branch which contains the functionality below.

  • Implement BPM280 source (moved to archive)
  • Implement calibration mechanism using DAC (moved to archive)
  • [Fail] Implement ADC Source (esp adc not sufficiently accurate)
  • Redesign PCB to use 16bit ADC over i2c (ADS1115)
  • Implement ADC sensor code (move to archive)
  • Add OLED display (moved to archive)
  • Add touch sensor to control oled display (moved to archive)
  • Bench Calibrate expecialy shunt (moved to archive)
  • Install test and calibrate. - Failed, the distance between the board and the batteries is too great to get reliable shunt measurements.
  • Investigate using a shunt amplifier as used by VRA Alternator controller
  • Implement remote battery sensor using Modbus (see BatterySensor project) (moved to archive)
  • Reimplement PCB to have no BME280 and no ADS1115 replacing with a RS485 interface to ModBus devices. (see CanPressure for Can based pressure, humidity, temperature sensor) (moved to archive)
  • Implement Modbus master (moved to archive)
  • Support Configuraiton of single and differential ADC.

Usage

The device will boot and connect to wifi controlled by its burned in config file (data/config.txt). There are 2 modes it can run in, a WiFi client or a AP. If its configured to be an AP, then join its network. If its a client find its ip. From now on this will be

Go to its ip on http:// and you will see a data view of the data it is capturing. Go to http:///admin.html and you will see a admin view with the ability to upload a new configuration file and reboot.

Both these views are SPAs served from static files burned into the ESP32 Flash using APIs.

Connecting to the serial port monitor allows lower level control and diagnostics, enter 'h' to get help.

Developing

Project uses PlatformIO. WebUI is in ui/v2. This can be developed locally using node http-server in ui/vi/src or by updating the flash drive of a device running the firmare over http, using ui/v2/build.sh

PIO commands

because I always forget.

  • pio run -t upload
  • pio device monitor

See buildui.sh for SPIFFS image commands. Don't use this to update the flashdrive, its easier to use the admin ui or curl.

PCB

There have been a few version including ones with TFT screens which were too power hungry and not very good to use. look at pcb for details.

Archived Functionality

Rather than using dedicated hardware displays I have decided to switch to apps since the power consumption is better and they require no installation onboard. Most of the display code is in the archive subfolder.

eInk Waveshare display (archived)

This uses SPI output only with a bunch of additional pins.

eInk DIN <-  f  SPI MOSI GPIO32
eInk CLK <-  e  SPI SCK  GPIO33
eInk CS  <-  d  SPI CS   GPIO25
eInk DC  <-  c  SPI DC   GPIO26
eInk RST <-  b  SPI RST  GPIO12
eInk BUSY -> a  SPI BL   GPIO13

4 inch TFT display 480x320 24 bit colour driven by a ILI9488 driver. (archived)

uses SPI + a PWM blacklight. Control via a TPP233 Touch switch. Library is TFT_eSPI library. Pin mappings defined as compile definitions.

-D TFT_MISO=GPIO_NUM_35
-D TFT_MOSI=GPIO_NUM_32
-D TFT_SCLK=GPIO_NUM_33
-D TFT_CS=GPIO_NUM_25
-D TFT_DC=GPIO_NUM_26
-D TFT_RST=GPIO_NUM_27
-D TFT_PWM_BL=GPIO_NUM_14
-D ILI9488_DRIVER

The display sleeps after 60s of inactivity to reduce power drain, but turning off the backlight. Sleeping the IMI9488 driver has not been done as of yet. Pages are implemented as classes using widgets. When a page is selected it is allocated into heap and does not exist while not displaying. On other drivers here all pages were compiled in, however the RAM usage is higher on account of full colour and display size.

In most cases attempts to update the screen, on screen causes flickering so double buffering of updates is done using sprits with DMA transfers from the sprite to the screen. Images for screen are stored in jpg on flash, consuming about 30KB per screen.

Drawing to sprites also works in 1bpp, 4bpp or 16bpp.

TFT case

3d printed case, with 2 touch sensors and round shielded cable, as ribbon will emit too much interference to nearby devices.

Wires

|| color || designation || ESP32 Pin ||

| Red | 5v | 5v | | black | 0v | 0v | | pink | CS | GPIO_NUM_25 | | cyan | Reset | GPIO_NUM_27 | | white | DC/RS | GPIO_NUM_26 | | blue | SDI(MOSI) | GPIO_NUM_32 | | green | SCK | GPIO_NUM_33 | | grey | LED | GPIO_NUM_14 | | Purple | SDO(MISO) | GPIO_NUM_35 | | Orange | 3.3v | 3.3v | | Brown | Touch Lower | GPIO_NUM_19 | | Yellow | Touch Top | TBD |

Due to the additional touch pins a fresh PCB is probably needed.

Firmware crash

Most of the code is single threaded with no malloc or new operations, and hence it is stable. The only crash I have seen in 4 years has been websockets as below. AFAICT, not fixed upstream with no prospects. Hence removed websockets code. This also makes it easier to implement http clients.

PgnFilter ,5,128259,130306,127250,127257,127258,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ws[/ws/seasmart][6] error(1002): WebSocket Protocol Error ws[/ws/seasmart][6] disconnect ws[/ws/seasmart][7] connect ws[/ws/seasmart][7] pong[0]: Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled. Core 1 register dump: PC : 0x400da7fb PS : 0x00060630 A0 : 0x800db1d1 A1 : 0x3ffb1cd0
A2 : 0x3ffc31ec A3 : 0x3ffd26b4 A4 : 0x00000001 A5 : 0x3ffc325c
A6 : 0x00000000 A7 : 0x3ffc7fd8 A8 : 0x800da830 A9 : 0x3ffb1cb0
A10 : 0x3ffd26b4 A11 : 0x3ffd26b4 A12 : 0x3ffb5110 A13 : 0x00000000
A14 : 0x00000000 A15 : 0x00000034 SAR : 0x00000010 EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000001 LBEG : 0x4000c349 LEND : 0x4000c36b LCOUNT : 0x00000000

ELF file SHA256: 0000000000000000

Backtrace: 0x400da7fb:0x3ffb1cd0 0x400db1ce:0x3ffb1d00 0x400db1e7:0x3ffb1d20 0x400db1fd:0x3ffb1d40 0x400db20d:0x3ffb1d60 0x400ea220:0x3ffb1d80 0x400d1b4e:0x3ffb1da0 0x400eef46:0x3ffb1dc0 0x400eef9d:0x3ffb1de0 0x400ee570:0x3ffb1e10 0x400eed32:0x3ffb1e70 0x400d1a41:0x3ffb1f10 0x4019a42e:0x3ffb1f30 0x400d6859:0x3ffb1f50 0x400d1e78:0x3ffb1f90 0x400f3370:0x3ffb1fb0 0x400899d2:0x3ffb1fd0 #0 0x400da7fb:0x3ffb1cd0 in AsyncWebSocket::_cleanBuffers() at .pio/libdeps/nodemcu-32s/ESP Async WebServer/src/AsyncWebSocket.cpp:842 #1 0x400db1ce:0x3ffb1d00 in AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer*) at .pio/libdeps/nodemcu-32s/ESP Async WebServer/src/AsyncWebSocket.cpp:842 #2 0x400db1e7:0x3ffb1d20 in AsyncWebSocket::textAll(char const*, unsigned int) at .pio/libdeps/nodemcu-32s/ESP Async WebServer/src/AsyncWebSocket.cpp:842 #3 0x400db1fd:0x3ffb1d40 in AsyncWebSocket::textAll(char*) at .pio/libdeps/nodemcu-32s/ESP Async WebServer/src/AsyncWebSocket.cpp:842 #4 0x400db20d:0x3ffb1d60 in AsyncWebSocket::textAll(char const*) at ??:? #5 0x400ea220:0x3ffb1d80 in WebServer::sendN0183(char const*) at lib/network/httpserver.cpp:382 #6 0x400d1b4e:0x3ffb1da0 in SendNMEA0183Message(char const*) at src/main.cpp:393 #7 0x400eef46:0x3ffb1dc0 in NMEA0183N2KMessages::send() at lib/N2KHandler/NMEA0183N2KMessages.cpp:443 #8 0x400eef9d:0x3ffb1de0 in NMEA0183N2KMessages::sendHDG(double, double, double) at lib/N2KHandler/NMEA0183N2KMessages.cpp:45 #9 0x400ee570:0x3ffb1e10 in N2KHandler::handle127250(tN2kMsg const&) at lib/N2KHandler/N2KHandler.cpp:229 #10 0x400eed32:0x3ffb1e70 in N2KHandler::handle(tN2kMsg const&) at lib/N2KHandler/N2KHandler.cpp:82 #11 0x400d1a41:0x3ffb1f10 in HandleNMEA2000Msg(tN2kMsg const&) at src/main.cpp:393 #12 0x4019a42e:0x3ffb1f30 in tNMEA2000::RunMessageHandlers(tN2kMsg const&) at .pio/libdeps/nodemcu-32s/NMEA2000-library/src/NMEA2000.cpp:2314 #13 0x400d6859:0x3ffb1f50 in tNMEA2000::ParseMessages() at .pio/libdeps/nodemcu-32s/NMEA2000-library/src/NMEA2000.cpp:2314 #14 0x400d1e78:0x3ffb1f90 in loop() at src/main.cpp:400 #15 0x400f3370:0x3ffb1fb0 in loopTask(void*) at /Users/ieb/.platformio/packages/framework-arduinoespressif32/cores/esp32/main.cpp:23 #16 0x400899d2:0x3ffb1fd0 in vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c:355 (discriminator 1)

Rebooting...

About

N2K and NMEA0183 Wifi Server for ESP32 exposing http, tcp and udp services.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published