The Big Bash 2015

We went on our first boat trip of the year. We left our home dock on Friday, April 24th, and tied up to the docks at the Port of Poulsbo that afternoon. On Saturday I presented to the Cascadia sailors also in attendance, discussing Building your own Chartplotter with AIS, Radar, and followed up by listening in to presentations on sailing much further north than we have tried — all the way up to Alaska in most cases.  On Sunday, April 26th, we returned home, this time going around the east side of Vashon Island to avoid the currents, tying up at home in the mid-afternoon hours.

The map below shows the path as captured by our GPS location on our OpenCPN chartplotter with the purple track being the north-bound trip, and the yellow track being the south-bound return trip. There are a few gaps where I rebooted the machine for some reason or another, but it still shows the path we took quite well. I just extracted the route from OpenCPN, and used gpsvisualizer.com to generate KML files from them. Then I logged into Google Maps, and imported the KML files into a custom public map.

Preparation

Prior to the trip there was a ton of work to do.

Last summer we had a bit of an electrical issue on board, and lost our alternator and house bank of batteries. While we had replaced all the batteries, and purchased and mounted a new alternator, it wasn’t hooked up properly. I couldn’t figure it out as the wiring did not match up to any of the diagrams on the instructions that came with it. So before we went on this trip, I wanted to have a professional marine electrician finish up the installation.

I improvised a charging solution so that the engine wouldn’t die on the way, and drove the boat over to ModuTech Marine. At first even the electrician was confused as to what the instructions were showing. But he then discovered the issue: The instructions were showing three completely different options for installing the alternator. They just didn’t actually state that they were options, so I was trying to do all three at once! Less than an hour later we were done, and spent a couple hours cruising around Commencement Bay, validating that the batteries were, in fact, being charged properly.

Mechanically the boat was ready to go. Since this was the first time the boat had been running for any considerable time since last August, it was nice to just be out on the water.
20150420_125712

I also had to prepare for the presentation I was giving. I have been working on running OpenCPN on these low-powered systems such as the Raspberry Pi and CubieTruck for quite some time, but because these are active projects and platforms things change. Taking the opportunity, I started over from scratch, building the chartplotter on a Raspberry Pi 2, the most recent device. Over a couple days’ span, I had built a new system, running the most recent version of OpenCPN (4.1.412), with GPS location.

I also had an AIS receiver module that I planned on hooking up while we were underway and within range of signals. I also threw together a Powerpoint slide deck for the presentation, plus a bunch of other examples of hardware. The presentation was ready as well!

The Trip North

The trip north started out under overcast skies. We got a bit of a later start than originally planned, but did get some extra water in the tanks, and we were still well within the timeframe to catch the ebb current to assist our way north. We cruised through Colvos Passage at nearly 8 kts over ground, 6 through the water, with a substantial front to the south following us all the way.

The conditions were nice enough, though, that I was able to have a few hours to play with the AIS module, and caught our first ever AIS contact just as we were coming out of Colvos. It was a ferry leaving the Southworth Docks, and it showed up great! I wasn’t sure how to take a screenshot, so I simply took a picture with my phone:
First AIS Contact

Once we got to the north end of Blake Island, that front that was chasing us caught up. 2-3′ chop built up, and the ride got quite bumpy, right as we were entering into Rich Passage. Of course the Bremerton Ferry heading in at the same time didn’t do anything to reduce the stress of the situation. But once we rounded the southwest corner of Bainbridge island and turned more north, the wind pushed us straight into Liberty Bay. It was still windy and bumpy, but much better. The boat handles these conditions well enough that everyone was able to at least get a small rest in:
20150424_122447

An hour or so later we were docking at Poulsbo. I didn’t have the bow thruster working (did I mention we have had some electrical issues?) so it was a matter of lining the boat up on the dock and letting the wind push us in. We docked the boat as if we’d been doing it every weekend all year long! With the boat safely tied up to the docks, with shore-power and water, we were able to turn on a few movies and assist the rest of the Cascadian sailors as they arrived. Soon appetizers were shared and everyone was having a good time on each other’s boats.

