# Bluerov2, Waterlinked & QGis

**URL:** <https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240>\
**Category:** Adventure Log\
**Tags:** positioning\
**Created:** [August 22, 2018, 10:25am UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240 "2018-08-22T10:25:35Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![alexis](https://avatars.discourse-cdn.com/v4/letter/a/838e76/32.png) [@alexis](https://discuss.bluerobotics.com/u/alexis)\
**Post date:** [August 22, 2018, 10:25am UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/1 "2018-08-22T10:25:35Z")

</div>

Hello everybody!

I just wanted to share with you our other day mission.

We were filming the correct placement of weights in a port. We tried the [Waterlinked](https://waterlinked.com/) positioning system with [QGis](https://qgis.org/), an app that lets you visualize go-referenced images.

The result? The rov getting video with the position visualized over the plan of the port: [Bluerov2, Waterlinked & QGis - YouTube](https://youtu.be/7XwM9eRq7LE)

Note: sorry about the video frame rate. I had to reduce it at 15fps because the computer was not powerful enough.

To export the position I used the Waterlinked python [examples](https://github.com/waterlinked/examples) to create an NMEA sentence. After I used [socat](http://www.dest-unreach.org/socat/doc/socat.html) to create a virtual port that QGis could easily read.  
If anybody is interested in the python script let me know! 🙂

Regards,  
Alex

---

<div class="post-metadata">

**Author:** ![Dale\_A](https://avatars.discourse-cdn.com/v4/letter/d/838e76/32.png) [@Dale\_A](https://discuss.bluerobotics.com/u/Dale_A)\
**Post date:** [August 22, 2018, 11:38am UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/2 "2018-08-22T11:38:53Z")

</div>

Nice work. How stable was the software?

---

<div class="post-metadata">

**Author:** ![alexis](https://avatars.discourse-cdn.com/v4/letter/a/838e76/32.png) [@alexis](https://discuss.bluerobotics.com/u/alexis)\
**Post date:** [August 22, 2018, 12:16pm UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/3 "2018-08-22T12:16:33Z")

</div>

Hello @Dale_A  
What part of the software are you talking about? The position reception of the system?  
Actually it is pretty stable. It is true that sometimes the position can be lost for a few seconds but as you can see on the video we did not get issues.  
However we did not test the 100m. The ROV was about 50 meters for the longest distance.

Here is a photograph of the location:

 ![IMG_3153](https://us1.discourse-cdn.com/flex019/uploads/bluerobotics/original/2X/1/1cf68eb8ef11b0c92f082e2144eca5754b6815fc.jpeg)

Alex

---

<div class="post-metadata">

**Author:** ![Dale\_A](https://avatars.discourse-cdn.com/v4/letter/d/838e76/32.png) [@Dale\_A](https://discuss.bluerobotics.com/u/Dale_A)\
**Post date:** [August 22, 2018, 12:19pm UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/4 "2018-08-22T12:19:26Z")

</div>

The QGIS water linked positioning. If it’s pretty stable I’d be interested in having a further chat about your setup. ( I have a couple of harbour jobs that might become regular runs)

---

<div class="post-metadata">

**Author:** ![alexis](https://avatars.discourse-cdn.com/v4/letter/a/838e76/32.png) [@alexis](https://discuss.bluerobotics.com/u/alexis)\
**Post date:** [August 22, 2018, 12:25pm UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/5 "2018-08-22T12:25:15Z")

</div>

No problem. What are your questions? Do you need an email?

---

<div class="post-metadata">

**Author:** ![rjehangir](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.bluerobotics.com/rjehangir/32/671_2.png) [@rjehangir](https://discuss.bluerobotics.com/u/rjehangir)\
**Post date:** [August 22, 2018, 1:29pm UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/6 "2018-08-22T13:29:16Z")

</div>

Alexis - awesome! Nice work! This looks really good and shows off the system capabilities well.

For reference, do you know what the computer you were using was that you had to reduce to 15fps? That would be helpful to know to help us make sure we have accurate minimum system requirements.

-Rusty

---

<div class="post-metadata">

**Author:** ![jwalser](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.bluerobotics.com/jwalser/32/2234_2.png) [@jwalser](https://discuss.bluerobotics.com/u/jwalser)\
**Post date:** [August 22, 2018, 2:51pm UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/7 "2018-08-22T14:51:14Z")

</div>

What’s the application with the BROV2 icon?

---

<div class="post-metadata">

**Author:** ![alexis](https://avatars.discourse-cdn.com/v4/letter/a/838e76/32.png) [@alexis](https://discuss.bluerobotics.com/u/alexis)\
**Post date:** [August 23, 2018, 6:18am UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/8 "2018-08-23T06:18:03Z")

</div>

Hello!

@rjehangir  
The system works perfectly with no lag when using the water positioning system, QGis and video recording from QGroundControl. It is only when in addition of all that I make a double screen record at 24 fps that the GQC stops streaming video fluently (it lags a little every 4-5 seconds and we prefer to have a perfect control of the ROV).  
The computer I use is a [Getac](http://www.getac.com/) running with:

- Ubuntu 16.04
- Intel® Core™ i5-4300U CPU @ 1.90GHz × 4
- Intel® Haswell Mobile
- 8Gb RAM

I actually switched to Linux because we had lot more of performance issues (and software bugs) with windows.

@jwalser  
It is an app I wrote in Python to start all the process we may use during missions. It looks like like this:

 ![Screenshot%20from%202018-08-23%2010-15-22](https://us1.discourse-cdn.com/flex019/uploads/bluerobotics/original/2X/6/6e3e1f17550f053c14a1cd981f41d7eea1aeb9ca.png)

You basically have some fields for variables that may usually change and some toggles to activate and deactivate functions.  
At the beginning of a mission we press start and everything setups automatically. We discovered that we lost a lot of time searching for all the functionalities so this makes things easier, faster and more organized. Some of the things it does:

- Executes different apps simultaneously.
- Windows placement and resizing.
- Creates a directory with the day an hour of the mission where all files (from QGC, NMEA logs, screen captures, etc) are saved.
- Mouse bigger resizing. (It may seem stupid but actually in the boat on the sea with the sun it was hard to find it sometimes).

To configure it you have to complete a python file that looks like this:

```
#!/usr/bin/python3
from collections import OrderedDict
from ui import *
from output_NMEA import process_position
from input_NMEA import input_position
from executing import *
import os

def start():
...
def end():
...

### SETUP VARIABLES ###
fields = OrderedDict([("Path to QGC .appImage:", "/home/id-ocean/Documents/QGroundControl/QGroundControl.AppImage"),
                    ("Path to VLC .sdp file:", "/home/id-ocean/Documents/QGroundControl/secondStream.sdp"),
                    ("Path to save files:", "/home/id-ocean/Documents/SortiesROV/last"),
                    ("Serial output:", "/dev/ttyS9"),
                    ("Serial input:", "/dev/ttyUSB0")])

toggles = OrderedDict([("QGroundControl:",
                        Toggle(value=True, method=execute,
                            args=["${Path to QGC .appImage:}"],
                            kwargs={'shell':True, 'size':['0', '0', '40', '70'], 'description':"QGroundControl"})),
                    ("Second Window",
                        Toggle(value=False, method=execute,
                            args=["vlc ${Path to VLC .sdp file:}"],
                            kwargs={"size":['50', '0', '100', '100']})),
                    ("Screen Record",
                        Toggle(value=False, method=execute,
                            args=['simplescreenrecorder --start-hidden'],
                            kwargs={"beforeTerminate" : ctrlR, "afterLaunch" : ctrlR})),
                    ("Output NMEA",
                        Toggle(value=False, method=process_position,
                            args=['http://192.168.2.94'],
                            kwargs={'serial_name':'${Serial output:}',
                                'path':'${Path to save files:}',
                                'GLOBAL': True})),
                    ("Input NMEA",
                        Toggle(value=False, method=input_position,
                            args=['http://192.168.2.94', '${Serial input:}'])),
                    ("QGIS",
                        Toggle(value=False, method=execute,
                            args=['qgis'])),
                    ("Google",
                        Toggle(value=True, method=execute,
                            args=['/usr/bin/google-chrome-stable --new-window %U http://192.168.2.94/#/visualization http://192.168.2.2:2770/system'],
                            kwargs={'size':['0', '0', '100', '100'], 'description':"Google"}))])

if __name__ == ' __main__':
    iconPath = os.path.dirname(os.path.abspath( __file__ )) + '/icon.gif'
    MainWindow(fields, toggles, icon=iconPath, startMethod=start, endMethod=end, title='BlueROV Software Setup')

```

I have created some documentation in french but I you consider it could be useful for other teams I could maybe create a public repository or similar with the complete code and more extensive doc in English.

Regards,  
Alex

---

<div class="post-metadata">

**Author:** ![Geraud](https://avatars.discourse-cdn.com/v4/letter/g/3ab097/32.png) [@Geraud](https://discuss.bluerobotics.com/u/Geraud)\
**Post date:** [August 23, 2018, 9:05am UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/9 "2018-08-23T09:05:47Z")

</div>

Hi Alexis,

Thank you very much for your help.  
Could you send me the Batch file to create serial virtual port for Python.?  
I thank you in advance.

Best regards,

Géraud NAANKEU WATI

---

<div class="post-metadata">

**Author:** ![alexis](https://avatars.discourse-cdn.com/v4/letter/a/838e76/32.png) [@alexis](https://discuss.bluerobotics.com/u/alexis)\
**Post date:** [August 23, 2018, 10:01am UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/10 "2018-08-23T10:01:07Z")

</div>

Hello @Geraud

I am just going to do a pull request to complete the script [nmeaoutput.py](https://github.com/waterlinked/examples/blob/master/nmeaoutput.py) with the virtual port integration.

Here is what the python script looks like:

```
#!/usr/bin/python3
"""
Read position from Water Linked Underwater GPS convert to NMEA and send to serial
port or on UDP socket
"""
from __future__ import print_function
import requests
import argparse
import json
import time
from math import floor
import socket
import serial
import sys
import subprocess
import os

def get_data(url, stderr=False):
    try:
        r = requests.get(url, timeout=0.2)
    except requests.exceptions.RequestException as exc:
        if stderr:
            print("Exception occured {}".format(exc))
        return None

    if r.status_code != requests.codes.ok:
        if stderr:
            print("Got error {}: {}".format(r.status_code, r.text))
        return None

    return r.json()

def get_global_position(base_url):
    return get_data("{}/api/v1/position/global".format(base_url))

def get_master_position(base_url):
    return get_data("{}/api/v1/position/master".format(base_url))

def gen_gga(time_t, lat, lng, fix_quality, num_sats, hdop, alt_m, geoidal_sep_m, dgps_age_sec=None, dgps_ref_id=None):
    # Code is adapted from https://gist.github.com/JoshuaGross/d39fd69b1c17926a44464cb25b0f9828
    hhmmssss = '%02d%02d%02d%s' % (time_t.tm_hour, time_t.tm_min, time_t.tm_sec, '.%02d' if 0 != 0 else '')

    lat_abs = abs(lat)
    lat_deg = lat_abs
    lat_min = (lat_abs - floor(lat_deg)) * 60
    lat_sec = round((lat_min - floor(lat_min)) * 1000)
    lat_pole_prime = 'S' if lat < 0 else 'N'
    lat_format = '%02d%02d.%03d' % (lat_deg, lat_min, lat_sec)

    lng_abs = abs(lng)
    lng_deg = lng_abs
    lng_min = (lng_abs - floor(lng_deg)) * 60
    lng_sec = round((lng_min - floor(lng_min)) * 1000)
    lng_pole_prime = 'W' if lng < 0 else 'E'
    lng_format = '%03d%02d.%03d' % (lng_deg, lng_min, lng_sec)

    dgps_format = '%s,%s' % ('%.1f' % dgps_age_sec if dgps_age_sec is not None else '', '%04d' % dgps_ref_id if dgps_ref_id is not None else '')

    result = 'GPGGA,%s,%s,%s,%s,%s,%d,%02d,%.1f,%.1f,M,%.1f,M,%s' % (hhmmssss, lat_format, lat_pole_prime, lng_format, lng_pole_prime, fix_quality, num_sats, hdop, alt_m, geoidal_sep_m, dgps_format)
    crc = 0
    for c in result:
        crc = crc ^ ord(c)
    crc = crc & 0xFF

    return '$%s*%0.2X' % (result, crc)

def send_udp(sock, ip, port, message):
    sock.sendto(message, (ip, port))

#Create a virtual port in the computer. Valid for linux with socat installed.
class VirtualPort:
    def __init__ (self, password, origin='/dev/ttyS8', end='/dev/ttyS9'):
        self.origin = origin
        self.end=end
        self.p = subprocess.Popen(['/bin/bash', '-c', 'echo %s|sudo -S sudo socat PTY,link=%s PTY,link=%s' % (password, self.origin, self.end)], preexec_fn=os.setpgrp)
        time.sleep(1) #Wait for process to start
        subprocess.call(['/bin/bash', '-c', 'echo %s|sudo -S sudo chmod 666 %s' % (password, self.origin)])
        subprocess.call(['/bin/bash', '-c', 'echo %s|sudo -S sudo chmod 666 %s' % (password, self.end)])
        return

    def stop(self):
        os.killpg(self.p.pid, signal.SIGTERM)
        return

    def write(self, message):
        message = "echo \"" + message + "\" > " + self.origin
        subprocess.call(["/bin/bash", "-c", message])
        return

def main():
    parser = argparse.ArgumentParser(description= __doc__ )
    parser.add_argument('-u', '--url', help='IP/URL of Underwater GPS kit. Typically http://192.168.2.94', type=str, default='http://demo.waterlinked.com')
    parser.add_argument('-m', '--master', help='Print master position insstead of global', action="store_true")
    parser.add_argument("-v", "--verbose", help="Print NMEA sentences", action="store_true")
    # UDP options
    parser.add_argument('-i', '--ip', help="Enable UDP output by specifying IP address to send UDP packets. Default disabled", type=str, default='')
    parser.add_argument('-p', '--port', help="Port to send UDP packet", type=int, default=5000)
    # Serial port options
    parser.add_argument('-s', '--serial', help="Enable serial port output by specifying port to use. Example: '/dev/ttyUSB0' or 'COM1' Default disabled", type=str, default='')
    parser.add_argument('-b', '--baud', help="Serial port baud rate", type=int, default=9600)
    #Virtual port options
    parser.add_argument('-w', '--password', help='Password to execute sudo commands', type=str, default='')
    parser.add_argument('-o', '--origin', help='Virtual port to write in', type=str, default='/dev/ttyS7')
    parser.add_argument('-e', '--end', help='Virtual port where to read from', type=str, default='/dev/ttyS9')
    args = parser.parse_args()

    if not (args.ip or args.serial or args.password):
        parser.print_help()
        print("ERROR: Please specify either serial port to use, ip address to use or a password to create a virtual port")
        sys.exit(1)

    print("Using base_url: {}".format(args.url))

    ser = None
    if args.serial:
        print("Serial port: {}".format(args.serial))
        ser = serial.Serial(args.serial, args.baud)

    sock = None
    if args.ip:
        print("UDP: {} {}".format(args.ip, args.port))
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    virtualPort = None
    if args.password:
        print("Sending data from virtual port '{}' to '{}'".format(args.origin, args.end))
        virtualPort = VirtualPort(args.password, args.origin, args.end)

    while True:
        if args.master:
            pos = get_master_position(args.url)
        else:
            pos = get_global_position(args.url)

        if pos:
            #print("Current global position lat:{} lon:{}".format(pos["lat"], pos["lon"]))
            sentence = gen_gga(time.gmtime(), pos["lat"], pos["lon"], 1, 0, 0, 0, 0) #added 1 argument to fixed quality or position was rejected by other software
            if args.verbose:
                print(sentence)
            if sock:
                send_udp(sock, args.ip, args.port, sentence)
            if ser:
                ser.write(sentence + "\n")
            if virtualPort:
                #Add \ to make the simbol $ writable
                dollar_sentence = '\\' + sentence
                virtualPort.write(dollar_sentence)
            time.sleep(0.2)

if __name__ == " __main__":
    main()

```

More specifically responding to your question, to create a virtual port I used [socat](http://www.dest-unreach.org/socat/doc/socat.html).  
To install it in linux write in a terminal:  
`$ sudo apt-get update && sudo apt-get install socat`

And after to create two connected virtual ports write:  
`$ sudo socat PTY PTY`

You can check the creation of two virtual ports by running from another terminal:  
`$ ls /dev/pts*`  
Before and after executing socat. You will see the apparition of two ports, in my case 4 and 18:

 ![Screenshot%20from%202018-08-23%2013-55-34](https://us1.discourse-cdn.com/flex019/uploads/bluerobotics/original/2X/c/c9557df54bd997f5f7f94481a77b98e0270b75cd.png)

My app only reads from ttyS\_X ports so I created a link with:  
`$ sudo socat PTY,link=/dev/ttyS8 PTY,link=/dev/ttyS9`

Finally you have to edit permissions to write and read in the ports so run from a second terminal:  
`$ sudo chmod 666 /dev/ttyS8`  
`$ sudo chmod 666 /dev/ttyS9`

You can check the communication by writing on this second terminal:  
`$ cat < /dev/ttyS9`  
and on a third new one:  
`$ echo "Hello World" > /dev/ttyS8`

_Note_: this is also necessary even if you do not create the link. If you use the pts/X ports you also have to edit permisions to write or read:  
`$ sudo chmod 666 /dev/pts/18`  
`$ sudo chmod 666 /dev/pts/4`

_Note2:_ You must keep open the socat terminal to maintain communication. When you close it or press ctrl+c the process is interrupted and the virtual ports destroyed.

Regards,  
Alex

---

<div class="post-metadata">

**Author:** ![Donne96](https://avatars.discourse-cdn.com/v4/letter/d/ad7895/32.png) [@Donne96](https://discuss.bluerobotics.com/u/Donne96)\
**Post date:** [March 22, 2021, 4:05pm UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/11 "2021-03-22T16:05:05Z")

</div>

Hi,

Although it has been a while since this has been posted I would like to follow up on this. I am currently trying to implement a plugin of the waterlinked underwater GPS into QGIS on a windows system.  
I aim to use a virtual port setup as is used in this case, however I do not get a connection to the GPS system through QGIS (created a virtual COM13). The attempt is terminated before a connection is established.  
Since you indicated that you did have a setup for windows, although with some stability issues. Could you indicate how you managed to create this connection?

Kind regards,  
Donné

---

<div class="post-metadata">

**Author:** ![adurix](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.bluerobotics.com/adurix/32/12375_2.png) [@adurix](https://discuss.bluerobotics.com/u/adurix)\
**Post date:** [March 24, 2021, 12:54am UTC](https://discuss.bluerobotics.com/t/bluerov2-waterlinked-qgis/3240/12 "2021-03-24T00:54:46Z")

</div>

Hello Alex how are you  
I m Alain Durot how i can get the python script for get NMEA for waterlinked

thanks
