changeset 236:493f994c999a main

Merge from upstream
author Bob Cook <bob@bobcookdev.com>
date Sun, 13 Jul 2014 09:15:53 -0700
parents 8af48f7c2f34 (diff) 5657cf2325c9 (current diff)
children 43f43ebe35df
files main/robots/odr-sim/ControllerStatus.cpp main/robots/odr-sim/ControllerStatus.h
diffstat 11 files changed, 2293 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/buttons.cpp	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,142 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  Button functions for the Outdoor Robot user interface board.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#include <avr/io.h>
+
+#include "func.h"
+#include "leds.h"
+
+// ----------------------------------------------------------------------------------------
+
+static const uint8_t DEBOUNCE_THRESHOLD = 10;
+
+// ----------------------------------------------------------------------------------------
+
+button_press_t g_current_state;
+button_press_t g_transition_state;
+uint8_t        g_transition_count;
+
+// ----------------------------------------------------------------------------------------
+
+void button_init()
+{
+    //---   The button (joystick) is on PORTB.3, PORTB.4, PORTC.4, PORTC.5, PORTC.6
+    //      Enable the pull-ups.
+
+    DDRB &= ~( ( 1 << PB4 ) | ( 1 << PB3 ) );
+    DDRC &= ~( ( 1 << PC6 ) | ( 1 << PC5 ) | ( 1 << PC4 ) );
+
+    PORTB |= ( 1 << PB4 ) | ( 1 << PB3 );
+    PORTC |= ( 1 << PC6 ) | ( 1 << PC5 ) | ( 1 << PC4 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static button_press_t determine_button_direction()
+{
+    if ( ( PINC & ( 1 << PC5 ) ) == 0 )
+    {
+        return button_center_e;
+    }
+    else if ( ( PINC & ( 1 << PC4 ) ) == 0 )
+    {
+        return button_up_e;
+    }
+    else if ( ( PINB & ( 1 << PB3 ) ) == 0 )
+    {
+        return button_down_e;
+    }
+    else if ( ( PINB & ( 1 << PB4 ) ) == 0 )
+    {
+        return button_left_e;
+    }
+    else if ( ( PINC & ( 1 << PC6 ) ) == 0 )
+    {
+        return button_right_e;
+    }
+    else
+    {
+        return button_not_pressed_e;
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void button_update()
+{
+    button_press_t dir = determine_button_direction();
+
+    if ( dir == button_not_pressed_e )
+    {
+        led_blue_off();
+    }
+    else
+    {
+        led_blue_on();
+    }
+
+    if ( dir == g_current_state )
+    {
+        return;
+    }
+
+    if ( dir == g_transition_state )
+    {
+        if ( ++g_transition_count > DEBOUNCE_THRESHOLD )
+        {
+            g_current_state = g_transition_state;
+        }
+    }
+    else if ( g_transition_count > DEBOUNCE_THRESHOLD / 2 )
+    {
+        --g_transition_count;
+    }
+    else
+    {
+        g_transition_state = dir;
+        g_transition_count = 0;
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+button_press_t button_state()
+{
+    return g_current_state;
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool button_is_pressed()
+{
+    return ( g_current_state != button_not_pressed_e );
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/canmsgs.cpp	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,288 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2013-2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  CAN related functions for the Outdoor Robot user interface node.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#include <avr/interrupt.h>
+#include <avr/io.h>
+
+#include "project_defs.h"
+#include "func.h"
+#include "leds.h"
+
+#include "packages/avr/can/m1.h"
+#include "packages/avr/device/spinwait.h"
+
+#include "packages/common/can/can_helpers.h"
+#include "packages/common/can/can_messages.h"
+#include "packages/common/can/can_nodes.h"
+
+// ----------------------------------------------------------------------------------------
+
+bool canmsg_init()
+{
+    return m1can_init();
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool canmsg_did_error_occur()
+{
+    return ( m1can_status() != m1can_status_ok );
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool canmsg_send_emergency()
+{
+    return m1can_send( can_build_message_id( can_node_odr_display,
+                                             can_node_broadcast,
+                                             can_dataid_emergency ), 0, 0 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool canmsg_send_all_clear()
+{
+    return m1can_send( can_build_message_id( can_node_odr_display,
+                                             can_node_broadcast,
+                                             can_dataid_all_clear ), 0, 0 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool canmsg_send_heartbeat()
+{
+    return m1can_send( can_build_message_id( can_node_odr_display,
+                                             can_node_broadcast,
+                                             can_dataid_heartbeat ), 0, 0 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+#if 0
+bool canmsg_send_command( uint8_t cmd, uint8_t data_1, uint8_t data_2 )
+{
+    uint32_t msgid = can_build_message_id( can_node_odr_display,
+                                           can_node_broadcast,
+                                           can_dataid_odr_display_cmd );
+
+    can_data_odrctl_button info;
+
+    info.button_1 = button_is_1_down();
+    info.button_2 = button_is_2_down();
+
+    if ( ! m1can_send( msgid, reinterpret_cast< uint8_t* >( &info ), sizeof( info ) ) )
+    {
+        return false;
+    }
+    
+    return true;
+}
+#endif
+
+// ----------------------------------------------------------------------------------------
+
+static void update_wheel_status( const can_data_wheel_status* data )
+{
+    display_update_wheel_speed( data->actual_rpm_front, data->actual_rpm_rear );
+    display_update_wheel_position( data->angle_front, data->angle_rear );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_sonar_front( const can_data_sonar_front* data )
+{
+    display_update_sonar_front( data->left,
+                                data->center_left,
+                                data->center_right,
+                                data->right );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_yaw( const can_data_imu_data* data )
+{
+    display_update_yaw( data->data );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_pitch( const can_data_imu_data* data )
+{
+    display_update_pitch( data->data );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_roll( const can_data_imu_data* data )
+{
+    display_update_roll( data->data );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_latitude( const can_data_gps_data* data )
+{
+    display_update_latitude( data->degrees, data->min_thousandths );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_longitude( const can_data_gps_data* data )
+{
+    display_update_longitude( data->degrees, data->min_thousandths );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_gps_fix( const can_data_gps_fix* data )
+{
+    status_got_gps_fix( data->satellites > 0 );
+    display_update_gps_fix( data->satellites );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void got_mgr_update( const can_data_odrmgr_update* data, uint8_t length )
+{
+    status_got_mgr_update();
+
+    if ( length > 0 )
+    {
+        display_set_mgr_status( data->msg, length );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool canmsg_process_pending()
+{
+    uint32_t recvid;
+    bool     recvrequest = false;
+    uint8_t  recvdata[ 8 ];
+    uint8_t  recvlen = sizeof( recvdata );
+
+    for ( ;; )
+    {
+        if ( ! m1can_read( &recvid, &recvrequest, recvdata, &recvlen ) )
+        {
+            return true; // no messages, everything ok
+        }
+
+        uint8_t  srcnode;
+        uint8_t  dstnode;
+        uint16_t dataid;
+        can_parse_message_id( recvid, &srcnode, &dstnode, &dataid );
+
+        if ( dstnode != can_node_broadcast && dstnode != can_node_odr_display )
+        {
+            continue; // not for us, everything ok, loop around and try again
+        }
+
+        switch ( dataid )
+        {
+            case can_dataid_heartbeat:
+                status_got_heartbeat( srcnode );
+                break;
+
+            case can_dataid_emergency:
+                status_got_emergency( srcnode );
+                break;
+
+            case can_dataid_all_clear:
+                status_got_all_clear( srcnode );
+                break;
+
+            case can_dataid_odrmgr_update:
+                got_mgr_update(
+                        reinterpret_cast< can_data_odrmgr_update* >( &recvdata ), recvlen );
+                break;
+
+            case can_dataid_wheel_status:
+                update_wheel_status( reinterpret_cast< can_data_wheel_status* >( &recvdata ) );
+                break;
+
+            case can_dataid_imu_yaw:
+                update_yaw( reinterpret_cast< can_data_imu_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_imu_pitch:
+                update_pitch( reinterpret_cast< can_data_imu_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_imu_roll:
+                update_roll( reinterpret_cast< can_data_imu_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_gps_latitude:
+                update_latitude( reinterpret_cast< can_data_gps_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_gps_longitude:
+                update_longitude( reinterpret_cast< can_data_gps_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_gps_fix:
+                update_gps_fix( reinterpret_cast< can_data_gps_fix* >( &recvdata ) );
+                break;
+
+            case can_dataid_origin_lat:
+                update_latitude( reinterpret_cast< can_data_gps_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_origin_long:
+                update_longitude( reinterpret_cast< can_data_gps_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_target_lat:
+                update_latitude( reinterpret_cast< can_data_gps_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_target_long:
+                update_longitude( reinterpret_cast< can_data_gps_data* >( &recvdata ) );
+                break;
+
+            case can_dataid_sonar_front_state_enabled:
+                status_got_sonar_front_enabled();
+                break;
+
+            case can_dataid_sonar_front_state_disabled:
+                status_got_sonar_front_disabled();
+                break;
+
+            case can_dataid_sonar_front:
+                update_sonar_front( reinterpret_cast< can_data_sonar_front* >( &recvdata ) );
+                break;
+        }
+    }
+
+    return true; // no errors
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/display.cpp	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,687 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2012-2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  Display functions for the Outdoor Robot user interface board..
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#include <avr/io.h>
+#include <avr/pgmspace.h>
+
+#include "func.h"
+
+#include "packages/avr/device/spinwait.h"
+
+#include "packages/common/util/printutils.h"
+#include "packages/common/util/misc.h"
+
+#include "packages/avr/lcd/ili9340/ili9340.h"
+
+// always after other includes
+#include "packages/avr/device/workaround34734.h"
+
+// ----------------------------------------------------------------------------------------
+//
+//  The LCD display is 26 rows by 30 columns:
+//
+//                   1         2
+//         012345678901234567890123456789
+//         ||||||||||||||||||||||||||||||
+//      0-|Outdoor Robot                H|- H = heartbeat spinner
+//      1-|                              |-
+//      2-|                              |-
+//      3-|                              |-
+//      4-|      motion controller       |-
+//      5-|  f: +00 / +00  r: +00 / +00  |-
+//      6-|                              |-
+//      7-|       front obstacles        |-
+//      8-|    000   000   000   000     |-
+//      9-|                              |-
+//     10-|        location (00)         |-
+//      1-|  N 123 45.678  W 123 45.678  |-
+//      2-|                              |-
+//      3-|     origin (123.4) target    |-
+//      4-| N 123 45.678    N 123 45.678 |-
+//      5-| W 123 45.678    W 123 45.678 |-
+//      6-|                              |-
+//      7-|   Y +000  P +00.0  R +00.0   |-
+//      8-|                              |-
+//      9-|                              |-
+//     20-|                              |-
+//      1-|                              |-
+//      2-|                              |-
+//      3-|                              |-
+//      4-|                              |-
+//      5-|       emergency active       |-
+//         ||||||||||||||||||||||||||||||
+//         012345678901234567890123456789
+//                   1         2
+//
+//
+//      0-|Outdoor Robot       H|-          H = heartbeat spinner
+//      1-|                     |-
+//      2-|      XXXXXXXX       |-          X = status message from mgr
+//      3-|                     |-
+//      4-|  motion controller  |-
+//      5-| f:+00/+00 r:+00/+00 |-
+//      6-|                     |-
+//      7-|  front rangefinder  |-
+//      8-|   000 000 000 000   |-
+//      9-|                     |-
+//     10-| lat: N 123 45.678 * |-
+//      1-| lon: W 123 45.678   |-
+//      2-|                     |-
+//      3-| Y+000 P+00.0 R+00.0 |-
+//      4-|                     |-
+//      5-|ESTOP -A- -B-        |-
+//         ||||||||||||||||||||| 
+//         012345678901234567890
+//                   1         2
+//
+//
+// GPS, BUMP F, BUMP R
+// ----------------------------------------------------------------------------------------
+
+// ----------------------------------------------------------------------------------------
+
+static volatile uint8_t g_spinner_count;
+
+// ----------------------------------------------------------------------------------------
+
+static void str_small_int( char* str, int8_t value )
+{
+/*
+    if ( value >= 100 )
+    {
+        *str++ = '1';
+        value -= 100;
+    }
+    else
+    {
+        *str++ = '0';
+    }
+*/
+
+    if ( value >= 10 )
+    {
+        *str++ = ( '0' + ( value / 10 ) );
+        value %= 10;
+    }
+    else
+    {
+        *str++ = '0';
+    }
+
+    *str++ = ( '0' + value );
+
+    *str = '\0';
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void str_small_int_with_sign( char* str, int8_t value )
+{
+    if ( value < 0 )
+    {
+        value = -value;
+        *str++ = '-';
+    }
+    else if ( value > 0 )
+    {
+        *str++ = '+';
+    }
+    else
+    {
+        *str++ = '0';
+    }
+
+    str_small_int( str, value );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void str_write( char c, uint8_t x, uint8_t y )
+{
+    ili9340_write( c, x, y, ili9340_color_white, ili9340_color_blue );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void str_write( const char* str, uint8_t x, uint8_t y )
+{
+    ili9340_write( str, x, y, ili9340_color_white, ili9340_color_blue );
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void str_write_pgm( const prog_char* str, uint8_t x, uint8_t y )
+{
+    ili9340_write_pgm( str, x, y, ili9340_color_white, ili9340_color_blue );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_init()
+{
+    //--    Set the SS pin as an output; this prevents the SPI machinery from blocking.
+
+    DDRD |= ( 1 << PD3 );
+
+    //--    The backlight is on PORTC.1 which is OC1B. Set up Timer1 to allow variable
+    //      output to dim the display.
+
+//    DDRC |= ( 1 << PC1 );
+
+#if 0
+    TCCR1A = 0
+
+           | ( 0 << COM1A1 ) | ( 0 << COM1A0 )  // OC1A disconnected
+           | ( 1 << COM1B1 ) | ( 1 << COM1B0 )  // set OC1B on match
+           | ( 0 << WGM11  ) | ( 1 << WGM10  )  // Fast PWM, 8-bit
+           ;
+
+    TCCR1B = 0
+
+           | ( 0 << WGM13 ) | ( 1 << WGM12 )                   // Fast PWM, 8-bit
+           | ( 1 << CS12  ) | ( 0 << CS11  ) | ( 1 << CS10  )  // clk/1024
+           ;
+
+    OCR1B = 64;
+#endif
+
+    //--    Everything else is handled by the ILI9340 package.
+
+    ili9340_init();
+
+    //--    Write the initial display text to the screen.
+
+    ili9340_fill_screen( ili9340_color_blue );
+    str_write_pgm( PSTR("Outdoor Robot"), 1, 0 );
+    str_write_pgm( PSTR("motion controller"), 6, 4 );
+    str_write_pgm( PSTR("f:     /      r:     /"), 2, 5 );
+    str_write_pgm( PSTR("front obstacles"), 7, 7 );
+    str_write_pgm( PSTR("location (--)"), 8, 10 );
+    str_write_pgm( PSTR("origin         target"), 5, 13 );
+    str_write_pgm( PSTR("Y ----  P -----  R -----"), 3, 17 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_sleep()
+{
+    for ( uint8_t i = 254; i > 0; --i )
+    {
+        OCR1B = i;
+        spinwait_delay_ms( 1 );
+    }
+    OCR1B = 0;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_wake()
+{
+    OCR1B = 255;
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_heartbeat_flag()
+{
+    char flag;
+
+    switch ( g_spinner_count )
+    {
+        case 0:
+            flag = '-';
+            break;
+
+        case 1:
+            flag = '\\';
+            break;
+
+        case 2:
+            flag = '|';
+            break;
+
+        case 3:
+            flag = '/';
+            break;
+
+        default:
+            flag = '-';
+            break;
+    }
+
+    if ( status_is_mgr_up() )
+    {
+        ili9340_write( flag, 29, 0, ili9340_color_white, ili9340_color_blue );
+    }
+    else
+    {
+        ili9340_write( flag, 29, 0, ili9340_color_white, ili9340_color_red );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_emergency_info()
+{
+    if ( status_is_emergency() )
+    {
+        ili9340_write_pgm( PSTR("       emergency active       "),
+                           0, 25, ili9340_color_white, ili9340_color_red );
+    }
+    else
+    {
+        ili9340_write_pgm( PSTR("                              "),
+                           0, 25, ili9340_color_white, ili9340_color_blue );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_wheel_info()
+{
+    if ( ! status_is_motion_ctl_alive() )
+    {
+        ili9340_write_pgm( PSTR("         offline          "),
+                           2, 5, ili9340_color_white, ili9340_color_red );
+    }
+    else
+    {
+        str_write_pgm( PSTR("f: "),    2, 5 );
+        str_write_pgm( PSTR(" / "),    8, 5 );
+        str_write_pgm( PSTR("  r: "), 14, 5 );
+        str_write_pgm( PSTR(" / "),   22, 5 );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_sonar_front_info()
+{
+    if ( ! status_is_sonar_front_up() )
+    {
+        ili9340_write_pgm( PSTR("         offline          "),
+                           2, 8, ili9340_color_white, ili9340_color_red );
+    }
+    else if ( ! status_is_sonar_front_enabled() )
+    {
+        str_write_pgm( PSTR("        disabled          "), 2, 8 );
+    }
+    else
+    {
+        str_write_pgm( PSTR("  "),   2, 8 );
+        str_write_pgm( PSTR("   "),  7, 8 );
+        str_write_pgm( PSTR("   "), 13, 8 );
+        str_write_pgm( PSTR("   "), 19, 8 );
+        str_write_pgm( PSTR("   "), 25, 8 );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+static void update_gps_status()
+{
+    if ( ! status_is_gps_up() )
+    {
+        str_write( '-',  18, 10 );
+        str_write( '-',  19, 10 );
+        ili9340_write_pgm( PSTR("         offline          "),
+                           2, 11, ili9340_color_white, ili9340_color_red );
+    }
+    else if ( ! status_does_gps_have_fix() )
+    {
+        str_write( '-',  18, 10 );
+        str_write( '-',  19, 10 );
+        str_write_pgm( PSTR("- --- ------  - --- ------"), 2, 11 );
+    }
+    else
+    {
+        str_write( ' ',  3, 11 );
+        str_write( ' ',  7, 11 );
+        str_write( ' ', 14, 11 );
+        str_write( ' ', 15, 11 );
+        str_write( ' ', 17, 11 );
+        str_write( ' ', 21, 11 );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_refresh()
+{
+    update_heartbeat_flag();
+    update_emergency_info();
+    update_wheel_info();
+    update_sonar_front_info();
+    update_gps_status();
+
+    ++g_spinner_count;
+    g_spinner_count %= 4;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_set_mgr_status( const char* msg, uint8_t length )
+{
+    for ( uint8_t i = 0; i < length; ++i )
+    {
+        ili9340_write( *msg++, i + 6, 2, ili9340_color_white, ili9340_color_blue );
+    }
+
+    for ( uint8_t i = length; i < 8 /* maximum message length */; ++i )
+    {
+        ili9340_write( ' ', i + 6, 2, ili9340_color_white, ili9340_color_blue );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_clear_mgr_status()
+{
+    ili9340_write_pgm( PSTR("       "), 6, 2, ili9340_color_white, ili9340_color_blue );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_button_1( bool down )
+{
+    if ( down )
+    {
+        ili9340_write_pgm( PSTR(" A "), 6, 15, ili9340_color_blue, ili9340_color_white );
+    }
+    else
+    {
+        ili9340_write_pgm( PSTR(" A "), 6, 15, ili9340_color_white, ili9340_color_blue );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_button_2( bool down )
+{
+    if ( down )
+    {
+        ili9340_write_pgm( PSTR(" B "), 10, 15, ili9340_color_blue, ili9340_color_white );
+    }
+    else
+    {
+        ili9340_write_pgm( PSTR(" B "), 10, 15, ili9340_color_white, ili9340_color_blue );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_wheel_speed( int8_t front, int8_t rear )
+{
+    char buf[ 6 ];
+
+    str_small_int_with_sign( buf, front );
+    ili9340_write( buf, 5, 5, ili9340_color_white, ili9340_color_blue );
+
+    str_small_int_with_sign( buf, rear );
+    ili9340_write( buf, 19, 5, ili9340_color_white, ili9340_color_blue );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_wheel_position( int8_t front, int8_t rear )
+{
+    char buf[ 6 ];
+
+    str_small_int_with_sign( buf, front );
+    ili9340_write( buf, 11, 5, ili9340_color_white, ili9340_color_blue );
+
+    str_small_int_with_sign( buf, rear );
+    ili9340_write( buf, 25, 5, ili9340_color_white, ili9340_color_blue );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_sonar_front
+(
+    uint8_t left,
+    uint8_t center_left,
+    uint8_t center_right,
+    uint8_t right
+)
+{
+    char  buf[ 6 ];
+    char* p = buf;
+
+    p = print_decimal( buf, left );
+    *p = '\0';
+    str_write( buf, 4, 8 );
+
+    p = print_decimal( buf, center_left );
+    *p = '\0';
+    str_write( buf, 10, 8 );
+
+    p = print_decimal( buf, center_right );
+    *p = '\0';
+    str_write( buf, 16, 8 );
+
+    p = print_decimal( buf, right );
+    *p = '\0';
+    str_write( buf, 22, 8 );
+}
+
+// ----------------------------------------------------------------------------------------
+//  format the IMU value as "+123.4" in the given buf (which should be 7 bytes minimum)
+
+static void format_imu_value( int32_t value, char* buf )
+{
+    char* p = buf + 6; // sign, three digits, decimal pt, one digit, null
+    *p-- = '\0';
+
+    *buf = '+';
+
+    if ( value < 0 )
+    {
+        *buf = '-';
+        value *= -1;
+    }
+
+    // value is scaled by 10,000, so +123.4567 becomes 1,234,567
+    value /= 1000; // discard three least significant digits
+
+    for ( uint8_t i = 0; i < 4; ++i )
+    {
+        int32_t d = value % 10;
+        *p-- = ( '0' + d );
+        value /= 10;
+
+        if ( i == 0 )
+        {
+            *p-- = '.';
+        }
+    }
+
+    // remove the leading zeros, if any
+
+    if ( buf[ 1 ] == '0' )
+    {
+        buf[ 1 ] = ' ';
+
+        if ( buf[ 2 ] == '0' )
+        {
+            buf[ 2 ] = ' ';
+        }
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_yaw( int32_t value )
+{
+    // yaw is displayed as a whole integer, dropping the fractional portion
+
+    char  buf[ 8 ];
+    format_imu_value( value, buf );
+    buf[ 4 ] = '\0';
+
+    str_write( buf, 5, 17 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_pitch( int32_t value )
+{
+    // pitch is displayed as a the least two significant integral digits plus one digit
+    // from the fraction e.g. "+xx.y"
+
+    char  buf[ 8 ];
+    format_imu_value( value, buf );
+    buf[ 1 ] = buf[ 0 ];
+
+    str_write( &buf[ 1 ], 13, 17 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_roll( int32_t value )
+{
+    // roll is displayed as a the least two significant integral digits plus one digit
+    // from the fraction e.g. "+xx.y"
+
+    char  buf[ 8 ];
+    format_imu_value( value, buf );
+    buf[ 1 ] = buf[ 0 ];
+
+    str_write( &buf[ 1 ], 22, 17 );
+}
+
+// ----------------------------------------------------------------------------------------
+//  format the GPS value as "123 45.678" in the given buf (11 bytes minimum)
+
+static void format_gps_value( int16_t degrees, int32_t min_thousandths, char* buf )
+{
+    char* p = buf + 3; // three digits for degrees followed by space
+    *p-- = ' ';
+
+    for ( uint8_t i = 0; i < 3; ++i )
+    {
+        int16_t d = degrees % 10;
+        *p-- = ( '0' + d );
+        degrees /= 10;
+    }
+
+    // remove the leading zeros, if any
+
+    if ( buf[ 0 ] == '0' )
+    {
+        buf[ 0 ] = ' ';
+
+        if ( buf[ 1 ] == '0' )
+        {
+            buf[ 1 ] = ' ';
+        }
+    }
+
+    // now write the factional min_thousandths value
+
+    p = buf + 10; // degrees, space, two digits, decimal point, three digits, null
+    *p-- = '\0';
+
+    for ( uint8_t i = 0; i < 5; ++i )
+    {
+        int32_t d = min_thousandths % 10;
+        *p-- = ( '0' + d );
+        min_thousandths /= 10;
+
+        if ( i == 2 )
+        {
+            *p-- = '.';
+        }
+    }
+
+    // remove the leading zero, if any
+
+    if ( buf[ 4 ] == '0' )
+    {
+        buf[ 4 ] = ' ';
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_latitude( int16_t degrees, int32_t min_thousandths )
+{
+    if ( degrees < 0 )
+    {
+        str_write( 'S', 2, 11 );
+        degrees *= -1;
+    }
+    else
+    {
+        str_write( 'N', 2, 11 );
+    }
+
+    char buf[ 12 ];
+    format_gps_value( degrees, min_thousandths, buf );
+    str_write( buf, 4, 11 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_longitude( int16_t degrees, int32_t min_thousandths )
+{
+    if ( degrees < 0 )
+    {
+        str_write( 'W', 16, 11 );
+        degrees *= -1;
+    }
+    else
+    {
+        str_write( 'E', 16, 11 );
+    }
+
+    char buf[ 12 ];
+    format_gps_value( degrees, min_thousandths, buf );
+    str_write( buf, 18, 11 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void display_update_gps_fix( uint8_t satellites )
+{
+    if ( satellites == 0 )
+    {
+        return;
+    }
+
+    if ( satellites > 10 )
+    {
+        str_write( '1', 18, 10 );
+        satellites -= 10;
+    }
+
+    str_write( '0' + satellites, 19, 10 );
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/func.h	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,115 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  Function prototypes.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#if !defined( FUNC_H )
+#define FUNC_H
+
+#include <stdint.h>
+
+// ----------------------------------------------------------------------------------------
+
+typedef enum
+{
+    button_not_pressed_e = 0x00,
+    button_center_e,
+    button_up_e,
+    button_down_e,
+    button_left_e,
+    button_right_e
+
+} button_press_t;
+
+void button_init();
+void button_update();
+
+bool           button_is_pressed();
+button_press_t button_state();
+
+// ----------------------------------------------------------------------------------------
+
+void status_init();
+void status_update();
+
+void status_got_heartbeat( uint8_t node );
+void status_got_emergency( uint8_t node );
+void status_got_all_clear( uint8_t node );
+void status_got_mgr_update();
+void status_got_sonar_front_enabled();
+void status_got_sonar_front_disabled();
+void status_got_gps_fix( bool has_fix );
+
+bool status_is_emergency();
+bool status_is_mgr_up();
+bool status_is_estop_active();
+bool status_is_motion_ctl_alive();
+bool status_is_sonar_front_up();
+bool status_is_sonar_front_enabled();
+bool status_is_gps_up();
+bool status_does_gps_have_fix();
+
+// ----------------------------------------------------------------------------------------
+
+void display_init();
+void display_refresh();
+
+void display_sleep();
+void display_wake();
+
+void display_set_mgr_status( const char* msg, uint8_t length );
+void display_clear_mgr_status();
+
+void display_update_wheel_speed( int8_t front, int8_t rear );
+void display_update_wheel_position( int8_t front, int8_t rear );
+
+void display_update_sonar_front( uint8_t l, uint8_t c_l, uint8_t c_r, uint8_t r );
+
+void display_update_yaw( int32_t value );
+void display_update_pitch( int32_t value );
+void display_update_roll( int32_t value );
+
+void display_update_latitude( int16_t degrees, int32_t min_thousandths );
+void display_update_longitude( int16_t degrees, int32_t min_thousandths );
+void display_update_gps_fix( uint8_t satellites );
+
+void display_update_button_1( bool down );
+void display_update_button_2( bool down );
+
+// ----------------------------------------------------------------------------------------
+
+bool canmsg_init();
+bool canmsg_did_error_occur();
+bool canmsg_send_emergency();
+bool canmsg_send_all_clear();
+bool canmsg_send_heartbeat();
+bool canmsg_send_button_status();
+bool canmsg_process_pending();
+
+// ----------------------------------------------------------------------------------------
+#endif // #if !defined( FUNC_H )
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/jamfile	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,51 @@
+# -----------------------------------------------------------------------------------------
+#
+#   Copyright (C) 2014 Bob Cook
+#
+#   Bob Cook Development, Robotics Library
+#   http://www.bobcookdev.com/rl/
+#    
+#   Permission is hereby granted, free of charge, to any person obtaining a copy
+#   of this software and associated documentation files (the "Software"), to deal
+#   in the Software without restriction, including without limitation the rights
+#   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#   copies of the Software, and to permit persons to whom the Software is
+#   furnished to do so, subject to the following conditions:
+#
+#   The above copyright notice and this permission notice shall be included in
+#   all copies or substantial portions of the Software.
+#
+#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#   THE SOFTWARE.
+#
+# -----------------------------------------------------------------------------------------
+
+if $(TRACE) { echo "trace /robots/odr-display/jamfile" ; }
+
+SubDir TOP robots odr-display ;
+
+# -----------------------------------------------------------------------------------------
+
+avr_executable
+    odr-display atmega16m1
+    : main.cpp
+      buttons.cpp
+      canmsgs.cpp
+      display.cpp
+      leds.cpp
+      status.cpp
+      packages.avr.can.pkg
+      packages.avr.device.pkg
+      packages.avr.lcd.ili9340.pkg
+      packages.common.can.pkg
+      packages.common.font.pkg
+      packages.common.util.pkg
+    ;
+
+# -----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/leds.cpp	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,42 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  LED functions for the Outdoor Robot user interface board.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#include <avr/io.h>
+
+// ----------------------------------------------------------------------------------------
+
+void leds_off()
+{
+    PORTB |= ( 1 << PB6 ) | ( 1 << PB5 );
+    PORTC |= ( 1 << PC7 ) | ( 1 << PC0 );
+    PORTD |= ( 1 << PD0 );
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/leds.h	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,73 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  LED functions for the Outdoor Robot user interface board.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#if !defined( LEDS_H )
+#define LEDS_H
+
+// ----------------------------------------------------------------------------------------
+
+void leds_off();
+
+// ----------------------------------------------------------------------------------------
+
+void led_red_off();
+void led_red_on();
+void led_red_toggle();
+
+// ----------------------------------------------------------------------------------------
+
+void led_yellow_off();
+void led_yellow_on();
+void led_yellow_toggle();
+
+// ----------------------------------------------------------------------------------------
+
+void led_green_1_off();
+void led_green_1_on();
+void led_green_1_toggle();
+
+// ----------------------------------------------------------------------------------------
+
+void led_green_2_off();
+void led_green_2_on();
+void led_green_2_toggle();
+
+// ----------------------------------------------------------------------------------------
+
+void led_blue_off();
+void led_blue_on();
+void led_blue_toggle();
+
+// ----------------------------------------------------------------------------------------
+
+#include "leds.inl"
+
+// ----------------------------------------------------------------------------------------
+#endif // #if !defined( LEDS_H )
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/leds.inl	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,131 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  LED functions for the Outdoor Robot user interface board.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#include <avr/io.h>
+
+inline void led_red_off() __attribute__((always_inline));
+inline void led_red_off()
+{
+    PORTB |= ( 1 << PB5 );
+}
+
+inline void led_red_on() __attribute__((always_inline));
+inline void led_red_on()
+{
+    PORTB &= ~( 1 << PB5 );
+}
+
+inline void led_red_toggle() __attribute__((always_inline));
+inline void led_red_toggle()
+{
+    PORTB ^= ( 1 << PB5 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+inline void led_yellow_off() __attribute__((always_inline));
+inline void led_yellow_off()
+{
+    PORTB |= ( 1 << PB6 );
+}
+
+inline void led_yellow_on() __attribute__((always_inline));
+inline void led_yellow_on()
+{
+    PORTB &= ~( 1 << PB6 );
+}
+    
+inline void led_yellow_toggle() __attribute__((always_inline));
+inline void led_yellow_toggle()
+{
+    PORTB ^= ( 1 << PB6 );
+}
+    
+// ----------------------------------------------------------------------------------------
+
+inline void led_green_2_off() __attribute__((always_inline));
+inline void led_green_2_off()
+{
+    PORTD |= ( 1 << PD0 );
+}
+
+inline void led_green_2_on() __attribute__((always_inline));
+inline void led_green_2_on()
+{
+    PORTD &= ~( 1 << PD0 );
+}
+    
+inline void led_green_2_toggle() __attribute__((always_inline));
+inline void led_green_2_toggle()
+{
+    PORTD ^= ( 1 << PD0 );
+}
+    
+// ----------------------------------------------------------------------------------------
+
+inline void led_green_1_off() __attribute__((always_inline));
+inline void led_green_1_off()
+{
+    PORTC |= ( 1 << PC0 );
+}
+
+inline void led_green_1_on() __attribute__((always_inline));
+inline void led_green_1_on()
+{
+    PORTC &= ~( 1 << PC0 );
+}
+
+inline void led_green_1_toggle() __attribute__((always_inline));
+inline void led_green_1_toggle()
+{
+    PORTC ^= ( 1 << PC0 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+inline void led_blue_off() __attribute__((always_inline));
+inline void led_blue_off()
+{
+    PORTC |= ( 1 << PC7 );
+}
+
+inline void led_blue_on() __attribute__((always_inline));
+inline void led_blue_on()
+{
+    PORTC &= ~( 1 << PC7 );
+}
+
+inline void led_blue_toggle() __attribute__((always_inline));
+inline void led_blue_toggle()
+{
+    PORTC ^= ( 1 << PC7 );
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/main.cpp	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,401 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  User interface node for the Outdoor Robot project, using a CAN interface.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#include <avr/interrupt.h>
+#include <avr/io.h>
+#include <avr/sleep.h>
+
+#include "project_defs.h"
+
+#include "func.h"
+#include "leds.h"
+
+#include "packages/avr/device/int_helpers.h"
+#include "packages/avr/device/spinwait.h"
+
+#include "packages/common/util/misc.h"
+
+// always after other includes
+#include "packages/avr/device/workaround34734.h"
+
+// ----------------------------------------------------------------------------------------
+
+static const uint8_t trigger_main_loop_ok    = ( 1 << 0 );
+static const uint8_t trigger_send_heartbeat  = ( 1 << 1 );
+static const uint8_t trigger_check_emergency = ( 1 << 2 );
+static const uint8_t trigger_update_status   = ( 1 << 3 );
+static const uint8_t trigger_update_display  = ( 1 << 4 );
+
+static volatile uint8_t g_triggers;
+
+// ----------------------------------------------------------------------------------------
+
+static const uint8_t odrdisp_fatal_error_can_init = 1;
+static const uint8_t odrdisp_fatal_error_can_comm = 2;
+static const uint8_t odrdisp_fatal_error_exiting  = 9;
+
+void odrdisp_fatal_error( uint8_t fault )
+{
+    cli(); // no more interrupts
+    DDRB = 0; DDRC = 0; DDRD = 0; // everything is an input
+    DDRB |= ( 1 << PB6 ) | ( 1 << PB5 ); // except the leds
+    DDRC |= ( 1 << PC7 ) | ( 1 << PC0 );
+    DDRD |= ( 1 << PD0 );
+    leds_off();
+
+    for ( ;; )
+    {
+        for ( uint8_t i = 0; i < 5; i++ )
+        {
+            led_red_on();
+            spinwait_delay_ms( 125 );
+            led_red_off();
+            spinwait_delay_ms( 75 );
+        }
+
+        spinwait_delay_ms( 125 );
+        led_red_on();
+        led_yellow_on();
+        spinwait_delay_ms( 125 );
+
+        for ( uint8_t i = 0; i < fault; i++ )
+        {
+            led_green_1_on();
+            led_green_2_on();
+            spinwait_delay_ms( 350 );
+            
+            led_green_1_off();
+            led_green_2_off();
+            spinwait_delay_ms( 350 );
+        }
+
+        spinwait_delay_ms( 1000 );
+        
+        led_red_off();
+        led_yellow_off();
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void odrdisp_hw_init()
+{
+    //--    Turn off all interrupts.
+
+    cli();
+    interrupts_clear_all();
+
+    //--    Indicator LEDs on PORTB.5, PORTB.6, PORTC.0, PORTC.7, PORTD.0
+
+    DDRB |= ( 1 << PB6 ) | ( 1 << PB5 );
+    DDRC |= ( 1 << PC7 ) | ( 1 << PC0 );
+    DDRD |= ( 1 << PD0 );
+    leds_off();
+
+    //--    Blink the LEDs just to say hello.
+    
+    for ( uint8_t i = 0; i < 5; i++ )
+    {
+        led_red_on();
+        led_yellow_on();
+        led_green_1_on();
+        led_green_2_on();
+        spinwait_delay_ms( 75 );
+        leds_off();
+        spinwait_delay_ms( 75 );
+    }
+
+    //--    Timer0 gives up a periodic "heartbeat" interrupt at ~100 Hz.
+
+    TCCR0A = 0
+
+        | ( 0 << COM0A1 ) | ( 0 << COM0A0 )                 // OC0A disconnected
+        | ( 0 << COM0B1 ) | ( 0 << COM0B0 )                 // OC0B disconnected
+        | ( 1 << WGM01  ) | ( 0 << WGM00  )                 // CTC mode
+        ;
+
+    TCCR0B = 0
+
+        | ( 0 << FOC0A ) | ( 0 << FOC0B )                   // no force output compare
+        | ( 0 << WGM02 )                                    // CTC mode
+        | ( 1 << CS02  ) | ( 0 << CS01  ) | ( 1 << CS00 )   // clk/1024
+        ;
+
+    OCR0A = 156;
+
+    TIMSK0 |= ( 1 << OCIE0A );
+
+    //--    When we sleep we want the "idle" mode e.g. wake up on any interrupt.
+
+    set_sleep_mode( SLEEP_MODE_IDLE );
+
+    //--    Re-enable interrupts.
+
+    sei();
+
+    //--    Initialize the status package.
+
+    status_init();
+
+    //--    Initialize the button package.
+
+    button_init();
+
+    //--    Initialize the display package (the LCD display).
+
+    display_init();
+
+    //--    Initialize the CAN related functions and hardware.
+
+    if ( ! canmsg_init() )
+    {
+        odrdisp_fatal_error( odrdisp_fatal_error_can_init );
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+ISR( TIMER0_COMPA_vect )
+{
+    static uint8_t count = 0;
+
+    //--    The trigger_main_loop_ok flag indicates the main loop is "alive" e.g. still
+    //      running. We only blink the "heartbeat" LED if it continues to toggle.
+    //
+    //      check for button press: every 10 milliseconds
+    //      led blink pattern: 60 milliseconds on, 150 off, 60 on
+    //      check emergency status: every 100 milliseconds
+    //      update the system status and display every 500 milliseconds
+    //      heartbeat: every 2.5 seconds
+
+    button_update();
+
+    if ( count % 10 == 0 )
+    {
+        g_triggers |= trigger_check_emergency;
+    }
+
+    if ( count % 50 == 0 )
+    {
+        g_triggers |= ( trigger_update_status | trigger_update_display );
+    }
+
+    switch ( ++count )
+    {
+        case 10:
+            if ( g_triggers & trigger_main_loop_ok ) { led_green_1_on(); }
+            break;
+
+        case 16:
+            if ( g_triggers & trigger_main_loop_ok ) { led_green_1_off(); }
+            break;
+
+        case 31:
+            if ( g_triggers & trigger_main_loop_ok ) { led_green_1_on(); }
+            break;
+
+        case 37:
+            if ( g_triggers & trigger_main_loop_ok ) { led_green_1_off(); }
+            g_triggers &= ~trigger_main_loop_ok;
+            break;
+
+        case 250:
+            g_triggers |= trigger_send_heartbeat;
+            count = 0;
+            break;
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+int main()
+{
+    //--    Initialize the hardware and send a "hello we started" message.
+
+    odrdisp_hw_init();
+    
+    spinwait_delay_ms( 1000 );
+    canmsg_send_all_clear();
+
+#if 0
+    for ( ;; )
+    {
+        switch ( button_state() )
+        {
+            case button_not_pressed_e:
+                leds_off();
+                break;
+
+            case button_center_e:
+                led_red_on();
+                led_yellow_on();
+                led_green_1_on();
+                led_green_2_on();
+                break;
+
+            case button_up_e:
+                led_red_on();
+                led_yellow_off();
+                led_green_1_off();
+                led_green_2_off();
+                break;
+
+            case button_down_e:
+                led_red_off();
+                led_yellow_on();
+                led_green_1_off();
+                led_green_2_off();
+                break;
+
+            case button_left_e:
+                led_red_off();
+                led_yellow_off();
+                led_green_1_on();
+                led_green_2_off();
+                break;
+
+            case button_right_e:
+                led_red_off();
+                led_yellow_off();
+                led_green_1_off();
+                led_green_2_on();
+                break;
+        }
+    }
+#endif
+
+    //--    Loop forever responding to CAN messages and system status.
+
+    uint8_t can_comm_errors = 0;
+
+    for ( ;; )
+    {
+        //--    The main loop is still running.
+
+        g_triggers |= trigger_main_loop_ok;
+
+        //--    Sleep until there is a new interrupt (the CAN driver gets one, as does
+        //      the periodic heartbeat, so we won't sleep forever).
+
+        sleep_mode();
+
+        if ( button_is_pressed() )
+        {
+//            display_sleep();
+        }
+        else
+        {
+//            display_wake();
+        }
+
+        //--    Check for an emergency situation?
+
+        if ( g_triggers & trigger_check_emergency )
+        {
+            g_triggers &= ~trigger_check_emergency;
+
+            if ( status_is_emergency() )
+            {
+                led_red_on();
+            }
+            else
+            {
+                led_red_off();
+            }
+        }
+
+        //--    Time to send the heartbeat message?
+
+        if ( g_triggers & trigger_send_heartbeat )
+        {
+            g_triggers &= ~trigger_send_heartbeat;
+
+            if ( ! canmsg_send_heartbeat() )
+            {
+                ++can_comm_errors;
+            }
+        }
+
+        //--    Time to check status?
+
+        if ( g_triggers & trigger_update_status )
+        {
+            g_triggers &= ~trigger_update_status;
+            status_update();
+        }
+
+        //--    Time to refresh the display?
+        
+        if ( g_triggers & trigger_update_display )
+        {
+            g_triggers &= ~trigger_update_display;
+            display_refresh();
+        }
+
+        //--    Any pending CAN messages to receive/process?
+
+        if ( ! canmsg_process_pending() )
+        {
+            ++can_comm_errors;  // cannot overflow due to check below
+        }
+
+        if ( canmsg_did_error_occur() )
+        {
+            ++can_comm_errors;  // cannot overflow due to check below
+        }
+
+        //--    Report warning and/or error conditions.
+
+        bool warning = false;
+
+        if ( can_comm_errors > 10 )
+        {
+            odrdisp_fatal_error( odrdisp_fatal_error_can_comm );
+        }
+        else if ( can_comm_errors > 0 )
+        {
+            warning = true;
+        }
+
+        if ( warning )
+        {
+            led_yellow_on();
+        }
+        else
+        {
+            led_yellow_off();
+        }
+    }
+
+    odrdisp_fatal_error( odrdisp_fatal_error_exiting );
+    return 0;
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/project_defs.h	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,87 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2014 Bob Cook
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+// ----------------------------------------------------------------------------------------
+
+#if !defined( PROJECT_DEFS_H )
+#define PROJECT_DEFS_H
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/common
+
+#define PRJ_CPU_FREQ    16000000L
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/can
+
+#define PRJ_M1CAN_ENABLE
+#define PRJ_M1CAN_CANBUS_250_KHZ
+#define PRJ_M1CAN_TX_BUFFER_SIZE  4
+#define PRJ_M1CAN_RX_BUFFER_SIZE  8
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/common/util
+
+#define PRJ_PRINTDEC_ENABLE_OUTPUT_TO_BUFFER
+#define PRJ_PRINTHEX_ENABLE_OUTPUT_TO_BUFFER
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/device/spi
+
+#define PRJ_SPI_ENABLE_MASTER
+#define PRJ_SPI_BUS_MODE_0
+#define PRJ_SPI_DATA_DIRECTION_MSB
+#define PRJ_SPI_CLOCK_FACTOR_DIV_2
+
+// ----------------------------------------------------------------------------------------
+//  packages/avr/lcd/ili9340
+
+#define PRJ_ILI9340_ENABLE
+
+#define PRJ_ILI9340_SELECT_PORT  PORTD
+#define PRJ_ILI9340_SELECT_PIN   PD5
+#define PRJ_ILI9340_SELECT_DDR   DDRD
+
+#define PRJ_ILI9340_COMMAND_PORT  PORTB
+#define PRJ_ILI9340_COMMAND_PIN   PB2
+#define PRJ_ILI9340_COMMAND_DDR   DDRB
+
+#define PRJ_ILI9340_USE_HW_RESET
+#define PRJ_ILI9340_RESET_PORT   PORTD
+#define PRJ_ILI9340_RESET_PIN    PD7
+#define PRJ_ILI9340_RESET_DDR    DDRD
+
+#define PRJ_ILI9340_DISPLAY_ORIENTATION_180
+#define PRJ_ILI9340_INC_WRITE_CHAR
+#define PRJ_ILI9340_INC_WRITE_CSTRINGS
+#define PRJ_ILI9340_INC_WRITE_PGM_CSTRINGS
+#define PRJ_ILI9340_INC_DRAWING
+
+// ----------------------------------------------------------------------------------------
+//  packages/common/font
+
+#define PRJ_FONT_8X12_ENABLE
+
+
+#endif // #if !defined( PROJECT_DEFS_H )
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr-display/status.cpp	Sun Jul 13 09:15:53 2014 -0700
@@ -0,0 +1,276 @@
+// ----------------------------------------------------------------------------------------
+//
+//  Copyright (C) 2011-2014 Bob Cook
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+// 
+//  Status display functions for the Outdoor Robot user interface board.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+// 
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+// 
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+//
+//  There is an implicit assumption our "heartbeat" is 2 Hz (twice per second).
+//
+// ----------------------------------------------------------------------------------------
+
+#include "func.h"
+
+#include <avr/io.h>
+
+#include "packages/common/can/can_nodes.h"
+
+#include "leds.h"
+
+// ----------------------------------------------------------------------------------------
+
+static volatile uint8_t g_beats_without_mgr;
+static volatile uint8_t g_beats_without_motion_ctl;
+static volatile uint8_t g_beats_without_sonarfront;
+static volatile uint8_t g_beats_without_gps;
+
+static volatile uint8_t g_status_flags;
+
+static const uint8_t status_flag_emergency_active    = ( 1 << 0 );
+static const uint8_t status_flag_sonar_front_enabled = ( 1 << 1 );
+static const uint8_t status_flag_gps_has_fix         = ( 1 << 2 );
+
+// ----------------------------------------------------------------------------------------
+
+void status_init()
+{
+    // initalize the heartbeat watchdogs assuming no contact yet
+    
+    g_beats_without_mgr = 100;
+    g_beats_without_sonarfront = 100;
+    g_beats_without_motion_ctl = 100;
+    g_beats_without_gps = 100;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_got_heartbeat( uint8_t source )
+{
+    switch ( source )
+    {
+        case can_node_odr_manager:
+            g_beats_without_mgr = 0;
+            break;
+
+        case can_node_odr_motion:
+            g_beats_without_motion_ctl = 0;
+            break;
+
+        case can_node_odr_sonar_front:
+            break;
+
+        case can_node_sensor_gps:
+            g_beats_without_gps = 0;
+            break;
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_set_emergency()
+{
+    g_status_flags |= status_flag_emergency_active;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_got_emergency( uint8_t source )
+{
+    g_status_flags |= status_flag_emergency_active;
+
+    switch ( source )
+    {
+        case can_node_odr_manager:
+            break;
+
+        case can_node_odr_motion:
+            break;
+
+        case can_node_odr_sonar_front:
+            break;
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_got_all_clear( uint8_t source )
+{
+    switch ( source )
+    {
+        case can_node_odr_manager:
+            g_status_flags &= ~status_flag_emergency_active;
+            break;
+
+        case can_node_odr_motion:
+            break;
+
+        case can_node_odr_sonar_front:
+            break;
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_update()
+{
+    //--    Increment all of the beat counters.
+
+    if ( g_beats_without_mgr < 200 ) { ++g_beats_without_mgr; }
+    if ( g_beats_without_sonarfront < 200 ) { ++g_beats_without_sonarfront; }
+    if ( g_beats_without_motion_ctl < 200 ) { ++g_beats_without_motion_ctl; }
+    if ( g_beats_without_gps < 200 ) { ++g_beats_without_gps; }
+
+#if 0
+    //--    Has the ESTOP condition transitioned? Note we ignore this check if the
+    //      override switch has been set.
+
+    if ( g_status_flags & status_flag_estop_triggered )
+    {
+        if ( ! did_estop_timeout() )
+        {
+            // switching from ESTOP triggered to not triggered
+            g_status_flags &= ~status_flag_estop_triggered;
+            canmsg_send_all_clear();
+        }
+        else
+        {
+            // keep sending the emergency until the ESTOP is not triggered
+            canmsg_send_emergency();
+        }
+    }
+    else
+    {
+        if ( did_estop_timeout() )
+        {
+            // switching from ESTOP not triggered to triggered
+            g_status_flags |= status_flag_estop_triggered;
+            canmsg_send_emergency();
+        }
+        else if ( status_is_emergency() )
+        {
+            // keep sending the all-clear until we are out of the emergency state
+            canmsg_send_all_clear();
+        }
+    }
+#endif
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_got_mgr_update()
+{
+    g_beats_without_mgr = 0;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_got_sonar_front_enabled()
+{
+    g_beats_without_sonarfront = 0;
+    g_status_flags |= status_flag_sonar_front_enabled;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_got_sonar_front_disabled()
+{
+    g_beats_without_sonarfront = 0;
+    g_status_flags &= ~status_flag_sonar_front_enabled;
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool status_is_emergency()
+{
+    // more than two seconds == down
+    if ( g_beats_without_mgr > 5 )
+    {
+        return true;
+    }
+
+    return ( g_status_flags & status_flag_emergency_active ) > 0;
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool status_is_mgr_up()
+{
+    // more than five seconds == down
+    return ( g_beats_without_mgr < 10 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool status_is_motion_ctl_alive()
+{
+    // more than four seconds == down
+    return ( g_beats_without_motion_ctl < 8 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool status_is_sonar_front_up()
+{
+    // more than four seconds == down
+    return ( g_beats_without_sonarfront < 8 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool status_is_sonar_front_enabled()
+{
+    return ( g_status_flags & status_flag_sonar_front_enabled ) > 0;
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool status_is_gps_up()
+{
+    // more than four seconds == down
+    return ( g_beats_without_gps < 8 );
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool status_does_gps_have_fix()
+{
+    // more than four seconds == down
+    return ( g_status_flags & status_flag_gps_has_fix ) > 0;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void status_got_gps_fix( bool has_fix )
+{
+    if ( has_fix )
+    {
+        g_status_flags |= status_flag_gps_has_fix;
+    }
+    else
+    {
+        g_status_flags &= ~status_flag_gps_has_fix;
+    }
+}
+
+// ----------------------------------------------------------------------------------------
+