20150424_161231

Poulsbo

The next morning we woke up to fog and glassy conditions.

20150425_07070420150425_07065020150425_074213

There is a ton to do for everyone in Poulsbo. The bakery is great. The bookstores are great. There are great restaurants. There’s even a great little aquarium with free (donations) admission. In short, it’s a great destination.

My AIS presentation went well, I think. It’s difficult to find that right balance between technical and non-technical aspects in a presentation like this. Some of the geeky sailors wanted to know things like what kind of interface do the chips communicate on between the modules, while other sailors just picked up the Raspberry Pi and said “well that’s kinda cool” before putting it down. But in the end, I had lots of people that seemed legitimately interested, and very few people walked out of the room. Well, except my wife of course.

I also wanted to listen to the other presentations, and was able to catch the vast majority of all of them. We definitely need to do more cruising on our boat, even if it’s just short weekend trips. But to hear about the multiple-months-long trips that these sailors went on, and see some of the thousands of pictures they took was amazing. The room was mostly full the entire day.

20150425_202319

Gramma and Papa took Tay and the dog home in the afternoon so that neither of them would be bored just sitting around. That night, with the boat to ourselves, we decided to watch the first episode of this season of Game of Thrones. About 20 minutes into the show, our electric heater went out. For those that have boats, you probably already know that electric heaters very often trip the breaker switch, and I assumed that was the situation here. Except when I looked at the switch panel, the breaker was fine. And the same was true with the breaker on the dock. For giggles I tried to turn over the engine, just to see… It barely made a sound.  Uh-oh.

So at 11:00pm, I dug into the engine room with a headlamp. I grabbed my electrical meter, and stuck the probes on the first house bank — 13.7 volts. Then I tested each of the other house bank batteries, and all of them were fully charged. I tested the starter battery, though, and it registered only 9.8 volts — essentially dead!

We weren’t stuck because with the fully charged house bank I knew I could get the engine turned over if I wired the engine starter to it. It’s not the most efficient way to do it, but once would work just fine. But I needed to figure out why we ran down our starter battery while plugged in to shore power. As I’m tracing wires and trying to figure it out, I lift up one of the house bank batteries to see behind it… and a large cable falls away loose. It was stuck between the battery and the battery case, and I couldn’t see it. It was also about 8AWG, so substantially large. I reconnected it to the negative bus, and realized that it was the return line from the starter battery charge circuit — the starter wasn’t charging at all.

I believe that because our 1-2-All switch was set to all, we were running down both banks all weekend, and only the house bank was being replenished. After letting the starter battery charge overnight, the next morning I tested the engine and she turned over without hesitation. We were good to go again.

The Return Trip

Most of the boats were starting to leave around 10:00. After a couple cups of coffee, and failing to give away the last of our mini cinnamon rolls, we decided to follow suit. Running the Raspberry Pi Chartplotter (because why not?), we backed out of the slip we had rented, into a very wide fairway, spun the boat, and headed back south.

The return trip was, for the most part, uneventful. We saw more ferries coming through Rich Passage, and again had to deal with ferries leaving the Vashon Island docks that confused us (we thought they were going to head east, so moved out of the way of that direction, but instead the ferry turned 90 degrees and headed north, more towards us than away!), but there was nothing truly concerning.
20150426_112546
The boat ran well the whole time, showing charging voltages on both battery banks, and we even took a short detour just north of Brown’s Point Lighthouse when we saw a small pod of Dall’s Porpoises off to our starboard.

Just four short hours after leaving the docks in Poulsbo, we were docking in our slip. Of course we did an absolutely terrible job on that docking maneuver! But at least there was nobody there to watch it happen, so nobody knows.

Right?

OpenCPN on the Raspberry Pi 2 with AIS

This past weekend I gave a presentation to a group of local sailors and boaters touting the benefits of running OpenCPN on a System-on-a-chip board such as the Raspberry Pi.

