Connect Any NMEA-1083 Device to a Raspberry Pi
Just a quick update on my previous post about NMEA-0183 on the Arduino. Since I'm no longer using the Arduino, here's a quick down and dirty on how to interface with any NMEA-0183 device. The protocols in that post remain the same (RS-232, 422, 485, etc...), and the easiest way to connect them to a Raspberry Pi is through a USB to RS-422 adapter. This is the one I'm using, and I also have this one here for a later project. It's real self-explanatory: Plug in NMEA Out + into B, NMEA Out - into A, and it goes (quick disclaimer: I haven't actually tried it with an NMEA device yet--I've been using my Arduino to simulate my depth sounder, and so those wires may actually have to be swapped with an actual device).
Once it's connected, you need to know the USB port's address in order to find that information. This step is really really easy. Make sure that you have it unplugged, and type this into the terminal:
and that will list a bunch of lines. You're main focus will be at the end of the list, with something like ttyUSB0 or something like that. Next, plug in your USB device (the NMEA-0183 device need not be powered on) and type in
You should see an additional USB line at the end. Obviously, that's your NMEA's USB address. Make note of this.
Before we go on, a quick word on these addresses: if you have more than one USB device plugged in, and you unplug one of them assigned to USB0 or USB1, then the other USB's address will shift down by 1--such that your script won't work. So basically, keep everything plugged in that you'll be using. If you unplug something, you'll have to rework the script to reflect the new address.
The script for my DST-800 plugged into my Raspberry Pi is below, but it can pretty much work for everything. Here's the basic architecture:
It reads a single line coming in from the USB port, extracts the actual sentence by splitting it by the * symbol and removing the $ sign. It then calculates it's checksum, and compares that to the given checksum. If it's valid (meaning it's a valid NMEA sentence), it splits the sentence into its comma separated values, and runs specific code depending on the title.
For example, I convert the temperature from Celsius to Fahrenheit, and add a .5 depth sounder offset to that sentence.
Whenever it receives a sentence, it runs its specific code, then it sends it to a UDP port to another script for further processing and compilation. Now, one of the reasons why I wanted to switch from the Arduino to the Raspberry Pi was for fault protection. I'm checking for a valid NMEA sentence, but what if the sensor wigs out and stops sending data?
If it goes for more than 10 seconds without receiving a depth sentence, it assumes the DST-800 has failed (or circuit breaker popped, or whatever). In this case, it creates a new NMEA sentence to let me know. Example:
which then goes through the gonkulator script (more on that to follow), through kplex, and into OpenCPN or whatever device I'm using since this is a valid NMEA sentence, and it tells me that the DST hasn't sent a sentence in 4.5 minutes. That will continue to tick up forever. If I see that, then I know I need to go reset the circuit breaker or something like that.
More to follow on what happens next.
The dst.py script:
import sys import serial import math import operator import time import socket DST_IP = "127.0.0.4" DST_PORT = 5005 vlwfirst = 1 vlwinit = 0.0 t_dpt = 0 t_print = time.time() t_fail = 0.0 sddpt = '' mtw = '' yxmtw = '' vwvhw = '' vlw = '' vwvlw = '' ser = serial.Serial('/dev/ttyUSB0', 4800, timeout=1) while True: hack = time.time() dst_raw = ser.readline() if "*" in dst_raw: dst_split = dst_raw.split('*') dst_sentence = dst_split.strip('$') cs0 = dst_split[:-2] cs = format(reduce(operator.xor,map(ord,dst_sentence),0),'X') if len(cs) == 1: cs = "0" + cs if cs0 == cs: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) dst_vars = dst_sentence.split(',') title = dst_vars if title == "SDDPT": sddpt = dst_raw[:-2] sock.sendto(sddpt, (DST_IP, DST_PORT)) t_dpt = hack t_fail = 0.0 if title == "YXMTW": mtw = dst_vars + "," + str(float(dst_vars) * 9 / 5 + 32) + ",F" cs = format(reduce(operator.xor,map(ord,mtw),0),'X') if len(cs) == 1: cs = "0" + cs yxmtw = "$" + mtw + "*" + cs sock.sendto(yxmtw, (DST_IP, DST_PORT)) if title == "VWVHW": vwvhw = dst_raw[:-2] sock.sendto(vwvhw, (DST_IP, DST_PORT)) if title == "VWVLW": if vlwfirst == 1: vlwinit = dst_vars vlwfirst = 0 trip = float(dst_vars) - float(vlwinit) vlw = "VWVLW," + dst_vars + ",N," + str(trip) + ",N" cs = format(reduce(operator.xor,map(ord,mtw),0),'X') if len(cs) == 1: cs = "0" + cs vwvlw = "$" + vlw + "*" + cs sock.sendto(vwvlw, (DST_IP, DST_PORT)) if (hack - t_dpt) > 10.0: if (hack - t_print > 1.0): t_fail += 1.0 dst_sentence = "IIXDR,DST_FAIL," + str(round(t_fail / 60, 1)) cs = format(reduce(operator.xor,map(ord,dst_sentence),0),'X') if len(cs) == 1: cs = "0" + cs dst_sentence = "$" + dst_sentence + "*" + cs sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(dst_sentence, (DST_IP, DST_PORT)) t_print = hack