Friday, February 26, 2016

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:

ls /dev/tty*

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

ls /dev/tty* 

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:

$IIXDR,DST_FAIL,4.5*7F

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[0].strip('$')  
     cs0 = dst_split[1][:-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[0]  
   
       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[0] + "," + str(float(dst_vars[1]) * 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[1]  
           vlwfirst = 0  
         trip = float(dst_vars[1]) - float(vlwinit)  
         vlw = "VWVLW," + dst_vars[1] + ",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  
   

2 comments:

  1. Hello, thanks for the post it's really interesting!
    Hope it's not to late for asking...

    I'm connecting a DST800 Transductor to a Raspberry Pi and to connect between them I bought the same RS-422 Adapter:
    https://www.amazon.es/WINGONEER-USB-485-adaptador-convertidor-Windows/dp/B016IG6X7I/ref=pd_sbs_147_2?_encoding=UTF8&psc=1&refRID=029NQYDZDYS6QBPTP692

    The problem is when I try to read the NMEA sentences with a basic C# code:
    static void Main(string[] args)
    {
    SerialPort sp = new SerialPort("/dev/ttyUSB0", 4800);
    sp.ReadTimeout = 1000;
    sp.Open();

    while(true) {
    int i = sp.ReadChar();
    char c = (char) i;
    Console.Write(c);
    }

    }


    The data recived it's incorrect, doesn't have any sence and I don't know for what reason could be.
    Here I upload a picture with the recived data in Ubuntu:
    https://www.dropbox.com/s/x0ro4h4iu86jjuw/WhatsApp%20Image%202017-07-16%20at%2013.43.35.jpeg?dl=0

    What do you think can be happen? It is a wrong USB adapter? Or needs a post software conversion after reciving data?

    Thanks you,
    Martí

    ReplyDelete
    Replies
    1. Can you confirm that you have the NMEA-0183 DST800, and not the NMEA-2000?

      Delete