Hand tools and fabrication machines

Bottle opener

Anchor Steam

Story

(10-JAN-18 Update: See Version 2 addendum below)

There are plenty of apps out there that help the golfer with their micro-experience (course map, stroke count, distance to the hole) but not much for course managers to monitor and optimize the macro-experience. The Hologram Nova/Raspberry Pi combination offers the technology stack needed to provide course managers with real-time information on golfer locations in order to optimize tee times, for example, based on current conditions of golfer spacing and pace.

Each golfer is provided with a Golfer Tracker when they check in. The Golfer Tracker transmits GPS information to the course manager, as visualized in the Google Earth snapshot below. The Golfer Tracker data shows the course manager where there are bottlenecks so the marshal (blue paddle icon) can be dispatched to take action. (The course marshal would also have ready access to the live Google Earth image via smartphone.)

Google Earth rendering of Golfer Tracker data

The Golfer Tracker can also be configured to identify "Gold" (yellow paddle icon) and "Platinum" tier frequent golfers and make sure the beer cart (the obvious icon) is always at the ready. The Google Spreadsheet Mapper provides multiple configuration options that help the course manager determine at-a-glance what's most important to know about the current state of the course.

In addition to real-time golfer position monitoring, the system captures a local log file of the entire golfer outing for offline course manager analysis of the golfer experience and optimization of course/hazard layout.

Golfer Tracker data log

Visualization of Golfer Tracker data log

Future iterations for the prototype will include:

Replace the USB GPS receiver with breakout board to reduce tracker size and power usage

Version 2.0 Addendum

Since the contest deadline was extended by a few weeks, I decided to address a couple of the less-elegant components of the original submission. Specifically:

Adding an interval timer to the code in order to cut down on cellular usage

Implement a more straightforward approach to visualizing the golf course load by replacing the gspread/Google Sheets/Google Earth approach with MQTT and the provided map dashboard via ThingsBoard.io

Seeing as an 18-hole golf round can take 3-4 ours, we don't really need to know where the golfers are every second (the default timing of the GPS receiver signal). And sending location coordinates every second can quickly rack up data charges. To address this, we add an interval timer using the Python time library:

Save cellular data charges by adding an interval timer

The original submission uses Google Spreadsheet Mapper and Google Earth - enabled by the gspread library - which suits the purpose just fine and provides significant configuration options. But the data flow is a little convoluted. The ThingsBoard.io platformoffers a simpler 1-step approach with MQTT and a provided Google map widget.

First, set up the Pi Zero with MQTT via the following:

The amended project file - golfer_gps_v2.py - adds the following code to enable MQTT communication of location coordinates with ThingsBoard:

ThingsBoard MQTT communication set up

ThingsBoard documentation explains how to set up a device with an asset token - you can start with the default Raspberry P. Once you have your access token, update the golfer_gps_v2.py file and run the program. If all goes right, you'll get the following in your device details view - live GPS data!:

ThingsBoard device details - live data from the GPS receiver

Select the two available keys - latitude and longitude - and you'll be prompted to select a widget (or widgets) to visualize the data on your ThingsBoard dashboard. The Google Maps widget renders as you would expect:

ThingsBoard dashboard Google Maps widget - Just one golfer today, and way off course

The Google Map widget has plenty of configuration options, similar to Google Spreadsheet Mapper. ThingsBoard also provides an library of other widget options that could prove useful in golf course management.

importserialimportos# Library for managing Google Sheets update and authenticationimportgspreadfromoauth2client.service_accountimportServiceAccountCredentials# Hologram cloud library for cellular comminucationsfromHologram.HologramCloudimportHologramCloudhologram=HologramCloud(dict(),network='cellular')result=hologram.network.connect()ifresult==False:print('Failed to connect to cell network')# Use creds to create a client to interact with the Google Drive APIscope=['https://spreadsheets.google.com/feeds']creds=ServiceAccountCredentials.from_json_keyfile_name('client_secret.json',scope)client=gspread.authorize(creds)# Name of the Google Sheet and the specific worksheetsheet=client.open("Golfer Tracker").worksheet("PlacemarkData")# GPS receiver set upfirstFixFlag=False# this will go true after the first GPS fix.firstFixDate=""# Set up serial:ser=serial.Serial(port='/dev/ttyUSB0',\
baudrate=4800,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS,\
timeout=1)# Helper function to take HHMM.SS, Hemisphere and make it decimal:defdegrees_to_decimal(data,hemisphere):try:decimalPointPosition=data.index('.')degrees=float(data[:decimalPointPosition-2])minutes=float(data[decimalPointPosition-2:])/60output=degrees+minutesifhemisphereis'N'orhemisphereis'E':returnoutputifhemisphereis'S'orhemisphereis'W':return-outputexcept:return""# Helper function to take a $GPRMC sentence, and turn it into a Python dictionary.# This also calls degrees_to_decimal and stores the decimal values as well.defparse_GPRMC(data):data=data.split(',')dict={'fix_time':data[1],'validity':data[2],'latitude':data[3],'latitude_hemisphere':data[4],'longitude':data[5],'longitude_hemisphere':data[6],'speed':data[7],'true_course':data[8],'fix_date':data[9],'variation':data[10],'variation_e_w':data[11],'checksum':data[12]}dict['decimal_latitude']=degrees_to_decimal(dict['latitude'],dict['latitude_hemisphere'])dict['decimal_longitude']=degrees_to_decimal(dict['longitude'],dict['longitude_hemisphere'])returndict# Main program loop:whileTrue:line=ser.readline()if"$GPRMC"inline:# This will exclude other NMEA sentences the GPS unit providesgpsData=parse_GPRMC(line)# Turn a GPRMC sentence into a Python dictionary called gpsDataifgpsData['validity']=="A":# If the sentence shows that there's a fix, then we can log the lineiffirstFixFlagisFalse:# If we haven't found a fix before, then set the filename prefix with GPS date & time.firstFixDate=gpsData['fix_date']+"-"+gpsData['fix_time']firstFixFlag=Trueelse:# write the data to a simple log file and then to Google Sheets:withopen("/home/pi/gps_experimentation/"+firstFixDate+"-simple-log.txt","a")asmyfile:myfile.write(gpsData['fix_date']+","+gpsData['fix_time']+","+str(gpsData['decimal_latitude'])+","+str(gpsData['decimal_longitude'])+"\n")print(gpsData['fix_date']+","+gpsData['fix_time']+","+str(gpsData['decimal_latitude'])+","+str(gpsData['decimal_longitude'])+"\n")sheet.update_cell(11,5,str(gpsData['decimal_latitude']))# Write location data to the Google Sheetsheet.update_cell(11,6,str(gpsData['decimal_longitude']))# Disconnect from the cellular networkhologram.network.disconnect()

