Fun Stuff‎ > ‎

Pebble Watch

With the advent of capable navigation programs on the smartphone and tablets, it is becoming increasingly important to provide the boat's electronic data over a wireless network. Many older boat's navigation systems link their instruments using the NMEA 0183 standard; newer boats support both the older NMEA 0183 and the NMEA 2000 standards.  On Intuition, we integrated the Raymarine SeaTalk network data with the AIS data using the vYacht multiplexer and wireless router.   This provides the needed data for a variety of applications, including the display on smartphones (iPhone) or tablets (iPad).  

In practice, I found it difficult to use a smartphone or tablet on the deck of a sailboat.  With the popularity of smart-watches, I felt the watch is a better platform for displaying this data.  The inspiration for this is "Boat Remote", an open source project to display navigation data on the Pebble watch.  This is a good effort and suits their needs; however, I wanted to display wind, AIS data, and a positional anchor-watch.  Based on their work, I developed my own version of a Pebble SmartWatch boat application.  

 Menu Navigation    AIS Targets
 True/Apparent Wind Navigation to Waypoint
Anchor Watch
 
 
Originally, I extended their server code to support the additional data fields.  Where this approach started to break down was when I needed current location for the anchor watch drift distance.  I ended up with kludgy code to maintain the boat's position.  I also found the original Boat Remote python server code to be very inefficient and overly complicated.  Their basic approach was to define each watch element and associate the underlying NMEA 0183 sentence. They used the pynmea open source project code to parse the NMEA sentences.   When parsing an input NMEA sentence, their server would have to hunt for the one or many watch fields to display.  This is a problem in that the NMEA 0183 sentences typically have one or more data elements.  For example, the NMEA 0183 BWC sentence has waypoint latitude, longitude, bearing true, bearing magnetic, distance and eta.  We care about each of these data elements.  We don't need the pynmea class to parse a dozen sentences, nor the associated object/class structures.

My approach was to invert this process and associate each NMEA 0183 sentence with one or more watch fields, and the parsing positional data, filter field and criteria (e.g., wind-speed and angle (MWV) has multiple values for true vs. relative wind -- thus a filter criteria is to match on 'T' or 'R'.  Here's the bearing and distance to a waypoint (BWC) NMEA 0183 structure,

"BWC": [  # Bearing and distance to waypoint
["wp_name", [12], None, None, None],
["wp_lat", [2, 3], None, None, "format_lat"],
["wp_lon", [4, 5], None, None, "format_lon"],
["wp_bearing_t", [6], None, None, "format_angle"],
["wp_bearing", [8], None, None, "format_angle"],
["wp_distance", [10], None, None, None],
["wp_eta", [10], None, None, "format_eta"]
],

With this structure, it is easy to parse the NMEA 0183 sentence and store the results in an ordinary dictionary.  This eliminated a custom class for each watch-field and the unnecessary hunting for field values in the object structure.  Overall, my code is much smaller, faster and more stable.  As a side benefit, different NMEA sentences can be defined for the various watch values, which supports GPS's from Raymarine, Garmin, etc.  without additional parsing effort.

The entire parsing logic is about 1/2 page of python code and is easy to understand,

#
# Parse NMEA 0183 messages based on nmea_lookup table
#
def parse_gps(self, sentence):

# Fetch parsing rules from nmea_lookup
try:
# Which NMEA sentence, e.g., RMC
nmea_token = sentence[3:6]  # NMEA sentence
if (nmea_token in nmea_lookup) == False:
#print "nmea-parse-nmea_token %s not defined, skipping " % nmea_token
#self.logger.info("nmea-parse-nmea_token %s not defined, skipping " % nmea_token)
return

# Remove checksum characters
s = sentence.split("*")[0]
# Step through the watch fields, e.g., lat, lon,...
for items in nmea_lookup[nmea_token]:
field_name = items[0]
fields = items[1]
filter_field = items[2]
filter_value = items[3]
format_function = items[4]

# Extract fields defined by nmea_lookup
nmea_fields = s.split(',')
temp_values = []
for sub_field in fields:
temp_values.append(nmea_fields[sub_field])

# Assume no filter and match criteria
filter_match = True

# Check filter value
if (filter_field is not None) and (nmea_fields[filter_field] != filter_value):
filter_match = False

# Keep this value
if filter_match:
if format_function is not None:
       # Format value using format function
self.watch_fields[field_name] = getattr(self, format_function)(temp_values)
else:
self.watch_fields[field_name] = ' '.join(temp_values)

except Exception, e:
self.logger.error("NMEA.parse_gps ERROR %s %s" % (e,sentence))
raise

That's it -- the watch display values are stored in the watchfields[] dictionary.

The server listens for a NMEA 0183 sentence.  



If GPS related ($), then parse the NMEA sentence using the parsing data; otherwise, parse AIS data (!).  Each Pebble watch display page makes a request to the HTML server for the specific data elements for that display. This keeps the data transmission to a minimum -- we don't need to fetch 50 AIS targets when  displaying wind values!  The server data is returned via an JSON data structure that the Pebble watch can easily parse. 

Subset of watch navigation data,


Original total set of watch data,



I'm in the process of testing the app's functionality.  I hope to package as a Pebble application and list on their app store.

This works for me!