In preparation for the talk, I rebuilt OpenCPN on the newest generation Raspberry Pi 2, running the most current source code available on git at the time (4.1.412). I didn’t have the time to do any performance tuning of the system, and ran it out of the box all weekend long. OpenGL was not running appropriately, so performance was less than previous installations, but it did run out of the box, following the Raspberry Pi installation steps, with the additional step of compiling OpenCPN from source to get the most recent version.

However, I did add a new feature to the installation, and one that I am extraordinarily happy with — the ability to receive AIS data on OpenCPN running on a Raspberry Pi 2!

First AIS Contact

The first AIS contact!

In order to read AIS messages, I purchased a demo board of a product called dAISy designed by Adrian Studer. I’ve had the board in my hands for a few months, but as always work and life gets in the way of projects such as this. This past weekend I sailed my boat the 35nm or so from Tacoma to Poulsbo and had a few hours to play around with the chartplotter AND dAISy in an environment where the messages could be received. In short, it worked exceptionally well, and you can see the very first AIS contact that was picked up by the system just as we exited the north end of Colvos Passage in the photo above.

dAISy is a very well designed board that is easy to use. Just connect an antennae to the BNC connector, and a USB cable to the mini-USB port. In the current Raspbian image, all the required kernel support is set up, so simply plugging in the board is enough.

To test that it is working, perform the following test:

sudo apt-get install screen
screen /dev/ttyACM0

To exit screen, type CTRL-A, followed by K. At this point you should have a terminal window set up, and if you are within range of AIS targets you should see a list of AIVDM NMEA messages, that look something like this:

!AIVDM,1,1,,A,85Mwp`1Kf3aCnsNvBWLi=wQuNhA5t43N`5nCuI=p<IBfVqnMgPGs,0*47 !AIVDM,2,1,6,A,8>qc9wiKf>d=Cq5r0mdew:?DLq>1LmhHrsqmBCKnJ50,0*30
!AIVDM,2,1,1,A,55?MbV02;H;s<HtKR20EHE:0@T4@Dn2222222216L961O5Gf0NSQEp6ClRp8,0*1C

If you are not within range of AIS targets, you can still confirm that dAISy is responding by accessing it’s menu. Simply press [esc] and the menu will display such as the screencapture from Windows shows below:

AIS

If you turn on debug messages, and you are out of range (or have a very low signal) then you will see a series of messages like the following:
sync A RSSI=-085dBm
error: invalid stuff bit
sync A RSSI=-073dBm
error: CRC error
sync A RSSI=-086dBm
error: invalid stuff bit
sync B RSSI=-071dBm
error: CRC error
sync B RSSI=-087dBm
error: CRC error
sync B RSSI=-086dBm
error: invalid stuff bit

Then the last step is to render the targets in OpenCPN. It’s a fairly simple process of creating a new data connection. Go to Options -> Connections | Data Connections, and Add Connection. Select a Serial message, and set the DataPort to the dAISy USB message port (generally /dev/ttyACM0). Set the Baudrate to 9600, and uncheck Control checksum. Apply and return to OpenCPN.

If you think you have done everything correctly, but you are still not seeing AIS contacts or messages, and especially if ‘screen’ is showing messages, make sure that your user is added to the ‘dialout’ group. To add yourself to the group:
sudo usermod -a -G dialout $USER

Otherwise, enjoy the benefits of knowing where other vessels are, where they are heading, and who they are!

20150426_112418

20150426_112546

OpenCPN Chartplotter Software Plugins

The Chartplotter functionality of my off-season project is complete, due to the fact that the OpenCPN software package is, for the most part, a turn-key solution that had all the necessary features already included. It is a remarkable solution. I prefer the Raspberry Pi platform, for a low-powered dedicated device, but on a small laptop or dedicated PC, it is even faster and extremely effective.

However, like any piece of software, it doesn’t have every feature that every user desires. So they also have a plugin mechanism that allows a user to build their own extensions to the base system, and create those features themselves.