importserialimportos# Library for managing Google Sheets update and authenticationimportgspread# Version 2 libraries:importtimeimportsysimportpaho.mqtt.clientasmqttimportjsonfromoauth2client.service_accountimportServiceAccountCredentials# Hologram cloud library for cellular comminucationsfromHologram.HologramCloudimportHologramCloudhologram=HologramCloud(dict(),network='cellular')result=hologram.network.connect()ifresult==False:print('Failed to connect to cell network')# Use creds to create a client to interact with the Google Drive APIscope=['https://spreadsheets.google.com/feeds']creds=ServiceAccountCredentials.from_json_keyfile_name('client_secret.json',scope)client=gspread.authorize(creds)# Name of the Google Sheet and the specific worksheetsheet=client.open("Golfer Tracker").worksheet("PlacemarkData")# Version 2 - Specify Thingsboard server and access tokenTHINGSBOARD_HOST='demo.thingsboard.io'ACCESS_TOKEN='<your access_token>'# Version 2 - Specify interval to reduce cellular data usageINTERVAL=10next_reading=time.time()# Version 2 - Initialize mqttgps_data={'latitude':0,'longitude':0}client=mqtt.Client()client.username_pw_set(ACCESS_TOKEN)# Version 2 - Connect to ThingsBoard using default MQTT port and 60 seconds keepalive intervalclient.connect(THINGSBOARD_HOST,1883,60)client.loop_start()# GPS fix flag will go true after first fixfirstFixFlag=FalsefirstFixDate=""# Set up serial:ser=serial.Serial(port='/dev/ttyUSB0',\
baudrate=4800,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS,\
timeout=1)# Helper function to take HHMM.SS, Hemisphere and make it decimal:defdegrees_to_decimal(data,hemisphere):try:decimalPointPosition=data.index('.')degrees=float(data[:decimalPointPosition-2])minutes=float(data[decimalPointPosition-2:])/60output=degrees+minutesifhemisphereis'N'orhemisphereis'E':returnoutputifhemisphereis'S'orhemisphereis'W':return-outputexcept:return""# Helper function to take a $GPRMC sentence, and turn it into a Python dictionary.# This also calls degrees_to_decimal and stores the decimal values as well.defparse_GPRMC(data):data=data.split(',')dict={'fix_time':data[1],'validity':data[2],'latitude':data[3],'latitude_hemisphere':data[4],'longitude':data[5],'longitude_hemisphere':data[6],'speed':data[7],'true_course':data[8],'fix_date':data[9],'variation':data[10],'variation_e_w':data[11],'checksum':data[12]}dict['decimal_latitude']=degrees_to_decimal(dict['latitude'],dict['latitude_hemisphere'])dict['decimal_longitude']=degrees_to_decimal(dict['longitude'],dict['longitude_hemisphere'])returndict# Main program loop:whileTrue:line=ser.readline()if"$GPRMC"inline:# This will exclude other NMEA sentences the GPS unit providesgpsData=parse_GPRMC(line)# Turn a GPRMC sentence into a Python dictionary called gpsDataifgpsData['validity']=="A":# If the sentence shows that there's a fix, then we can log the lineiffirstFixFlagisFalse:# If we haven't found a fix before, then set the filename prefix with GPS date & time.firstFixDate=gpsData['fix_date']+"-"+gpsData['fix_time']firstFixFlag=Trueelse:# write the data to a simple log file and then to Google Sheets:withopen("/home/pi/gps_experimentation/"+firstFixDate+"-simple-log.txt","a")asmyfile:myfile.write(gpsData['fix_date']+","+gpsData['fix_time']+","+str(gpsData['decimal_latitude'])+","+str(gpsData['decimal_longitude'])+"\n")print(gpsData['fix_date']+","+gpsData['fix_time']+","+str(gpsData['decimal_latitude'])+","+str(gpsData['decimal_longitude'])+"\n")# Write location data to the Google Sheetsheet.update_cell(11,5,str(gpsData['decimal_latitude']))sheet.update_cell(11,6,str(gpsData['decimal_longitude']))# Version 2 - Write location data to Thingsboard gps_data['latitude']=str(gpsData['decimal_latitude'])gps_data['longitude']=str(gpsData['decimal_longitude'])client.publish('v1/devices/me/telemetry',json.dumps(gps_data),1)# Version 2 - Interval timing controlnext_reading+=INTERVALsleep_time=next_reading-time.time()ifsleep_time>0:time.sleep(sleep_time)client.loop_stop()client.disconnect()# Disconnect from the cellular networkhologram.network.disconnect()