Getting Started

Introduction

The Programming Manual contains a description of the Python Gateway software library, code examples and will help you to create your own programs. To use this manual you should be familiar with beginner level Python programming, as the Gateway programs are usually simple enough and contain about ten or twenty lines of code.

The Gateway is designed for use in NMEA networks, but can also be used in other CAN networks (e.g. J1939) and with non-NMEA devices that are compatible with RS-232 or RS-422 protocols. However, this manual focuses on NMEA 2000 and NMEA 0183. Please note that programming requires knowledge of the NMEA protocols. A copy of the NMEA 2000 or NMEA 0183 standard can be obtained from the National Marine Electronics Association (www.nmea.org).

We recommend that you use the Thonny IDE to work with the Gateway. This is free software available for Microsoft Windows, Linux and OS X from www.thonny.org. This simple IDE allows you to edit program files directly on the Gateway, has syntax highlighting and auto-completion, save backup files to PC, run and stop your code, interact with REPL, plot variables, etc.

Before proceeding, we strongly recommend that you familiarize yourself with the User’s Manual, where you will find answers to questions such as how to connect the Gateway to a PC or to an NMEA network or device, how to upload or modify files on the Gateway, how to use Thonny or terminal software, what keyboard shortcuts are available in the terminal session, how to reset the Gateway, what LED signals mean. These topics are not covered in the Programming Manual.

Device description

The main difference between the Python Gateway and various “Python boards” with a built-in “CAN shield” is that it does not provide direct access to microcontroller pins and peripherals such as SPI or I2C, mainly because the user does not have physical access to these pins. Instead, it provides a powerful library that makes it easy to create NMEA devices and process NMEA data, saving days of development time.

You will work with the following software and hardware components of the Gateway:

  1. NMEA 2000 or CAN bus interface. The software developer can work with the interface at the level of the pyb.CAN class (MicroPython library, slightly modified in the Gateway version) or at the level of the NMEA2000 class (Yacht Devices library, which also uses pyb.CAN). In both cases the speed, filters and other CAN bus parameters can be configured. If the NMEA2000 class is used, the NMEA 2000 device is created.

  2. The NMEA 2000 device. Each physical device on the NMEA 2000 network should have a unique address, support address claiming and assignment, and send and process a set of mandatory messages. Complex devices, such as an MFD with built-in GPS and directly connected depth sounder, may have multiple virtual devices with different addresses sharing a single physical interface. When using the NMEA2000 class, the Yacht Devices library creates NMEA 2000 device that meet NMEA certification requirements and handle address assignment and all mandatory NMEA 2000 messages. All device settings (address, device and system instances, installation strings) are stored in EEPROM. The user program can set the device status field of the heartbeat message (sent every minute by the NMEA 2000 device) to indicate the program status. The NMEA2000 class also provides transparent handling of incoming and outgoing fast packets.

  3. NMEA 0183 interface. The Gateway has two physical serial ports, one for receiving messages and one for sending messages. It allows to use different speed and other settings for TX and RX. The software developer can work with the interface at the level of the pyb.UART class (MicroPython library, slightly modified in the Gateway version) or at the level of the NMEA0183 class (Yacht Devices library, it also uses pyb.UART). The pyb.UART allows reading and writing of serial data at byte level, supports 9-bit mode and is fully configurable. The NMEA0183 class allows to work with serial data on the level of lines, offers a callback with filters on talker ID or sentence formatter, and a set of helpers to check or add the NMEA 0183 checksum and so on.

  4. Real time clock. Both NMEA 2000 and NMEA 0183 have messages containing date and time data. The corresponding classes in the Yacht Devices library allow the time in the pyb.RTC class (MicroPython library) to be automatically synchronized with the network data. Although the Gateway does not have a dedicated oscillator to ensure accurate timing, NMEA 2000 provides synchronization at least once per second and NMEA 0183 provides time data every few seconds. The Gateway also has no battery, so it will take some time to synchronize the clock after a reset.

  5. USB interface. When connected to the PC, it provides two virtual devices at the same time: COM port with terminal access to REPL and 16 MBytes USB disk. The interface status can be checked in the user program with ydpg.usbstate() or callback.

  6. 16 Mbytes virtual disk. The flash disk is used to store the user program and is fully accessible from Python. The user program can use it to store its own data, including logs. Note that at power off, the Gateway is guaranteed to close only two files opened for write or append (files opened for read only access are not counted). The flash memory guarantees 100,000 write/erase cycles and software developers must take this into account when designing the application.

  7. Virtual serial port (COM). The serial port provides access to the REPL. The user program can be designed to run entirely within callbacks, and the REPL will be available to enter commands as the program runs. If the program has an infinite main loop, it can be interrupted with the Ctrl+C shortcut. To learn more, type “help()” at the Python interactive prompt.

  8. Bi-color LED. The LED flash sequences are described in the User Manual. During normal operation, the sequence consists of five flashes indicating the status of the NMEA 2000 and NMEA 0183 interfaces. The software developer can add the sixth flash to the sequence, which will automatically occur every five seconds to indicate the status of the program. Or developer can take full control of the LED behavior. In the latter case, you can choose the timing and duration of the signals to avoid confusion with the sequence indicating normal operation.

  9. Voltage monitor. The Gateway is equipped with a voltmeter, see ydpg.busvoltage(). The built-in capacitors are not sufficient to use the voltmeter for software power monitoring. The Gateway’s hardware power monitor is only used to close files opened in Python to protect the integrity of the file system at power down. There is no signal to the user program that the gateway is shutting down, because the built-in capacitors can’t guarantee that the software has enough time to react (send a message, etc.).

  10. Other hardware. The ydpg.cputemp() returns the temperature of the MCU, but it does not correlate with the execution of the user program. The MCU always operates at the same frequency and does not use power saving technologies.

Programming basics

On power up, the Gateway will search the flash disk for the boot.py and main.py files and execute them sequentially. The first file contains the code to import important modules and initialize peripherals. The second file is for user code and is supplied empty.

The typical use of the Gateway is to convert data between NMEA 0183 and NMEA 2000. The example below shows the conversion of water temperature from NMEA 0183 MTW sentence to NMEA 2000 PGN 130310 “Environmental Parameters”:

import struct

Temperature = None

def rx_clbk(n0183, line):
    global Temperature

    line = line.split(b'*')[0] # remove checksum
    args = line.split(b',')

    if len(args) > 2 and args[2] == b'C':
        # convert temperature to NMEA2000 format and unit (Kelvin)
        Temperature = round(float(args[1]) * 100 + 27315)
        data = struct.pack('<BHHHB', 0xFF, Temperature, 0xFFFF, 0xFFFF, 0xFF)
        n2k.sendpgn(data, 130310)

# Receive every message with 'MTW' sentence formatter, e.g.
# b'$INMTW,17.9,C*1B\r\n'
n0183.rxcallback(0, rx_clbk, b'$--MTW')

This example is based on the callback, so the REPL will be available during program execution, and to check the last value you can type in the REPL:

>>> print("Temperature:", Temperature/100, "Kelvin")

Temperature: 273.15 Kelvin

The program can also have the classic structure with an infinitive loop in the main thread. The following example sends data from the internal voltmeter in PGN 127508 “Battery Status” every 1.5 seconds to NMEA 2000:

import struct

def send_battery_status():
    i16 = int(round(ydpg.busvoltage() * 100))
    data = struct.pack('<BhhHB', 0, i16, 0x7FFF, 0xFFFF, 0xFF)
    n2k.sendpgn(data, 127508)

while True:
    send_battery_status()
    time.sleep(1.5)

In this case you can break the execution with Ctrl+C in the terminal application to get access to the REPL. For debugging purposes, you may need to restart the Python interpreter (soft reset). This can be done in the REPL with the shortcut Ctrl+D. To learn more about shortcuts, type “help()” in the REPL.

The above code can be rewritten using the timer from the standard library:

import struct
import machine

def send_battery_status(id):
    i16 = int(round(ydpg.busvoltage() * 100))
    data = struct.pack('<BhhHB', 0, i16, 0x7FFF, 0xFFFF, 0xFF)
    i16 = n2k.sendpgn(data, 127508)

t = machine.Timer()
t.init(callback=send_battery_status)

The following example demonstrates how to list all devices on the NMEA 2000 bus. The payload of the PGN 126996 “Product Information” is 134 bytes (called “fast-packet”) and is transferred in 20 CAN frames. The NMEA2000 class can assemble fast-packets and developer may focus on data processing:

def product_info(n2k, data, msgid, seq):
    # Remove unused characters in 32 bytes field
    id = data[68:100].split(b'\xff')[0]
    print(id.decode().rstrip())

def on_init(n2k, state):
    if state == NMEA2000.READY:
        n2k.request(126996)

# Product Information (fast-packet)
n2k.rxcallback(0, product_info,pgn=126996)
n2k.statecallback(on_init)

The code will print the model of devices each time someone enumerates devices on the bus. To enumerate devices yourself, type in the REPL:

>>> n2k.request(126996)

The NMEA 2000 status callback in this code is used to enumerate all devices after power on. According to the NMEA Standard, devices are prohibited from sending messages not related to the address claim procedure during the first 250 milliseconds, and the NMEA2000.request() method in the main thread should be used after the delay or in the state callback.

Gateway control via CAN bus

Installation description strings are usually set by installers to specify the device location or to leave notes or contact information. This can be done with professional PC software with a hardware gateway to NMEA 2000 network. If you have the gateway from Yacht Devices, the free CAN Log Viewer software can be used to control the Gateway via CAN bus.

To program the Device, enter a special string starting with “YD:” to installation description field 2 in the Device properties. For example, “YD:DEV 1” (without quotes) will change the NMEA 2000 Device instance to 1. If command (except “YD:RESET”) is accepted by the Device, it will add “DONE” to entered text and “YD:DEV 1 DONE” will be displayed in this installation description field. If a command is entered without the parameter(s), Device replies with the current value of the parameter(s).

Configuration Information Commands

Command

Description

YD:RESET

Resets NMEA 2000 device settings to factory state.

YD:REBOOT

Reboots the Gateway.

YD:DEV [0..255]

Sets NMEA 2000 device instance value (0 - 255). Factory setting 0.

YD:SYS [0..15]

Sets NMEA 2000 system instance value (0 - 15). Factory setting 0.

YD:PGN <pgn> [interval]

Set periodic transmission interval in milliseconds for PGN,

0 to disable. Available PGNs:

126993 “Heartbeat”.

YD:POWERDOWN [voltage]

Sets treshold CAN-BUS voltage at which file system cache is flushed

to flash memory and gateway is shutdown. Factory setting: 9.

YD:TEMPERATURE

Returns the MCU temperature in Celsius.

YD:VOLTAGE

Returns the CAN bus voltage.

YD:EVAL <python expression>

Evaluates Python expression.

Equivalent to “eval(<expression>)”.

YD:EXEC <python code>

Executes Python code.

Equivalent to “exec(<code>)”.

YD:RUN <file name> [args] …

Executes python file.

Parsed argumants placed in “sys.argv”.

Note

The last three commands can only be executed if REPL is available. A user program does not take up REPL if it is based on callbacks and interrupts.

Default contents of boot.py

import pyb
import time

import ydpg
from nmea2000 import *
from nmea0183 import *

led = ydpg.LED(manual=False)
rtc = pyb.RTC()

can = pyb.CAN(1, baudrate=250_000)
n2k = NMEA2000(can, rtc=rtc)

uart_rx = pyb.UART('rx', baudrate=4800)
uart_tx = pyb.UART('tx', baudrate=4800)
n0183 = NMEA0183(uart_rx, uart_tx, rtc=rtc)