I have done exactly this during the winter, writing a pair of plugins that I think enhance product and make it even more perfectly suited to my particular desires.

Remembrancer

The first plugin is Remembrancer. Remembrancer is a mostly-made-up word that describes the notion that it is used as a reminder. The concept is simple. All it does is bring up an alert box, and play a sound, after a configured length of time.

The idea is that on a long route, with the autopilot steering, the helmsman may become distracted or forgetful, and this plugin will force him/her to remember to do a safety sweep of the water, route, and equipment. The time delay is fully configurable, as is the sound that is played.

Remembrancer

The source files for the plugin can be found on GitHub.

NOAA Doppler

The second plugin, NOAA Doppler, is much more involved. Quite often while cruising we just want to know where it’s raining, so we jump online or on our phone, and view one of the many apps that show the current weather map. This plugin is designed to incorporate that feature into OpenCPN, and renders doppler radar imagery directly over the chart.

NOAA provides free images of the current radar returns for all domestic ground stations. If you live in the United States and know your station ID (Seattle area is ATX (Everett), you can find the live reflectivity, storm velocity, precipitation, and other readings. They also provide a GIS “world file”. The values in this file can then be used to calculate the projection.

I also added a blur factor to the image before rendering, so that the blockiness is reduced. It works fairly well.

Weather Out

Weather In

However, the NOAA Doppler plugin is still not complete:

  • The rendering works, though there are bugs with OpenGL’s transparency
  • The blur feature works perfectly
  • The overlay is correctly positioned, and matches up with NOAA’s own rendering as well as Google Earth
  • It does not download live images, and instead only uses the image previously stored
  • The world file values are hard-coded and don’t change when the stations change

The source files can also be found on GitHub

Resources

 

Still working on the Autopilot

It’s been a few months since I’ve made any tangible progress on the autopilot project.  Work and life has a way of interfering with my recreational activities. But this evening I decided to start to work on the Arduino side of the CPN Pi-lot project. Hopefully this post doesn’t get too technical…

The Arduino side of the system will need to be a “smart” component. Simply stated, given a desired destination heading, it will adjust the rudder angle accordingly so that the boat will eventually reach the destination.

First, to receive the chartplotter destination, it will receive and parse an RMB NMEA message, which is produced by OpenCPN, and which is defined as so:

$GPRMB,A,0.66,L,003,004,4917.24,N,12309.57,W,001.3,052.5,000.5,V*20
where:
           RMB          Recommended minimum navigation information
           A            Data status A = OK, V = Void (warning)
           0.66,L       Cross-track error (nautical miles, 9.99 max),
                                steer Left to correct (or R = right)
           003          Origin waypoint ID
           004          Destination waypoint ID
           4917.24,N    Destination waypoint latitude 49 deg. 17.24 min. N
           12309.57,W   Destination waypoint longitude 123 deg. 09.57 min. W
           001.3        Range to destination, nautical miles (999.9 max)
           052.5        True bearing to destination
           000.5        Velocity towards destination, knots
           V            Arrival alarm  A = arrived, V = not arrived
           *20          checksum

By combining the range and bearing to the destination, with the current heading, the Arduino will be able to determine how much rudder to adjust. This algorithm will be an Arduino implementation of a common PID Controller, but will be the subject of a future article, after it gets implemented.

If you read more on PID loops, you will see that the algorithm requires a feedback loop (PV or process variable in the equations)… as in, an actual measurement of the item being controlled. In an autopilot situation, this feedback is the current heading of the boat, received either from a GPS, compass or other device. After some research and communication with people that have built autopilots for things like quad-copters, it has become clear that GPS simply doesn’t have the resolution necessary to effectively steer a vehicle, even at the relatively slow speeds of a trawler (roughly six knots through the water).

So the Arduino component will need to receive the current heading via a digital compass, and that is what I worked on today.

A few weeks ago, I purchased a LSM303DLHC 3-axis Accelerometer and Magnetometer board … aka a digital compass. And tonight I wired it up to an Arduino, and was able to receive a fairly reliable compass heading. I hooked up a small LCD to it that displayed the heading as well as the cardinal direction it correlated to. Then I turned it on in my truck, and drove around town comparing what the ardino claimed was the direction to the direction on the GPS and dashboard compass. Thankfully, they were very close; the differences are attributed to the lack of tilt-compensation in the calculation.

The Arduino Code, as it stands today is copied here:

//Autopilot.h
typedef struct TiltCompass_s
{
	float Heading;
	float Pitch;
	float Roll;

	float X;
	float Y;
	float Z;

	float MaxX;
	float MaxY;
	float MaxZ;
	float MinX;
	float MinY;
	float MinZ;

	float XMean;
	float YMean;
	float ZMean;
	float XLeng;
	float YLeng;
	float ZLeng;
} TiltCompass;

//Autopilot.ino

#include <Wire.h>
#include <SoftwareSerial.h>
#include <Print.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM303_U.h>
#include "Autopilot.h"

SoftwareSerial lcd(3,2); // pin 2 = TX, pin 3 (unused)
const int LOOP_DELAY = 500;

TiltCompass Compass;
Adafruit_LSM303_Mag_Unified Sensor = Adafruit_LSM303_Mag_Unified(12345);

///Initiation Method
void setup() {
  Serial.begin(9600);
  Wire.begin();
  InitCompass();

  lcd.begin(9600); // set up serial port for 9600 baud
  delay(500); // wait for display to boot up
}

///Main Program Loop
void loop() {

	GetHeading();
		
	//LCD_WriteOutput();
  
	delay(LOOP_DELAY);
}

///Initialize the Compass
void InitCompass(void)
{
	//Start LSM303 Module
	if(!Sensor.begin())
	{
		Serial.println("Invalid sensor!");
		LCD_Clear();
		lcd.write("Invalid Sensor!");
		while(1);
	}

	//Calibrate sensore values to values discovered for the sensor
	SetCalibrationValues();
}

///Method to get the Compass heading from the LSM303DLHC module
void GetHeading()
{
	sensors_event_t magEvent; 
	Sensor.getEvent(&magEvent);

    AdjustCalibration(magEvent);

	float computedX = (magEvent.magnetic.x - Compass.XMean)/Compass.XLeng; // normalized x-component of acceleration
	float computedY = (magEvent.magnetic.y - Compass.YMean)/Compass.YLeng; // normalized y-component of acceleration
	float computedZ = (magEvent.magnetic.z - Compass.ZMean)/Compass.ZLeng; // normalized z-component of acceleration
	//float acceleration = sqrt(pow(computedX,2)+pow(computedY,2)+pow(computedZ,2)); 
    
	// Calculate the angle of the vector y,x
	Compass.Heading = (atan2(computedY, computedX) * 180) / PI;
	if (Compass.Heading < 0)
	{
		Compass.Heading += 360;
	}

	//Output 
	Serial.print("Compass: ");
	Serial.print(Compass.Heading);
	Serial.print(" - ");
	Serial.println(GetCompassRoseDirection());
}

///Method to convert a Compass direction to a cardinal direction
String GetCompassRoseDirection()
{
	if (Compass.Heading > 11.25 & Compass.Heading < 33.75)
	{
		return "NNE";
	}if (Compass.Heading > 33.75 & Compass.Heading < 56.25)
	{
		return "NE";
	}if (Compass.Heading > 56.25 & Compass.Heading < 78.75)
	{
		return "ENE";
	}if (Compass.Heading > 78.75 & Compass.Heading < 101.25)
	{
		return "E";
	}if (Compass.Heading > 101.25 & Compass.Heading < 123.75)
	{
		return "ESE";
	}if (Compass.Heading > 123.75 & Compass.Heading < 145.25)
	{
		return "SE";
	}if (Compass.Heading > 145.25 & Compass.Heading < 168.75)
	{
		return "SSE";
	}if (Compass.Heading > 168.75 & Compass.Heading < 191.25)
	{
		return "S";
	}if (Compass.Heading > 191.25 & Compass.Heading < 213.75)
	{
		return "SSW";
	}if (Compass.Heading > 213.75 & Compass.Heading < 236.25)
	{
		return "SW";
	}if (Compass.Heading > 236.25 & Compass.Heading < 258.75)
	{
		return "WSW";
	}if (Compass.Heading > 258.75 & Compass.Heading < 281.25)
	{
		return "W";
	}if (Compass.Heading > 281.25 & Compass.Heading < 303.75)
	{
		return "WNW";
	}if (Compass.Heading > 303.75 & Compass.Heading < 326.35)
	{
		return "NW";
	}if (Compass.Heading > 326.35 & Compass.Heading < 348.75)
	{
		return "NNW";
	}

	return "N";
	
}

///Take into account new values for calibration
void AdjustCalibration(sensors_event_t &magEvent)
{
  if (magEvent.magnetic.x < Compass.MinX)
  {
	  Serial.println("Resetting MinX");
	  Compass.MinX = magEvent.magnetic.x;
	  SetNormalizationValues();
  }
  if (magEvent.magnetic.x > Compass.MaxX)
  {
	  Serial.println("Resetting MaxX");
	  Compass.MaxX = magEvent.magnetic.x;
	  SetNormalizationValues();
  }  
  
  if (magEvent.magnetic.y < Compass.MinY)
  {
	  Serial.println("Resetting MinY");
	  Compass.MinY = magEvent.magnetic.y;
	  SetNormalizationValues();
  }
  if (magEvent.magnetic.y > Compass.MaxY)  
  {
	  Serial.println("Resetting MaxY");
	  Compass.MaxY = magEvent.magnetic.y;
	  SetNormalizationValues();
  }

  if (magEvent.magnetic.z < Compass.MinZ)  
  {
	  Serial.println("Resetting MinZ");
	  Compass.MinZ = magEvent.magnetic.z;
	  SetNormalizationValues();
  } 
  if (magEvent.magnetic.z > Compass.MaxZ)  
  {
	  Serial.println("Resetting MaxZ");
	  Compass.MaxZ = magEvent.magnetic.z;
	  SetNormalizationValues();
  } 
}

///Method to display information on the LCD screen
void LCD_WriteOutput()
{
	LCD_Clear();
	lcd.write("Heading: ");
	lcd.print(round(Compass.Heading));
	lcd.write(254); // move cursor to beginning of first line
	lcd.write(192);
	lcd.write("Direction: ");
	lcd.print(GetCompassRoseDirection());
}

///Method to clear the LCD scren
void LCD_Clear()
{
	lcd.write(254); // move cursor to beginning of first line
	lcd.write(128);
	lcd.write("                "); // clear display
	lcd.write("                ");
	lcd.write(254); // move cursor to beginning of first line
	lcd.write(128);
}

///Set calibrations values
///  - Initial hardcoded numbers come from Calibration example project
void SetCalibrationValues()
{
	//Set Calibration Values from examples
	Compass.MinX = -60.45;
	Compass.MinY = -58.64;
	Compass.MinZ = -52.14;
	Compass.MaxX = 44.00;
	Compass.MaxY = 50.18;
	Compass.MaxZ = 78.06;
	SetNormalizationValues();
}

///Set values uses to normalize measured accelerations
void SetNormalizationValues()
{
	//Set Normalization Parameters
	Compass.XMean = (Compass.MaxX + Compass.MinX)/2;
	Compass.YMean = (Compass.MaxY + Compass.MinY)/2;
	Compass.ZMean = (Compass.MaxZ + Compass.MinZ)/2;
	Compass.XLeng = (Compass.MaxX - Compass.MinX)/2;
	Compass.YLeng = (Compass.MaxY - Compass.MinY)/2;
	Compass.ZLeng = (Compass.MaxZ - Compass.MinZ)/2;
}

References: