April 19, 2024  AIS Weather Data

Update for all gateways with NMEA 2000: NMEA 2000 Wi-Fi Router and Gateway, Ethernet Gateway, USB Gateway and NMEA 0183 Gateway. Includes weather data, new Web Gauges and other cool features.

Coastal Explorer with AIS weather data

Formally speaking, AIS message #8 is capable of transmitting various types of data: data on navigational hazards, fairways and VTS, text messages and much more (see the list!). However, the maritime industry is very inert, and message #8 has not been widely used, despite the fact that the meteorological data format was proposed 20 years ago, in 2004.

Simrad NSX and GO7 multifunction displays, Garmin Echomap and Raymarine Axiom with the latest firmware failed to show our test NMEA 0183 and NMEA 2000 messages. However, NMEA 2000 PGN 129797 "AIS Binary Broadcast Message" support is not declared in their documentation. Below is the short list to test:

     !AIVDM,1,1,,B,8>k1oKiKpB9GO>42lBPWmre0<N00,0*55
     !AIVDM,1,1,,B,83aDChPj2d<dL<uM=hhhI?a@6HP0,0*40
     !AIVDM,1,1,4,B,8>jR06@0Bk3:wOli;<`WPhh<1rqVBQf2V@Pdt0J82avIM2b<<Rv1t<ot=@1,2*56
     !AIVDM,1,1,1,B,8>h8nkP0Glr=<hFI0D6??wvlFR06EuOwgwl?wnSwe7wvlOw?sAwwnSGmwvh0,0*17

OpenCPN developers have not yet released a version with support for this message, but the work is in progress. Expedition 10 does not fully support meteorological data, and only Coastal Explorer (see image above) was able to display data from an already obsolete message (DAC=1, FI=11, proposed in 2004, obsolete in 2013), but modern messages (DAC=1, FI=31) are not yet supported.

On the one hand, it's great that support for these messages is starting to appear in software products. But on the whole it is very sad that such a simple and powerful channel of navigation and weather data transmission is not used now, and that major electronics manufacturers are in no hurry to support it. Nor have they supported Navtex, still very popular in European waters: the latest MFD to support it (Raymarine Widescreen) has been out of production for about 10 years.

In all our products that support data conversion between NMEA 0183 and NMEA 2000 (mentioned in the header), we have added AIS #8 bi-directional message conversion. Hopefully in the coming years there will be more and more places on the planet where mariners can get useful data using it, and support for it in software products will get better and better.

Substring replacement and incoming XDR

Figure 1. Substring replacement and incoming XDR

Last December we added the ability to replace substrings in NMEA 0183 messages and correct or add a checksum to our NMEA 0183 Wi-Fi multiplexers. In the firmware update for the NMEA 2000 Wi-Fi Router, we have also added these features.

They can be very useful, especially when connecting external sensors that use the XDR sentence. Suppose you have two sensors sending fuel level data in tanks in the same messages and connected to NMEA 0183 ports of NMEA 2000 Wi-Fi Router:

     $IIXDR,V,15,P,FUEL*56

Since the sensors have the same sensor name (FUEL), it is the same data for our device. But we can change the name of the sensor connected to the first port from FUEL to FUEL_BOW, and then in the settings of the incoming XDR specify that this is our fore tank, and the remaining tank is aft.

Many of our devices have a tunnel function where incoming NMEA 0183 data is sent to another port without checking. If it is enabled, the units will also attempt to extract even incomplete data from incomplete messages and convert them. This is partly to support the early NMEA standards (even though they have been obsolete for over 30 years now, with the release of NMEA 0183 2.0 in 1992), partly to pass non-NMEA compliant string data between ports (e.g. Navtex receiver output).

The tunnel function was not present in the NMEA 2000 USB Gateway, as finding software that sends a message without a checksum has probably become impossible in the last ten years. However, we have added a setting that allows you to disable the requirement and checksum validation in incoming messages. To use it, give the command in service mode:

     SET CHECKSUM OFF

All products with a web interface include the updated Web Gauges. It fixes a very annoying behaviour where double-clicking in some browsers would cause the screen to zoom out, it also fixes minor bugs.

New Ethernet Gateway firmware also fixes problems with uploading updates in some browsers. And added a configuration mode via NMEA 2000, useful if you have one more NMEA 2000 gateway.

Ethernet Gateway settings in the CAN Log Viewer app

Figure 2. Ethernet Gateway settings in the CAN Log Viewer app

In the free CAN Log Viewer software you can get a list of devices on the bus and see their properties. For our products with IP interface, you can click on the "More..." button and see the current settings of the network interface.

Now, for the Ethernet Gateway, you can enter the commands in the "Installation Description 2" field (it can be done with ActiSense or Maretron software too):

     YD:IP 10.1.1.3
     YD:MASK 255.255.0.0
     YD:GATEWAY 10.1.1.1
     YD:DNS 10.1.1.1
     YD:DHCP STATIC
     YD:REBOOT

to change the network interface settings and reboot the device. The YD:DHCP command also accepts the CLIENT, SERVER, and MAGIC arguments. All commands can be run without an argument to retrieve a stored configuration value. Note that the saved network settings in MAGIC and CLIENT mode may not match the settings in use (it is shown on Figure 2), as they may be retrieved from the DHCP server.

We recommend that all users use update 1.72 for NMEA 2000 Wi-Fi Router and Gateway, Ethernet Gateway, USB Gateway and NMEA 0183 Gateway YDNG-03. It can be downloaded from the Downloads page.

Permanent link...

 

 

 April 15, 2024  B&G Fastnet Protocol

How to make a gateway for this protocol using Python Gateway and debug the code on the boat using a mobile phone.

B&G Network

It all started when a friend asked us to look at his wind sensor. Unfortunately, the board was completely rotted and somewhere far away we found a similar board for $450, but we weren't sure it would fit in the case.

So we started to get acquainted with B&G Fastnet protocol. The protocol is based on a 28800 baud serial interface, and although the protocol is proprietary, amateurs were able to decode the message format used in the H2000 and H3000 systems.

FastNet decoding

Figure 1. FastNet decoding

Our friend, however, had Network Quad and Network Wind written on his displays. A little research showed that they also use the FastNet protocol, but at 11000 baud. We wrote simple code (see below) for a Python Gateway, to read data from the bus and output it to the console (see above). By changing the speed setting, this code can be used with the H2000 or H3000.

### ASCII TEXT COLORS

RED    = "\033[0;31m"
GREEN  = "\033[0;32m"
YELLOW = "\033[0;33m"
WHITE  = "\033[0;37m"

### FASTNET CONSTANTS AND UTILITY METHODS

FASTNET_FMT_BYTES = [ # total size of channel data in bytes
    0, 4, 4, 5,
    6, 0, 0, 0,
    4, 0, 0, 6,
    0, 0, 0, 0
]

FASTNET_CHANNELS = { # known data channel IDs
    0x1F: f"Sea Temperature, C",
    0x4E: f"Apparant Wind Speed, raw",
    0x50: f"Apparant Wind Speed, from nmea",
    0x51: f"Apparant Wind Angle",
    0x52: f"Apparant Wind Angle, raw",
    0x41: f"Boat Speed, knots",
    0x42: f"Boat Speed, raw",
    0x57: f"MEAS W/S knots",
    0x59: f"True Wind Angle",
    0x64: f"Average Speed, knots",
    0x65: f"Average Speed, raw",
    0x75: f"Timer",
    0x7f: f"Velocity Made Good, knots",
    0x81: f"Dead Reckoning Distance",
    0x8D: f"Battery Voltage",
    0xCD: f"Stored Log, NM",
    0xCF: f"Trip Log, NM",
    0xD3: f"Dead Reckoning Course",
}

FASTNET_DIVISORS = [1, 10, 100, 1000]

def fastnet_crc(data, size, init=0):
    crc = init
    for i in range(size):
        crc += data[i]
    crc = (0x100 - crc) & 0xFF
    return crc

def fastnet_decode_channel(data):
    global FASTNET_FMT_BYTES, FASTNET_CHANNELS, FASTNET_DIVISORS
    ch = data[0]
    fmt = data[1]
    
    div = (fmt >> 6) & 0x3
    size = (fmt >> 4) & 0x3
    fmt &= 0xF
    
    ret = FASTNET_FMT_BYTES[fmt]
    if ret <= 2:
        print(f'{RED}Unknown format: {fmt}!{WHITE}')
        return -1;
        
    if fmt == 1:
        value = data[3] | (data[2] << 8)
        if (value > 0x7FFF):
            value = value - 0x10000
    elif fmt == 8:
        value = data[3] | (data[2] << 8)
    else: # format is unknown, display raw bytes
        div = 0
        value = YELLOW + data[2:ret].hex(' ')
    
    if div > 0:
        value /= FASTNET_DIVISORS[div]
    
    name = FASTNET_CHANNELS.get(ch, None)
    if name == None:
        print(f'[{YELLOW}UNKNOWN, {ch}{WHITE}]: {value}')
    else:
        print(f'[{GREEN}{name}{WHITE}]: {value}')
    return ret

def fastnet_clbk(uart_rx, data):    
    # single callback call can contain multiple messages
    while len(data) > 7:
        dst, src, length, cmd, crc = data[:5]
        if cmd == 0x01 and fastnet_crc(data, 4) == crc:
            # message type is 'data broadcast'
            data = data[5:]
            if fastnet_crc(data, length, 0x56) == data[length]:
                led.green() # fastnet channel received successfuly
                offset = 0
                while offset < length:
                    readed = fastnet_decode_channel(data[offset:])
                    if readed < 2:
                        data = None # exit from outer loop
                        # unknown size of channel, can't continue
                        break 
                    offset += readed
        if data == None:
            break
        data = data[length+1:]

# deinitializes NMEA0183 object created in boot.py
n0183.deinit()
# sets UART settings according to Fastnet specification
uart_rx.init(baudrate=11000, bits=8, stop=2, parity=1, rx_invert=True, timeout=100)

uart_rx.rxcallback(fastnet_clbk, end=None, timeout=4, force=True)

# Dummy loop. Press Ctrl+C to exit program.
try:
    while True:
        time.sleep(1)
except:
    pass
uart_rx.rxcallback(None)

We suggested to our friend to buy an inexpensive NMEA 0183 wind sensor and connect it to the display via Python Gateway. He was not ready to spend a lot of money and we decided to give him our device as a gift. Below is the working code. The NMEA 2000 connector is used for power only, the NMEA 0183 RX port is connected to the wind sensor and the NMEA 0183 TX port is connected to the B&G Network Wind instrument display.


FASTNET_DIVISORS = [1, 10, 100, 1000]

FASTNET_FMT_BYTES = [ # total size of channel data in bytes
    0, 4, 4, 5,
    6, 0, 0, 0,
    4, 0, 0, 6,
    0, 0, 0, 0
]

FASTNET_CH_AWA = 0x51
FASTNET_CH_AWS = 0x50
FASTNET_CH_VOLTAGE = 0x8D

fastnet_header = bytearray([0xFF, 0x75, 0x14, 0x01, 0x77])
fastnet_buf = bytearray(81)
fastnet_buf_size = 0

N0183_SPEED_UNITS = {
    b'K': 1.0, # knots
    b'M': 1.943844492442284 # m/s
}

def fastnet_crc(data, size, init=0):
    crc = init
    for i in range(size):
        crc += data[i]
    crc = (0x100 - crc) & 0xFF
    return crc

def fastnet_add_channel(ch, fmt, size, divisor, value):
    global fastnet_buf, fastnet_buf_size
    global FASTNET_DIVISORS, FASTNET_FMT_BYTES
    
    if (len(fastnet_buf) - fastnet_buf_size) <= FASTNET_FMT_BYTES[fmt]:
        return -1
    
    offset = fastnet_buf_size
    fastnet_buf[offset] = ch
    fastnet_buf[offset + 1] = (fmt&0xF) | ((size&0x3) << 4) | ((divisor&0x3) << 6)
    offset += 2
    
    value = round(value * FASTNET_DIVISORS[divisor])
    
    if fmt == 1:
        if (value < 0):
            value = 0x10000 + value
        fmt = 8
    if fmt == 8:
        fastnet_buf[offset] = (value >> 8) & 0xFF
        fastnet_buf[offset + 1] = value & 0xFF
        fastnet_buf_size = offset + 2

def fastnet_flush():
    global fastnet_header, fastnet_buf, fastnet_buf_size
    
    # set message size and calculate CRCs
    fastnet_header[2] = fastnet_buf_size
    fastnet_header[4] = fastnet_crc(fastnet_header, 4)
    fastnet_buf[fastnet_buf_size] = fastnet_crc(fastnet_buf, fastnet_buf_size, 0x56)
    
    # send fastnet channels
    uart_tx.write(fastnet_header)
    uart_tx.write(fastnet_buf, 0, fastnet_buf_size + 1) # + 1 byte CRC
    fastnet_buf_size = 0
    led.green()

def n0183_receive_mwv(n0183, data):
    data = data.split(b'*')[0].rstrip().split(b',')
    print(data)
    if len(data) == 6 and data[2] == b'R' and data[5] == b'A':
        awa = float(data[1])
        aws = float(data[3]) * N0183_SPEED_UNITS.get(data[4], 1.0)
        fastnet_add_channel(FASTNET_CH_AWA, 8, 0, 0, awa)
        fastnet_add_channel(FASTNET_CH_AWS, 1, 0, 2, aws)
        fastnet_add_channel(FASTNET_CH_VOLTAGE, 8, 0, 2, ydpg.busvoltage())
        fastnet_flush()

# Removes TX port from NMEA0183 object
n0183 = NMEA0183(uart_rx, rtc=rtc)
n0183.check(False)
# Reinitializes TX port to match fastnet specification
uart_tx = pyb.UART('tx', baudrate=11000, bits=8, stop=2, parity=1)

led.state(led.RED)

n0183.rxcallback(0, n0183_receive_mwv, "$--MWV", check=False)

One nuance arises here. If the Quad and Wind are not connected, everything works fine. But if they are connected, network collisions occur. The point is that the devices send messages to one bus, and in order not to interrupt each other, the bus must be listened to before sending. Unfortunately, we have no free input, and our friend will have to say goodbye to the possibility of observing True Wind on his instrument, as his calculations require data from the B&G Network Quad. However, he claims that the Apparent Wind is enough for him.

But we have written code that uses NMEA 0183 RX to listen to the bus. In our tests there were no errors on the bus. The code below listens to the bus and sends wind demo data to it. You can use it as a basis to make a B&G FastNet to NMEA 2000 converter, or to send USB data to FastNet.

# ASCII TEXT COLORS
RED    = "\033[0;31m"
GREEN  = "\033[0;32m"
YELLOW = "\033[0;33m"
WHITE  = "\033[0;37m"

FASTNET_MAX_MSG_SIZE = 80

FASTNET_DIVISORS = [1, 10, 100, 1000]

FASTNET_FMT_BYTES = [ # total size of channel data in bytes
    0, 4, 4, 5,
    6, 0, 0, 0,
    4, 0, 0, 6,
    0, 0, 0, 0
]

def fastnet_crc(data, size, init=0):
    crc = init
    for i in range(size):
        crc += data[i]
    crc = (0x100 - crc) & 0xFF
    return crc

# ID from which YDPG-01 is sending data to Fastnet
fastnet_src = 0x70
# Fastnet header buffer (destination, source, msg size, msg type, CRC )
fastnet_header = bytearray([0xFF, fastnet_src, 0x00, 0x01, 0xC2])
# Fastnet transmit data buffer
fastnet_buf = bytearray(FASTNET_MAX_MSG_SIZE + 1) # + CRC byte
fastnet_buf_size = 0

# adds data to Fastnet transmission
def fastnet_add_channel(ch, fmt, size, divisor, value):
    global fastnet_buf, fastnet_buf_size
    global FASTNET_DIVISORS, FASTNET_FMT_BYTES
    
    ret = FASTNET_FMT_BYTES[fmt]
    if ret <= 2 or (len(fastnet_buf) - fastnet_buf_size) <= ret:
        return -1 # unknown format or not enough space in transmit buffer
    
    offset = fastnet_buf_size
    fastnet_buf[offset] = ch
    fastnet_buf[offset + 1] = (fmt&0xF) | ((size&0x3) << 4) | ((divisor&0x3) << 6)
    offset += 2
    
    value = round(value * FASTNET_DIVISORS[divisor])
    
    # allow formats 1 and 8 only
    if fmt == 1:
        if (value < 0):
            value = 0x10000 + value
        fmt = 8
    if fmt == 8:
        fastnet_buf[offset] = (value >> 8) & 0xFF
        fastnet_buf[offset + 1] = value & 0xFF
        fastnet_buf_size += ret
    return ret

# sends all added channels to Fastnet
def fastnet_flush():
    global fastnet_header, fastnet_buf, fastnet_buf_size
    
    # set message size and calculate CRCs
    fastnet_header[2] = fastnet_buf_size
    fastnet_header[4] = fastnet_crc(fastnet_header, 4)
    fastnet_buf[fastnet_buf_size] = fastnet_crc(fastnet_buf, fastnet_buf_size, 0x56)
    
    # send fastnet channels
    uart_tx.write(fastnet_header)
    uart_tx.write(fastnet_buf, 0, fastnet_buf_size + 1) # + 1 byte CRC
    fastnet_buf_size = 0
    led.green() # indicate fastnet transmission

def fastnet_add_relative_wind(awa, aws):
    fastnet_add_channel(0x51, 8, 0, 0, awa)
    fastnet_add_channel(0x50, 1, 0, 2, aws)

# deinitializes NMEA0183 object created in boot.py
n0183.deinit()
# sets UART settings according to Fastnet specification
uart_rx.init(baudrate=11000, bits=8, stop=2, parity=1, rx_invert=True, timeout=100)
uart_tx.init(baudrate=11000, bits=8, stop=2, parity=1)

led.state(led.RED)

# successful data reception flag
was_received = False

# demo AWA angles
demo_angles = [-40, 0, 40, 0]
demo_index = 0

while True:
    data = uart_rx.read(5)
    if data != None and len(data) == 5 and fastnet_crc(data, 4) == data[-1]:
        src = data[1]
        size = data[2]
        cmd = data[3]
        data = uart_rx.read(size + 1)
        
        # check that message is successfuly received and not sent from YDPG
        if src != fastnet_src and data != None and len(data) == (size + 1):
            if fastnet_crc(data, len(data)-1, 0x56) == data[-1]:
                # here we can process received messages
                was_received = True

    # wait 10 milliseconds to be sure that all messages has been received and
        # we can safely send messages    
    time.sleep(0.01) 
    if uart_rx.any() == 0 and was_received:
        was_received = False
        fastnet_add_relative_wind(demo_angles[demo_index], demo_index)
        demo_index = (demo_index + 1) % len(demo_angles)
        fastnet_flush()

To test the code, we went boating and a colleague connected the Python Gateway to his Android phone via a USB cable (the phone must support USB OTG).

Moreover, he installed the free Micro REPL (MicroPython IDE for Android) software on his phone. He found it a bit crude, but it is still quite usable and much more convenient than using a separate code editor and terminal program to access the console.

Python Gateway has shown itself as a device capable of solving atypical tasks, easily creating test code and debugging literally on the go, and even without a computer. And we extend our respect to B&G, whose devices have worked on the boat for over 20 years, and have earned the sincere affection of their owner.

Permanent link...

 

 

 April 8, 2024  Engine Gateway YDEG-04 Update

Instant fuel consumption for Volvo Penta D3 and D4, and calculated fuel rate for any engine!

Volvo Penta D2-40 fuel rate

In the new firmware version we have added the RUDDER=ON/OFF parameter to disable the rudder angle data messages. Now this data is only available for the SmartCraft protocol (Mercury and MerCruiser engines) and some engines send it with a constant value of 0.

For Volvo Penta D3 and D4 engines produced in 2003-2004 (Volcano protocol, EVC-MC, requires VOLCANO=ON setting to enable) we have managed to find instantaneous fuel consumption data thanks to our users. These are not present explicitly and require calculation. To correct the calculations we have added the parameter FUEL_RATE_MUL=0.7 (the default value, the coefficient can be from 0.001 to 65.5, 0 disables the calculation of instantaneous fuel consumption). We would be very grateful for your feedback.

The Engine Gateway also provides fuel consumption data for J1939 (Volvo Penta, Yanmar and many other), VW Marine, MEFI, BRP, Mercury and MerCruiser engines.

Unfortunately, not all engine models provide instantaneous fuel consumption data. Users have long suggested that we add simulation of this data as a function of engine speed. This makes sense: after 10 years of boat ownership, many people have only cruise speed consumption in their heads, and even that is approximate. And if the boat is rented, users may not even have this data. Of course, it would be good to take into account engine load or gear engaged, but often such data is not available on simple engines either.

Well, meet the extremely confusing parameter FUEL_RATE_FAKE=ON. Let's try to explain how to use it. Let's start with the fact that it enables simulation of instantaneous fuel consumption only for engines 0 and 1 (left and right) and uses the settings of fuel tanks 8 and 9 for this purpose (for some reasons, we decided not to create separate settings, sorry).

So we have the settings:

   ENGINE_0=0
   TANK_CAPACITY_8=6000
   FUEL_RATE_MUL=30.0
   FUEL_RATE_FAKE=ON

They mean that the left or single engine (0) has the maximum 6000 RPM (set in the TANK_CAPACITY_8 setting) and it corresponds to a maximum fuel consumption of 30 US gallons per hour (in FUEL_RATE_MUL, yes exactly in gallons, not litres as it was above).

With these settings, at 3000 RPM, the Gateway will report 15 US gallons per hour, or 56.78 litres per hour. Of course, if you set the FUEL_RATE_FAKE=OFF setting, the other settings will mean what they should mean in normal operation.

Since engines rarely have a linear relationship of fuel rate to RPM, a 12-point calibration curve can be set using the TANK_CALIBRATION_8 and TANK_CALIBRATION_9 settings. It is assumed that the values 0 and 100% do not need to be calibrated. And the setting line contain adjusted values for 4,8,12,20,30,40,50,60,60,70,70,80,90 and 95 percent.

Suppose our setup looks like this:

   TANK_CALIBRATION_8=5,7,11,19,16,37,46,54,66,78,87,91

The first number (5) is the adjusted value for 4%. In the case of FUEL_RATE_FAKE=ON, it is interpreted as follows: 4% of the maximum RPM (specified in TANK_CAPACITY_8) corresponds to 5% of the maximum fuel consumption (FUEL_RATE_MUL). The other values are interpolated accordingly. Previously, calibration settings could only contain sequentially increasing values (otherwise it did not make sense for fuel tanks), but in the current version we have removed this check, because engines may have lower fuel consumption with increasing RPM.

You can use our calibration spreadsheet for the Engine Gateway (see Picture 2 below) to calculate the calibration parameters, but we will need to enter fuel consumption instead of liquid volume and percent of maximum RPM in the "Device readings" column. We recommend this article if you are not familiar with engine performance curves.

Volvo Penta D2-40 fuel rate

Picture 1. Volvo Penta D2-40 fuel rate

Let's take the Volvo Penta D2-40F engine as an example (see Picture 1 above, we will use the middle row) or the full source document. The reference data shows a maximum consumption of 9.5 litres per hour at 3200 rpm and at maximum load. To make it easier for us to fill in the table, let's assume that the maximum engine speed is 4000 RPM and the consumption is 10 litres per hour. Then it is very easy to convert the data into percentages: 1200 RPM is 30% from 4000, and so on with 5% increments (200 RPM).

Calibration spreadsheet

Picture 2. Calibration spreasheet with Volvo Penta D2-40 data

And here's our calibration, note that we must change the capacity setting to maximum RPM when coping to the YDEG.TXT file:

   TANK_CAPACITY_8=4000
   TANK_CALIBRATION_8=1,2,4,6,9,15,24,38,60,95,98,99

All that remains is to add a maximum fuel flow rate parameter (convert 10 litres per hour to gallons):

   FUEL_RATE_MUL=2.641
   FUEL_RATE_FAKE=ON

Do you need fuel rate simulation in our other engine gateways, the Outboard Gateway or the J1708 Gateway? Are you planning to use this feature? Please send us your feedback!

The firmware update is version 1.41 and is available for download.

Permanent link...

 

Next articles:

All news...