changeset 230:50414c680910 main

Split the IMU reader out to a separate application. Rework the scoreboard accordingly. Also remove all the legacy "odr-controller" logic, and improve the management of "emergency" situations for all nodes.
author Bob Cook <bob@bobcookdev.com>
date Sat, 12 Jul 2014 17:21:10 -0700
parents 79283dc7dc54
children 783f69f37c64
files main/robots/odr/ImuApp.cpp main/robots/odr/ImuApp.h main/robots/odr/ImuReader.cpp main/robots/odr/ImuReader.h main/robots/odr/ODRApp.cpp main/robots/odr/SafetyTask.cpp main/robots/odr/SafetyTask.h main/robots/odr/Scoreboard.cpp main/robots/odr/Scoreboard.h main/robots/odr/StartupTask.cpp main/robots/odr/StartupTask.h main/robots/odr/jamfile main/robots/odr/odr_ubuntu.xml
diffstat 13 files changed, 862 insertions(+), 393 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr/ImuApp.cpp	Sat Jul 12 17:21:10 2014 -0700
@@ -0,0 +1,248 @@
+// ----------------------------------------------------------------------------------------
+//
+//  robots/odr/ImuApp.cpp
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+//
+//  Application object for the IMU sensor bridge.
+//
+//  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.
+//
+// ----------------------------------------------------------------------------------------
+
+#include <iostream>
+#include <memory>
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include <Poco/Logger.h>
+#include <Poco/Thread.h>
+#include <Poco/Util/Application.h>
+#include <Poco/Util/HelpFormatter.h>
+#include <Poco/Util/LoggingSubsystem.h>
+#include <Poco/Util/Option.h>
+#include <Poco/Util/OptionSet.h>
+
+#include "ImuApp.h"
+
+#include "ImuReader.h"
+
+#include "packages/common/can/can_helpers.h"
+#include "packages/common/can/can_messages.h"
+#include "packages/common/can/can_nodes.h"
+
+#include "packages/linux/can/CANMessage.h"
+#include "packages/linux/can/CANMsgProcessor.h"
+
+// ----------------------------------------------------------------------------------------
+
+void ImuApp::sendImuDataUpdate( Poco::Timer& timer )
+{
+    // if the IMU is not online then we don't send any messages
+
+    if ( ! ImuReader::isImuAlive() )
+    {
+        return;
+    }
+
+    // YAW
+
+    uint32_t imudatamsgid = can_build_message_id(
+                    can_node_odr_manager, can_node_broadcast, can_dataid_imu_yaw );
+
+    can_data_imu_data data;
+    data.data = static_cast< int32_t >( ImuReader::imuYaw() * can_data_imu_multiplier );
+
+    CANMessage::QueueToSend(
+            new CANMessage( imudatamsgid,
+                            reinterpret_cast< uint8_t* >( &data ),
+                            sizeof( data ) ) );
+
+    // PITCH
+
+    imudatamsgid = can_build_message_id(
+                    can_node_odr_manager, can_node_broadcast, can_dataid_imu_pitch );
+
+    data.data = static_cast< int32_t >( ImuReader::imuPitch() * can_data_imu_multiplier );
+
+    CANMessage::QueueToSend(
+            new CANMessage( imudatamsgid,
+                            reinterpret_cast< uint8_t* >( &data ),
+                            sizeof( data ) ) );
+
+    // ROLL
+
+    imudatamsgid = can_build_message_id(
+                    can_node_odr_manager, can_node_broadcast, can_dataid_imu_roll );
+
+    data.data = static_cast< int32_t >( ImuReader::imuRoll() * can_data_imu_multiplier );
+
+    CANMessage::QueueToSend(
+            new CANMessage( imudatamsgid,
+                            reinterpret_cast< uint8_t* >( &data ),
+                            sizeof( data ) ) );
+}
+
+// ----------------------------------------------------------------------------------------
+
+ImuApp::ImuApp()
+    : m_helpRequested( false ),
+      m_runloopActive( false ),
+      m_timerSendImuDataUpdate()
+{
+}
+
+// ----------------------------------------------------------------------------------------
+
+ImuApp::~ImuApp()
+{
+}
+
+// ----------------------------------------------------------------------------------------
+
+void ImuApp::initialize( Poco::Util::Application& self )
+{
+    loadConfiguration();
+    Poco::Util::ServerApplication::initialize( self );
+
+    addSubsystem( new Poco::Util::LoggingSubsystem() );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void ImuApp::uninitialize()
+{
+    Poco::Util::ServerApplication::uninitialize();
+}
+
+// ----------------------------------------------------------------------------------------
+
+void ImuApp::defineOptions( Poco::Util::OptionSet& options )
+{
+    Poco::Util::ServerApplication::defineOptions( options );
+
+    options.addOption(
+        Poco::Util::Option( "help", "h", "display argument help information" )
+            .required( false )
+            .repeatable( false )
+            .callback( Poco::Util::OptionCallback<ImuApp>( this, &ImuApp::handleHelp ) ) );
+}
+
+// ----------------------------------------------------------------------------------------
+
+void ImuApp::handleHelp( const std::string& name, const std::string& value )
+{
+    Poco::Util::HelpFormatter helpFormatter( options() );
+    helpFormatter.setCommand( commandName() );
+    helpFormatter.format( std::cout );
+    stopOptionsProcessing();
+    m_helpRequested = true;
+}
+
+// ----------------------------------------------------------------------------------------
+
+int ImuApp::main( const std::vector<std::string>& args )
+{
+    if ( m_helpRequested )
+    {
+        return Poco::Util::Application::EXIT_OK;
+    }
+
+    loadConfiguration();
+
+    logger().information( "------------------------------------------------------" );
+    logger().information( "ImuApp::main() started" );
+
+    std::string canInterfaceName = config().getString( "can.interfaceName", "can0" );
+    logger().information( "using CANbus interface name \"" + canInterfaceName + "\"" );
+
+    Poco::Thread    canMsgProcessorThread;
+    CANMsgProcessor canMsgProcessor( canInterfaceName, logger().name() );
+    canMsgProcessor.logIncomingMessages( config().getBool( "can.logIncoming", "no" ) );
+    canMsgProcessor.logOutgoingMessages( config().getBool( "can.logOutgoing", "no" ) );
+    canMsgProcessorThread.start( canMsgProcessor );
+
+    std::string imuSerialPortPath = config().getString( "imu.serialPortPath", "" );
+
+    Poco::Thread imuReaderThread;
+    ImuReader    imuReader( logger().name(), imuSerialPortPath );
+
+    if ( ! imuSerialPortPath.empty() )
+    {
+        logger().information( "using IMU serial port \"" + imuSerialPortPath + "\"" );
+        imuReaderThread.start( imuReader );
+    }
+    else
+    {
+        logger().information( "no IMU serial port configured; disabled" );
+    }
+
+    m_timerSendImuDataUpdate.setPeriodicInterval( 750 /* milliseconds */ );
+    m_timerSendImuDataUpdate.start(
+            Poco::TimerCallback< ImuApp >( *this, &ImuApp::sendImuDataUpdate ) );
+
+    m_runloopActive = true;
+    while ( m_runloopActive ) Poco::Thread::sleep( 5000 /* milliseconds */ );
+
+    imuReader.timeToQuit();
+    imuReaderThread.join();
+
+    canMsgProcessor.timeToQuit();
+    canMsgProcessorThread.join();
+
+    logger().information( "ImuApp::main() stopping" );
+
+    return Poco::Util::Application::EXIT_OK;
+}
+
+// ----------------------------------------------------------------------------------------
+
+int main( int argc, char** argv )
+{
+    int result = -1;
+
+    try
+    {
+        ImuApp app;
+        result = app.run( argc, argv );
+    }
+    catch ( const Poco::Exception& ex )
+    {
+        std::cerr << "Failed to start application, Poco::Exception: "
+            << ex.displayText() << std::endl;
+    }
+    catch ( const std::exception& ex )
+    {
+        std::cerr << "Failed to start application, std::exception: "
+            << ex.what() << std::endl;
+    }
+    catch ( ... )
+    {
+        std::cerr << "Failed to start application, unknown exception" << std::endl;
+    }
+
+    return result;
+}
+
+// ----------------------------------------------------------------------------------------
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/robots/odr/ImuApp.h	Sat Jul 12 17:21:10 2014 -0700
@@ -0,0 +1,76 @@
+// ----------------------------------------------------------------------------------------
+//
+//  robots/odr/ImuApp.h
+//    
+//  Bob Cook Development, Robotics Library
+//  http://www.bobcookdev.com/rl/
+//    
+//  Application object for the IMU sensor bridge.
+//
+//  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.
+//
+// ----------------------------------------------------------------------------------------
+
+#ifndef BCDRL_ROBOTS_ODR_IMUAPP_H
+#define BCDRL_ROBOTS_ODR_IMUAPP_H
+
+#include <Poco/Timer.h>
+#include <Poco/Util/ServerApplication.h>
+
+namespace Poco
+{
+    namespace Util
+    {
+        class OptionSet;
+    }
+}
+
+#include <string>
+#include <vector>
+
+// ----------------------------------------------------------------------------------------
+
+class ImuApp : public Poco::Util::ServerApplication
+{
+    public:
+        ImuApp();
+        virtual ~ImuApp();
+
+    protected:
+        void initialize( Poco::Util::Application& self );
+        void uninitialize();
+        void defineOptions( Poco::Util::OptionSet& options );
+        void handleHelp( const std::string& name, const std::string& value );
+        int  main( const std::vector< std::string >& args );
+
+    private:
+        void sendImuDataUpdate( Poco::Timer& timer );
+
+    private:
+        bool        m_helpRequested;
+        bool        m_runloopActive;
+        Poco::Timer m_timerSendImuDataUpdate;
+};
+
+// ----------------------------------------------------------------------------------------
+#endif // #ifndef BCDRL_ROBOTS_ODR_IMUAPP_H
+// ----------------------------------------------------------------------------------------
+
--- a/main/robots/odr/ImuReader.cpp	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/ImuReader.cpp	Sat Jul 12 17:21:10 2014 -0700
@@ -7,7 +7,7 @@
 // 
 //  Runnable object that reads and parses messages from a SparkFun 9-DOF Razor IMU.
 //
-//  Copyright (c) 2012 Bob Cook
+//  Copyright (c) 2012-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
@@ -52,8 +52,6 @@
 #include <Poco/NumberParser.h>
 #include <Poco/Thread.h>
 
-#include "Scoreboard.h"
-
 #include "packages/common/can/can_helpers.h"
 #include "packages/common/can/can_messages.h"
 #include "packages/common/can/can_nodes.h"
@@ -64,6 +62,84 @@
 
 // ----------------------------------------------------------------------------------------
 
+Poco::RWLock    ImuReader::sm_rwLock;
+bool            ImuReader::sm_isImuAlive;
+Poco::Timestamp ImuReader::sm_imuLastUpdate;
+double          ImuReader::sm_imuRoll;
+double          ImuReader::sm_imuPitch;
+double          ImuReader::sm_imuYaw;
+
+// ----------------------------------------------------------------------------------------
+
+void ImuReader::imuIsOffline()
+{
+    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
+    sm_isImuAlive = false;
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool ImuReader::isImuAlive()
+{
+    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
+
+    if ( ! sm_isImuAlive )
+    {
+        return false;
+    }
+
+    // check if the IMU is actually offline; more than 2 seconds silence means yes
+
+    static const Poco::Timestamp::TimeDiff TwoSeconds = Poco::Timestamp::resolution() * 2;
+
+    if ( sm_imuLastUpdate.elapsed() > TwoSeconds )
+    {
+        sm_isImuAlive = false;
+    }
+
+    return sm_isImuAlive;
+}
+
+// ----------------------------------------------------------------------------------------
+
+double ImuReader::imuRoll()
+{
+    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
+    return sm_imuRoll;
+}
+
+// ----------------------------------------------------------------------------------------
+
+double ImuReader::imuPitch()
+{
+    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
+    return sm_imuPitch;
+}
+
+// ----------------------------------------------------------------------------------------
+
+double ImuReader::imuYaw()
+{
+    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
+    return sm_imuYaw;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void ImuReader::imuUpdate( double roll, double pitch, double yaw )
+{
+    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
+
+    sm_isImuAlive = true;
+    sm_imuLastUpdate.update();
+
+    sm_imuRoll  = roll;
+    sm_imuPitch = pitch;
+    sm_imuYaw   = yaw;
+}
+
+// ----------------------------------------------------------------------------------------
+
 ImuReader::ImuReader( const std::string& loggerName, const std::string& serialPortPath )
     : Poco::Runnable(),
       m_loggerName( loggerName ),
@@ -605,7 +681,7 @@
         {
             // to keep ourselves sane, start by assuming the IMU is offline
 
-            Scoreboard::imuIsOffline();
+            ImuReader::imuIsOffline();
 
             // now try to find it on the serial port
 
@@ -648,7 +724,7 @@
                 {
                     if ( ++timeoutNoData == 10 )
                     {
-                        Scoreboard::imuIsOffline();
+                        ImuReader::imuIsOffline();
                     }
                     continue;
                 }
@@ -670,9 +746,9 @@
                     {
                         double averageYaw = computeAverageYaw( valuesYaw );
 
-                        Scoreboard::imuUpdate( m_parseValueRoll,
-                                               m_parseValuePitch,
-                                               averageYaw );
+                        ImuReader::imuUpdate( m_parseValueRoll,
+                                              m_parseValuePitch,
+                                              averageYaw );
 
                         // clear the vector, to accumulate more samples
 
--- a/main/robots/odr/ImuReader.h	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/ImuReader.h	Sat Jul 12 17:21:10 2014 -0700
@@ -7,7 +7,7 @@
 // 
 //  Runnable object that reads and parses messages from a SparkFun 9-DOF Razor IMU.
 //
-//  Copyright (c) 2012 Bob Cook
+//  Copyright (c) 2012-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
@@ -38,12 +38,21 @@
 #include <Poco/Runnable.h>
 #include <Poco/RWLock.h>
 #include <Poco/Timer.h>
+#include <Poco/Timestamp.h>
 
 // ----------------------------------------------------------------------------------------
 
 class ImuReader : public Poco::Runnable
 {
     public:
+        static void   imuIsOffline();
+        static bool   isImuAlive();
+        static double imuRoll();
+        static double imuPitch();
+        static double imuYaw();
+        static void   imuUpdate( double roll, double pitch, double yaw );
+
+    public:
         ImuReader( const std::string& loggerName, const std::string& serialPortPath );
         virtual ~ImuReader();
         virtual void run();
@@ -64,6 +73,14 @@
         }   imu_parse_state_t;
 
     private:
+        static Poco::RWLock    sm_rwLock;
+        static bool            sm_isImuAlive;
+        static double          sm_imuRoll;
+        static double          sm_imuPitch;
+        static double          sm_imuYaw;
+        static Poco::Timestamp sm_imuLastUpdate;
+
+    private:
         std::string       m_loggerName;
         std::string       m_serialPortPath;
         Poco::Event       m_quitEvent;
--- a/main/robots/odr/ODRApp.cpp	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/ODRApp.cpp	Sat Jul 12 17:21:10 2014 -0700
@@ -47,7 +47,6 @@
 
 #include "AvoidBySonarTask.h"
 #include "CruisingTask.h"
-#include "ImuReader.h"
 #include "MotorsAndServos.h"
 #include "NavigateTask.h"
 #include "SafetyTask.h"
@@ -147,37 +146,22 @@
     Scoreboard   scoreboard( logger().name() );
     scoreboardThread.start( scoreboard );
 
-    std::string imuSerialPortPath = config().getString( "imu.serialPortPath", "" );
-
-    Poco::Thread imuReaderThread;
-    ImuReader    imuReader( logger().name(), imuSerialPortPath );
+    m_tasks.clear();
 
-    if ( ! imuSerialPortPath.empty() )
-    {
-        logger().information( "using IMU serial port \"" + imuSerialPortPath + "\"" );
-        imuReaderThread.start( imuReader );
-    }
-    else
-    {
-        logger().information( "no IMU serial port configured; disabled" );
-    }
-
-    m_tasks.clear();
+    m_tasks.push_back(
+            Poco::SharedPtr< TaskObject >( new SafetyTask( logger().name() ) ) );
 
     m_tasks.push_back(
             Poco::SharedPtr< TaskObject >( new StartupTask( logger().name() ) ) );
 
     m_tasks.push_back(
-            Poco::SharedPtr< TaskObject >( new SafetyTask( logger().name() ) ) );
-
-    m_tasks.push_back(
             Poco::SharedPtr< TaskObject >( new AvoidBySonarTask( logger().name() ) ) );
 
-    m_tasks.push_back(
-            Poco::SharedPtr< TaskObject >( new TrackWaypointsTask( logger().name() ) ) );
+//    m_tasks.push_back(
+//            Poco::SharedPtr< TaskObject >( new TrackWaypointsTask( logger().name() ) ) );
 
-    m_tasks.push_back(
-            Poco::SharedPtr< TaskObject >( new SquareCourseTask( logger().name() ) ) );
+//    m_tasks.push_back(
+//            Poco::SharedPtr< TaskObject >( new SquareCourseTask( logger().name() ) ) );
 
     m_tasks.push_back(
             Poco::SharedPtr< TaskObject >( new NavigateTask( logger().name() ) ) );
--- a/main/robots/odr/SafetyTask.cpp	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/SafetyTask.cpp	Sat Jul 12 17:21:10 2014 -0700
@@ -7,7 +7,7 @@
 //
 //  Subsumption task to enact safety measures under certain unsafe conditions.
 //
-//  Copyright (c) 2011 Bob Cook
+//  Copyright (c) 2011-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
@@ -39,8 +39,11 @@
 SafetyTask::SafetyTask( const std::string& loggerName )
     : TaskObject( loggerName ),
       m_lastMessageTime(),
-      m_lastEstopStatus( Scoreboard::isEStopActive() ),
-      m_lastControllerStatus( Scoreboard::isControllerAlive() ),
+      m_lastEmergencyStatus( Scoreboard::isEmergencyActive() ),
+      m_lastDisplayStatus( Scoreboard::isDisplayAlive() ),
+      m_lastGpsStatus( Scoreboard::isGpsAlive() ),
+      m_lastImuStatus( Scoreboard::isImuAlive() ),
+      m_lastMotionStatus( Scoreboard::isMotionAlive() ),
       m_lastSonarFrontStatus( Scoreboard::isSonarFrontAlive() )
 {
 }
@@ -62,22 +65,76 @@
 
 bool SafetyTask::wantsControl()
 {
-    // odr-controller
+    // overall emergency situation
     
-    if ( ! Scoreboard::isControllerAlive() )
+    if ( Scoreboard::isEmergencyActive() )
     {
-        if ( m_lastControllerStatus )
+        if ( ! m_lastEmergencyStatus )
         {
-            log().information( "SafetyTask: controller is not alive" );
-            m_lastControllerStatus = false;
+            log().information( "SafetyTask: emergency situation active" );
+            m_lastEmergencyStatus = true;
+        }
+
+        return true;
+    }
+    else if ( m_lastEmergencyStatus )
+    {
+        log().information( "SafetyTask: emergency situation cleared" );
+        m_lastEmergencyStatus = false;
+    }
+
+    // odr-display
+    
+    if ( ! Scoreboard::isDisplayAlive() )
+    {
+        if ( m_lastDisplayStatus )
+        {
+            log().information( "SafetyTask: odr-display is not alive" );
+            m_lastDisplayStatus = false;
         }
 
         return true;
     }
-    else if ( ! m_lastControllerStatus )
+    else if ( ! m_lastDisplayStatus )
+    {
+        log().information( "SafetyTask: odr-display is alive" );
+        m_lastDisplayStatus = true;
+    }
+
+    // odr-gps
+    
+    if ( ! Scoreboard::isGpsAlive() )
+    {
+        if ( m_lastGpsStatus )
+        {
+            log().information( "SafetyTask: sensor-gps is not alive" );
+            m_lastGpsStatus = false;
+        }
+
+        return true;
+    }
+    else if ( ! m_lastGpsStatus )
     {
-        log().information( "SafetyTask: controller is alive" );
-        m_lastControllerStatus = true;
+        log().information( "SafetyTask: sensor-gps is alive" );
+        m_lastGpsStatus = true;
+    }
+
+    // odr-motion
+    
+    if ( ! Scoreboard::isMotionAlive() )
+    {
+        if ( m_lastMotionStatus )
+        {
+            log().information( "SafetyTask: odr-motion is not alive" );
+            m_lastMotionStatus = false;
+        }
+
+        return true;
+    }
+    else if ( ! m_lastMotionStatus )
+    {
+        log().information( "SafetyTask: odr-motion is alive" );
+        m_lastMotionStatus = true;
     }
 
     // odr-sonar-front
@@ -98,22 +155,22 @@
         m_lastSonarFrontStatus = true;
     }
 
-    // emergency stop
+    // imu
     
-    if ( Scoreboard::isEStopActive() )
+    if ( ! Scoreboard::isImuAlive() )
     {
-        if ( ! m_lastEstopStatus )
+        if ( m_lastImuStatus )
         {
-            log().information( "SafetyTask: estop is active" );
-            m_lastEstopStatus = true;
+            log().information( "SafetyTask: sensor-imu is not alive" );
+            m_lastImuStatus = false;
         }
 
-//        return true;
+        return true;
     }
-    else if ( m_lastEstopStatus )
+    else if ( ! m_lastImuStatus )
     {
-        log().information( "SafetyTask: estop is not active" );
-        m_lastEstopStatus = false;
+        log().information( "SafetyTask: sensor-imu is alive" );
+        m_lastImuStatus = true;
     }
 
     // otherwise no issues
--- a/main/robots/odr/SafetyTask.h	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/SafetyTask.h	Sat Jul 12 17:21:10 2014 -0700
@@ -7,7 +7,7 @@
 //
 //  Subsumption task to enact safety measures under certain unsafe conditions.
 //
-//  Copyright (c) 2011 Bob Cook
+//  Copyright (c) 2011-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
@@ -47,8 +47,11 @@
 
     private:
         Poco::Timestamp m_lastMessageTime;
-        bool            m_lastEstopStatus;
-        bool            m_lastControllerStatus;
+        bool            m_lastEmergencyStatus;
+        bool            m_lastDisplayStatus;
+        bool            m_lastGpsStatus;
+        bool            m_lastImuStatus;
+        bool            m_lastMotionStatus;
         bool            m_lastSonarFrontStatus;
 };
 
--- a/main/robots/odr/Scoreboard.cpp	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/Scoreboard.cpp	Sat Jul 12 17:21:10 2014 -0700
@@ -46,39 +46,82 @@
 
 // ----------------------------------------------------------------------------------------
 
-Poco::RWLock    Scoreboard::sm_rwLock;
-bool            Scoreboard::sm_isEmergencyActive;
-bool            Scoreboard::sm_MotionEmergency;
-bool            Scoreboard::sm_isMotionAlive;
-bool            Scoreboard::sm_isControllerAlive;
-bool            Scoreboard::sm_isEStopActive;
-bool            Scoreboard::sm_isButtonOneDown;
-bool            Scoreboard::sm_isButtonTwoDown;
+// scoreboard lock
+Poco::RWLock Scoreboard::sm_rwLock;
+
+// overall emergency state
+bool Scoreboard::sm_isEmergencyActive;
+
+// message broadcast
 std::string     Scoreboard::sm_msgLastText;
 Poco::Timestamp Scoreboard::sm_msgLastUpdate;
-bool            Scoreboard::sm_isSonarFrontAlive;
-bool            Scoreboard::sm_isSonarFrontEnabled;
-uint8_t         Scoreboard::sm_sonarFrontLeft;
-uint8_t         Scoreboard::sm_sonarFrontCenterLeft;
-uint8_t         Scoreboard::sm_sonarFrontCenterRight;
-uint8_t         Scoreboard::sm_sonarFrontRight;
+
+// odr-display
+bool Scoreboard::sm_DisplayEmergency;
+bool Scoreboard::sm_isDisplayAlive;
+
+// odr-gps
+bool            Scoreboard::sm_GpsEmergency;
 bool            Scoreboard::sm_isGpsAlive;
 bool            Scoreboard::sm_gpsHasFix;
 std::list< double > Scoreboard::sm_gpsLatitudes;
 double                Scoreboard::sm_gpsLatitudeAvg;
 std::list< double > Scoreboard::sm_gpsLongitudes;
 double                Scoreboard::sm_gpsLongitudeAvg;
-bool            Scoreboard::sm_isImuAlive;
-Poco::Timestamp Scoreboard::sm_imuLastUpdate;
-double          Scoreboard::sm_imuRoll;
-double          Scoreboard::sm_imuPitch;
-double          Scoreboard::sm_imuYaw;
-double          Scoreboard::sm_navTargetHeading = 100.0;
-int8_t          Scoreboard::sm_navMaximumSpeed = 30;
+
+// odr-motion
+bool Scoreboard::sm_MotionEmergency;
+bool Scoreboard::sm_isMotionAlive;
+
+// odr-sonar-front
+bool    Scoreboard::sm_SonarFrontEmergency;
+bool    Scoreboard::sm_isSonarFrontAlive;
+bool    Scoreboard::sm_isSonarFrontEnabled;
+uint8_t Scoreboard::sm_sonarFrontLeft;
+uint8_t Scoreboard::sm_sonarFrontCenterLeft;
+uint8_t Scoreboard::sm_sonarFrontCenterRight;
+uint8_t Scoreboard::sm_sonarFrontRight;
+
+// imu
+bool   Scoreboard::sm_isImuAlive;
+double Scoreboard::sm_imuRoll;
+double Scoreboard::sm_imuPitch;
+double Scoreboard::sm_imuYaw;
+
+// program
+bool    Scoreboard::sm_hasActiveProgram;
+uint8_t Scoreboard::sm_activeProgram;
+
+// navigation
+int8_t Scoreboard::sm_navMaximumSpeed;
+double Scoreboard::sm_navTargetHeading;
 
 // ----------------------------------------------------------------------------------------
 
-bool Scoreboard::isGpsSensorAlive()
+bool Scoreboard::isEmergencyActive()
+{
+    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
+    
+    sm_isEmergencyActive = sm_DisplayEmergency    | ! sm_isDisplayAlive
+                         | sm_GpsEmergency        | ! sm_isGpsAlive
+                         | sm_MotionEmergency     | ! sm_isMotionAlive
+                         | sm_SonarFrontEmergency | ! sm_isSonarFrontAlive
+                         ;
+
+    return sm_isEmergencyActive;
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool Scoreboard::isDisplayAlive()
+{
+    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
+    return sm_isDisplayAlive;
+}
+
+// ----------------------------------------------------------------------------------------
+
+bool Scoreboard::isGpsAlive()
 {
     Poco::RWLock::ScopedReadLock lock( sm_rwLock );
     return sm_isGpsAlive;
@@ -86,39 +129,22 @@
 
 // ----------------------------------------------------------------------------------------
 
-bool Scoreboard::isControllerAlive()
+bool Scoreboard::isMotionAlive()
 {
     Poco::RWLock::ScopedReadLock lock( sm_rwLock );
-    return true; // fake it
-    // return sm_isControllerAlive;
+    return sm_isMotionAlive;
 }
 
 // ----------------------------------------------------------------------------------------
 
-bool Scoreboard::isEStopActive()
-{
-    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
-    return sm_isEStopActive;
-}
-
-// ----------------------------------------------------------------------------------------
-
-bool Scoreboard::isButtonOneDown()
+bool Scoreboard::isSonarFrontAlive()
 {
     Poco::RWLock::ScopedReadLock lock( sm_rwLock );
-    return sm_isButtonOneDown;
+    return sm_isSonarFrontAlive;
 }
 
 // ----------------------------------------------------------------------------------------
 
-bool Scoreboard::isButtonTwoDown()
-{
-    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
-    return sm_isButtonTwoDown;
-}
-                 
-// ----------------------------------------------------------------------------------------
-
 void Scoreboard::sendManagerMessage( const char* msg )
 {
     // don't repeat the same message more than once per five seconds
@@ -156,14 +182,6 @@
 
 // ----------------------------------------------------------------------------------------
 
-bool Scoreboard::isSonarFrontAlive()
-{
-    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
-    return sm_isSonarFrontAlive;
-}
-
-// ----------------------------------------------------------------------------------------
-
 bool Scoreboard::isSonarFrontEnabled()
 {
     Poco::RWLock::ScopedReadLock lock( sm_rwLock );
@@ -204,14 +222,6 @@
 
 // ----------------------------------------------------------------------------------------
 
-bool Scoreboard::isGpsAlive()
-{
-    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
-    return sm_isGpsAlive;
-}
-
-// ----------------------------------------------------------------------------------------
-
 bool Scoreboard::gpsHasFix()
 {
     Poco::RWLock::ScopedReadLock lock( sm_rwLock );
@@ -268,24 +278,39 @@
 
 // ----------------------------------------------------------------------------------------
 
-void Scoreboard::imuUpdate( double roll, double pitch, double yaw )
+bool Scoreboard::isProgramRunning()
 {
-    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
-
-    sm_isImuAlive = true;
-    sm_imuLastUpdate.update();
-
-    sm_imuRoll  = roll;
-    sm_imuPitch = pitch;
-    sm_imuYaw   = yaw;
+    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
+    return sm_hasActiveProgram;
 }
 
 // ----------------------------------------------------------------------------------------
 
-void Scoreboard::imuIsOffline()
+uint8_t Scoreboard::activeProgram()
+{
+    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
+    if ( ! sm_hasActiveProgram )
+    {
+        return 0;
+    }
+    return sm_activeProgram;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void Scoreboard::setActiveProgram( uint8_t program )
 {
     Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
-    sm_isImuAlive = false;
+    sm_hasActiveProgram = true;
+    sm_activeProgram    = program;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void Scoreboard::stopActiveProgram()
+{
+    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
+    sm_hasActiveProgram = false;
 }
 
 // ----------------------------------------------------------------------------------------
@@ -328,11 +353,11 @@
       m_quitEvent( false /* not auto-reset */ ),
       m_timerLogStatus(),
       m_timerSendManagerUpdate(),
-      m_timerGpsUpdate(),
-      m_timerMotionUpdate(),
-      m_timerControllerUpdate(),
-      m_timerSonarFrontUpdate(),
-      m_timerSendImuDataUpdate()
+      m_timerDisplayTimeout(),
+      m_timerGpsTimeout(),
+      m_timerImuTimeout(),
+      m_timerMotionTimeout(),
+      m_timerSonarFrontTimeout()
 {
     m_timerLogStatus.setPeriodicInterval( 5000 );
     m_timerLogStatus.start(
@@ -342,29 +367,30 @@
     m_timerSendManagerUpdate.start(
             Poco::TimerCallback< Scoreboard >( *this, &Scoreboard::sendManagerUpdate ) );
 
-    m_timerGpsUpdate.setPeriodicInterval( 5000 );
-    m_timerGpsUpdate.start(
+    m_timerDisplayTimeout.setPeriodicInterval( 5000 );
+    m_timerDisplayTimeout.start(
             Poco::TimerCallback< Scoreboard >(
-                    *this, &Scoreboard::timeoutGpsSensorUpdate ) );
+                    *this, &Scoreboard::timeoutDisplayNode ) );
 
-    m_timerMotionUpdate.setPeriodicInterval( 5000 );
-    m_timerMotionUpdate.start(
+    m_timerGpsTimeout.setPeriodicInterval( 5000 );
+    m_timerGpsTimeout.start(
             Poco::TimerCallback< Scoreboard >(
-                    *this, &Scoreboard::timeoutMotionUpdate ) );
+                    *this, &Scoreboard::timeoutGpsSensorNode ) );
 
-    m_timerControllerUpdate.setPeriodicInterval( 5000 );
-    m_timerControllerUpdate.start(
+    m_timerImuTimeout.setPeriodicInterval( 5000 );
+    m_timerImuTimeout.start(
             Poco::TimerCallback< Scoreboard >(
-                    *this, &Scoreboard::timeoutControllerUpdate ) );
+                    *this, &Scoreboard::timeoutImuNode ) );
 
-    m_timerSonarFrontUpdate.setPeriodicInterval( 5000 );
-    m_timerSonarFrontUpdate.start(
+    m_timerMotionTimeout.setPeriodicInterval( 5000 );
+    m_timerMotionTimeout.start(
             Poco::TimerCallback< Scoreboard >(
-                    *this, &Scoreboard::timeoutSonarFrontUpdate ) );
+                    *this, &Scoreboard::timeoutMotionNode ) );
 
-    m_timerSendImuDataUpdate.setPeriodicInterval( 750 );
-    m_timerSendImuDataUpdate.start(
-            Poco::TimerCallback< Scoreboard >( *this, &Scoreboard::sendImuDataUpdate ) );
+    m_timerSonarFrontTimeout.setPeriodicInterval( 5000 );
+    m_timerSonarFrontTimeout.start(
+            Poco::TimerCallback< Scoreboard >(
+                    *this, &Scoreboard::timeoutSonarFrontNode ) );
 }
 
 // ----------------------------------------------------------------------------------------
@@ -429,13 +455,9 @@
 
     CANMessage::QueueToSend( new CANMessage( updatemsgid ) );
 
-    // lock the scoreboard data for read-only access
-
-    Poco::RWLock::ScopedReadLock lock( sm_rwLock );
-
     // update the emergency / all-clear status
 
-    if ( sm_isEmergencyActive )
+    if ( Scoreboard::isEmergencyActive() )
     {
         uint32_t msgid = can_build_message_id(
                     can_node_odr_manager, can_node_broadcast, can_dataid_emergency );
@@ -467,7 +489,15 @@
 
 // ----------------------------------------------------------------------------------------
 
-void Scoreboard::timeoutGpsSensorUpdate( Poco::Timer& timer )
+void Scoreboard::timeoutDisplayNode( Poco::Timer& timer )
+{
+    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
+    sm_isDisplayAlive = false;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void Scoreboard::timeoutGpsSensorNode( Poco::Timer& timer )
 {
     Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
     sm_isGpsAlive = false;
@@ -475,7 +505,15 @@
 
 // ----------------------------------------------------------------------------------------
 
-void Scoreboard::timeoutMotionUpdate( Poco::Timer& timer )
+void Scoreboard::timeoutImuNode( Poco::Timer& timer )
+{
+    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
+    sm_isImuAlive = false;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void Scoreboard::timeoutMotionNode( Poco::Timer& timer )
 {
     Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
     sm_isMotionAlive = false;
@@ -483,15 +521,7 @@
 
 // ----------------------------------------------------------------------------------------
 
-void Scoreboard::timeoutControllerUpdate( Poco::Timer& timer )
-{
-    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
-    sm_isControllerAlive = false;
-}
-
-// ----------------------------------------------------------------------------------------
-
-void Scoreboard::timeoutSonarFrontUpdate( Poco::Timer& timer )
+void Scoreboard::timeoutSonarFrontNode( Poco::Timer& timer )
 {
     Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
     sm_isSonarFrontAlive = false;
@@ -499,58 +529,6 @@
 
 // ----------------------------------------------------------------------------------------
 
-void Scoreboard::sendImuDataUpdate( Poco::Timer& timer )
-{
-    // check if the IMU is actually offline; more than 2 seconds silence means yes
-
-    static const Poco::Timestamp::TimeDiff TwoSeconds = Poco::Timestamp::resolution() * 2;
-
-    if ( sm_imuLastUpdate.elapsed() > TwoSeconds )
-    {
-        sm_isImuAlive = false;
-        return;
-    }
-
-    // YAW
-
-    uint32_t imudatamsgid = can_build_message_id(
-                    can_node_odr_manager, can_node_broadcast, can_dataid_imu_yaw );
-
-    can_data_imu_data data;
-    data.data = static_cast< int32_t >( Scoreboard::imuYaw() * can_data_imu_multiplier );
-
-    CANMessage::QueueToSend(
-            new CANMessage( imudatamsgid,
-                            reinterpret_cast< uint8_t* >( &data ),
-                            sizeof( data ) ) );
-
-    // PITCH
-
-    imudatamsgid = can_build_message_id(
-                    can_node_odr_manager, can_node_broadcast, can_dataid_imu_pitch );
-
-    data.data = static_cast< int32_t >( Scoreboard::imuPitch() * can_data_imu_multiplier );
-
-    CANMessage::QueueToSend(
-            new CANMessage( imudatamsgid,
-                            reinterpret_cast< uint8_t* >( &data ),
-                            sizeof( data ) ) );
-
-    // ROLL
-
-    imudatamsgid = can_build_message_id(
-                    can_node_odr_manager, can_node_broadcast, can_dataid_imu_roll );
-
-    data.data = static_cast< int32_t >( Scoreboard::imuRoll() * can_data_imu_multiplier );
-
-    CANMessage::QueueToSend(
-            new CANMessage( imudatamsgid,
-                            reinterpret_cast< uint8_t* >( &data ),
-                            sizeof( data ) ) );
-}
-
-// ----------------------------------------------------------------------------------------
-
 void Scoreboard::recvEmergency( uint8_t srcNode )
 {
     Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
@@ -559,9 +537,21 @@
 
     switch ( srcNode )
     {
+        case can_node_odr_display:
+            sm_DisplayEmergency = true;
+            break;
+
+        case can_node_sensor_gps:
+            sm_GpsEmergency = true;
+            break;
+
         case can_node_odr_motion:
             sm_MotionEmergency = true;
             break;
+
+        case can_node_odr_sonar_front:
+            sm_SonarFrontEmergency = true;
+            break;
     }
 }
 
@@ -573,15 +563,21 @@
 
     switch ( srcNode )
     {
+        case can_node_odr_display:
+            sm_DisplayEmergency = false;
+            break;
+
+        case can_node_sensor_gps:
+            sm_GpsEmergency = false;
+            break;
+
         case can_node_odr_motion:
             sm_MotionEmergency = false;
             break;
-    }
 
-    if ( sm_isEmergencyActive )
-    {
-        sm_isEmergencyActive = sm_MotionEmergency; // | sm_xxxEmergency
-
+        case can_node_odr_sonar_front:
+            sm_SonarFrontEmergency = false;
+            break;
     }
 }
 
@@ -593,58 +589,30 @@
 
     switch ( srcNode )
     {
-        case can_node_odr_controller:
-            m_timerControllerUpdate.restart();
-            sm_isControllerAlive = true;
-            break;
-
-        case can_node_odr_motion:
-            m_timerMotionUpdate.restart();
-            sm_isMotionAlive = true;
+        case can_node_odr_display:
+            m_timerDisplayTimeout.restart();
+            sm_isDisplayAlive = true;
             break;
 
         case can_node_sensor_gps:
-            m_timerGpsUpdate.restart();
+            m_timerGpsTimeout.restart();
             sm_isGpsAlive = true;
             break;
+
+        case can_node_odr_motion:
+            m_timerMotionTimeout.restart();
+            sm_isMotionAlive = true;
+            break;
     }
 }
 
 // ----------------------------------------------------------------------------------------
 
-void Scoreboard::recvControllerUpdate( CANMessage* msg )
-{
-    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
-
-    m_timerControllerUpdate.restart();
-    sm_isControllerAlive = true;
-
-    can_data_odrctl_update info;
-    msg->msgData( reinterpret_cast< uint8_t* >( &info ), sizeof( info ) );
-
-    sm_isEStopActive = info.estop;
-}
-
-// ----------------------------------------------------------------------------------------
-
-void Scoreboard::recvButtonUpdate( CANMessage* msg )
-{
-    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
-
-    can_data_odrctl_button info;
-    msg->msgData( reinterpret_cast< uint8_t* >( &info ), sizeof( info ) );
-
-    sm_isButtonOneDown = info.button_1;
-    sm_isButtonTwoDown = info.button_2;
-}
-
-// ----------------------------------------------------------------------------------------
-
 void Scoreboard::recvSonarFrontStatus( bool enabled )
 {
     Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
 
-    m_timerSonarFrontUpdate.restart();
+    m_timerSonarFrontTimeout.restart();
     sm_isSonarFrontAlive = true;
 
     sm_isSonarFrontEnabled = enabled;
@@ -740,6 +708,60 @@
 
 // ----------------------------------------------------------------------------------------
 
+void Scoreboard::recvImuRoll( CANMessage* msg )
+{
+    can_data_imu_data info;
+    msg->msgData( reinterpret_cast< uint8_t* >( &info ), sizeof( info ) );
+
+    double roll = static_cast< double >( info.data )
+                / static_cast< double >( can_data_imu_multiplier );
+
+    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
+
+    sm_isImuAlive = true;
+    m_timerImuTimeout.restart();
+    
+    sm_imuRoll = roll;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void Scoreboard::recvImuPitch( CANMessage* msg )
+{
+    can_data_imu_data info;
+    msg->msgData( reinterpret_cast< uint8_t* >( &info ), sizeof( info ) );
+
+    double pitch = static_cast< double >( info.data )
+                 / static_cast< double >( can_data_imu_multiplier );
+
+    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
+
+    sm_isImuAlive = true;
+    m_timerImuTimeout.restart();
+    
+    sm_imuPitch = pitch;
+}
+
+// ----------------------------------------------------------------------------------------
+
+void Scoreboard::recvImuYaw( CANMessage* msg )
+{
+    can_data_imu_data info;
+    msg->msgData( reinterpret_cast< uint8_t* >( &info ), sizeof( info ) );
+
+    double yaw = static_cast< double >( info.data )
+               / static_cast< double >( can_data_imu_multiplier );
+
+    Poco::RWLock::ScopedWriteLock lock( sm_rwLock );
+
+    sm_isImuAlive = true;
+    m_timerImuTimeout.restart();
+    
+    sm_imuYaw = yaw;
+}
+
+// ----------------------------------------------------------------------------------------
+
 void Scoreboard::run()
 {
     Poco::Logger& log = Poco::Logger::get( m_loggerName );
@@ -786,14 +808,6 @@
                         recvHeartbeat( srcNode );
                         break;
 
-                    case can_dataid_odrctl_update:
-                        recvControllerUpdate( msg );
-                        break;
-
-                    case can_dataid_odrctl_button:
-                        recvButtonUpdate( msg );
-                        break;
-
                     case can_dataid_sonar_front_state_enabled:
                         recvSonarFrontStatus( true /* enabled */ );
                         break;
@@ -818,6 +832,18 @@
                     case can_dataid_gps_longitude:
                         recvGpsLongitude( msg );
                         break;
+
+                    case can_dataid_imu_roll:
+                        recvImuRoll( msg );
+                        break;
+
+                    case can_dataid_imu_pitch:
+                        recvImuPitch( msg );
+                        break;
+
+                    case can_dataid_imu_yaw:
+                        recvImuYaw( msg );
+                        break;
                 }
             }
         }
--- a/main/robots/odr/Scoreboard.h	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/Scoreboard.h	Sat Jul 12 17:21:10 2014 -0700
@@ -7,7 +7,7 @@
 // 
 //  Global thread-safe state derived from incoming CAN messages and other internal data.
 //
-//  Copyright (c) 2010-2013 Bob Cook
+//  Copyright (c) 2010-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
@@ -33,6 +33,7 @@
 #define BCDRL_ROBOTS_ODR_SCOREBOARD_H
 
 #include <stdint.h>
+#include <queue>
 #include <list>
 
 #include <Poco/Event.h>
@@ -48,27 +49,13 @@
 class Scoreboard : public Poco::Runnable
 {
     public:
-        // odr-gps
-        static bool isGpsSensorAlive();
-
-        // odr-motion
-        static bool isMotionAlive();
+        // overall emergency state
+        static bool isEmergencyActive();
 
-        // odr-controller
-        static bool isControllerAlive();
-        static bool isEStopActive();
-        static bool isButtonOneDown();
-        static bool isButtonTwoDown();
+        // odr-display
+        static bool isDisplayAlive();
         static void sendManagerMessage( const char* msg );
 
-        // odr-sonar-front
-        static bool    isSonarFrontAlive();
-        static bool    isSonarFrontEnabled();
-        static uint8_t sonarFrontLeft();
-        static uint8_t sonarFrontCenterLeft();
-        static uint8_t sonarFrontCenterRight();
-        static uint8_t sonarFrontRight();
-
         // odr-gps
         static bool   isGpsAlive();
         static bool   gpsHasFix();
@@ -80,8 +67,23 @@
         static double imuRoll();
         static double imuPitch();
         static double imuYaw();
-        static void   imuUpdate( double roll, double pitch, double yaw );
-        static void   imuIsOffline();
+
+        // odr-motion
+        static bool isMotionAlive();
+
+        // odr-sonar-front
+        static bool    isSonarFrontAlive();
+        static bool    isSonarFrontEnabled();
+        static uint8_t sonarFrontLeft();
+        static uint8_t sonarFrontCenterLeft();
+        static uint8_t sonarFrontCenterRight();
+        static uint8_t sonarFrontRight();
+
+        // programs
+        static bool    isProgramRunning();
+        static uint8_t activeProgram();
+        static void    setActiveProgram( uint8_t program );
+        static void    stopActiveProgram();
 
         // navigation
         static int8_t navMaximumSpeed();
@@ -96,65 +98,87 @@
         void timeToQuit();
 
     private:
-        static Poco::RWLock    sm_rwLock;
-        static bool            sm_isEmergencyActive;
-        static bool            sm_MotionEmergency;
-        static bool            sm_isMotionAlive;
-        static bool            sm_isControllerAlive;
-        static bool            sm_isEStopActive;
-        static bool            sm_isButtonOneDown;
-        static bool            sm_isButtonTwoDown;
+        // scoreboard lock
+        static Poco::RWLock sm_rwLock;
+
+        // message broadcast
         static std::string     sm_msgLastText;
         static Poco::Timestamp sm_msgLastUpdate;
-        static bool            sm_isSonarFrontAlive;
-        static bool            sm_isSonarFrontEnabled;
-        static uint8_t         sm_sonarFrontLeft;
-        static uint8_t         sm_sonarFrontCenterLeft;
-        static uint8_t         sm_sonarFrontCenterRight;
-        static uint8_t         sm_sonarFrontRight;
+
+        // overall emergency state
+        static bool sm_isEmergencyActive;
+
+        // odr-display
+        static bool sm_DisplayEmergency;
+        static bool sm_isDisplayAlive;
+
+        // odr-gps
+        static bool            sm_GpsEmergency;
         static bool            sm_isGpsAlive;
         static bool            sm_gpsHasFix;
         static std::list< double > sm_gpsLatitudes;
         static double                sm_gpsLatitudeAvg;
         static std::list< double > sm_gpsLongitudes;
         static double                sm_gpsLongitudeAvg;
+
+        // odr-motion
+        static bool sm_MotionEmergency;
+        static bool sm_isMotionAlive;
+
+        // odr-sonar-front
+        static bool    sm_SonarFrontEmergency;
+        static bool    sm_isSonarFrontAlive;
+        static bool    sm_isSonarFrontEnabled;
+        static uint8_t sm_sonarFrontLeft;
+        static uint8_t sm_sonarFrontCenterLeft;
+        static uint8_t sm_sonarFrontCenterRight;
+        static uint8_t sm_sonarFrontRight;
+
+        // imu
         static bool            sm_isImuAlive;
         static Poco::Timestamp sm_imuLastUpdate;
         static double          sm_imuRoll;
         static double          sm_imuPitch;
         static double          sm_imuYaw;
-        static int8_t          sm_navMaximumSpeed;
-        static double          sm_navTargetHeading;
+
+        // program
+        static bool    sm_hasActiveProgram;
+        static uint8_t sm_activeProgram;
+
+        // navigation
+        static int8_t  sm_navMaximumSpeed;
+        static double  sm_navTargetHeading;
 
     private:
         std::string m_loggerName;
         Poco::Event m_quitEvent;
         Poco::Timer m_timerLogStatus;
         Poco::Timer m_timerSendManagerUpdate;
-        Poco::Timer m_timerGpsUpdate;
-        Poco::Timer m_timerMotionUpdate;
-        Poco::Timer m_timerControllerUpdate;
-        Poco::Timer m_timerSonarFrontUpdate;
-        Poco::Timer m_timerSendImuDataUpdate;
+        Poco::Timer m_timerDisplayTimeout;
+        Poco::Timer m_timerGpsTimeout;
+        Poco::Timer m_timerImuTimeout;
+        Poco::Timer m_timerMotionTimeout;
+        Poco::Timer m_timerSonarFrontTimeout;
 
     private:
         void logSystemStatus( Poco::Timer& timer );
         void sendManagerUpdate( Poco::Timer& timer );
-        void timeoutGpsSensorUpdate( Poco::Timer& timer );
-        void timeoutMotionUpdate( Poco::Timer& timer );
-        void timeoutControllerUpdate( Poco::Timer& timer );
-        void timeoutSonarFrontUpdate( Poco::Timer& timer );
-        void sendImuDataUpdate( Poco::Timer& timer );
+        void timeoutDisplayNode( Poco::Timer& timer );
+        void timeoutGpsSensorNode( Poco::Timer& timer );
+        void timeoutMotionNode( Poco::Timer& timer );
+        void timeoutSonarFrontNode( Poco::Timer& timer );
+        void timeoutImuNode( Poco::Timer& timer );
         void recvEmergency( uint8_t srcNode );
         void recvAllClear( uint8_t srcNode );
         void recvHeartbeat( uint8_t srcNode );
-        void recvControllerUpdate( CANMessage* msg );
-        void recvButtonUpdate( CANMessage* msg );
         void recvSonarFrontStatus( bool enabled );
         void recvSonarFrontValues( CANMessage* msg );
         void recvGpsFixInfo( CANMessage* msg );
         void recvGpsLatitude( CANMessage* msg );
         void recvGpsLongitude( CANMessage* msg );
+        void recvImuRoll( CANMessage* msg );
+        void recvImuPitch( CANMessage* msg );
+        void recvImuYaw( CANMessage* msg );
 };
 
 // ----------------------------------------------------------------------------------------
--- a/main/robots/odr/StartupTask.cpp	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/StartupTask.cpp	Sat Jul 12 17:21:10 2014 -0700
@@ -5,9 +5,9 @@
 //  Bob Cook Development, Robotics Library
 //  http://www.bobcookdev.com/rl/
 //
-//  Subsumption task to enact safety measures under certain unsafe conditions.
+//  Subsumption task to hold operations until a specific program is selected.
 //
-//  Copyright (c) 2011 Bob Cook
+//  Copyright (c) 2011-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
@@ -33,7 +33,6 @@
 
 #include "MotorsAndServos.h"
 #include "Scoreboard.h"
-#include "Sonar.h"
 
 // ----------------------------------------------------------------------------------------
 
@@ -43,10 +42,7 @@
 
 StartupTask::StartupTask( const std::string& loggerName )
     : TaskObject( loggerName ),
-      m_lastControllerStatus( Scoreboard::isControllerAlive() ),
-      m_lastMessageTime(),
-      m_gotButtonPress( false ),
-      m_gotButtonRelease( false ),
+      m_haveStartedProgram( Scoreboard::isProgramRunning() ),
       m_startRunTime()
 {
 }
@@ -61,56 +57,30 @@
 
 void StartupTask::update()
 {
-    // we need the odr-controller to be running
-    
-    if ( ! Scoreboard::isControllerAlive() )
+    // have an active program yet? if not, wait in this state until we do
+
+    if ( ! Scoreboard::isProgramRunning() )
     {
-        if ( m_lastControllerStatus )
-        {
-            log().information( "StartupTask: controller is not alive" );
-            m_lastControllerStatus = false;
-        }
-
-        return; // nothing else can be done
-    }
-    else if ( ! m_lastControllerStatus )
-    {
-        log().information( "StartupTask: controller is alive" );
-        m_lastControllerStatus = true;
+        m_haveStartedProgram = false;
+        return;
     }
 
-    // check for the button press then release event
+    // program running; this might be the start of that event
 
-    if ( ! m_gotButtonPress )
+    if ( ! m_haveStartedProgram )
     {
-        m_gotButtonPress = Scoreboard::isButtonOneDown();
-        if ( m_gotButtonPress ) log().information( "StartupTask: got button press" );
+        m_haveStartedProgram = true;
+        log().information( "StartupTask: starting program" );
+        m_startRunTime.update();
     }
 
-    if ( m_gotButtonPress && ! m_gotButtonRelease )
-    {
-        m_gotButtonRelease = ! Scoreboard::isButtonOneDown();
-        if ( m_gotButtonRelease ) log().information( "StartupTask: got button release" );
-
-        if ( m_gotButtonRelease )
-        {
-            log().information( "StartupTask: starting run" );
-            m_startRunTime.update();
+    // the program is running, check for timeout
 
-//            Scoreboard::navSetTargetHeading( Scoreboard::imuYaw() );
-        }
-    }
-
-    // if we are running, check for timeout
-
-    if ( m_gotButtonPress && m_gotButtonRelease )
+    if ( m_startRunTime.isElapsed( TimeToRun ) )
     {
-        if ( m_startRunTime.isElapsed( TimeToRun ) )
-        {
-            log().information( "StartupTask: ending run" );
-            m_gotButtonPress   = false;
-            m_gotButtonRelease = false;
-        }
+        Scoreboard::stopActiveProgram();
+        m_haveStartedProgram = false;
+        log().information( "StartupTask: ending run" );
     }
 }
 
@@ -118,14 +88,9 @@
 
 bool StartupTask::wantsControl()
 {
-    if ( ! m_lastControllerStatus )
+    if ( ! m_haveStartedProgram )
     {
-        return true; // take control if the odr-controller is not yet alive
-    }
-
-    if ( ! m_gotButtonPress || ! m_gotButtonRelease )
-    {
-        return true; // take control if the button hasn't been pressed/released yet
+        return true; // take control if no program started yet
     }
 
     return false; // otherwise not for us
@@ -135,19 +100,9 @@
 
 void StartupTask::takeControl()
 {
-    if ( ! m_lastControllerStatus )
-    {
-        Scoreboard::sendManagerMessage( "Waiting" );
-        return; // nothing can be done until the odr-controller is up
-    }
+    // display a helpful message
 
-    // send instructions for display on the odr-controller
-
-    if ( m_lastMessageTime.isElapsed( TaskObject::TwoSeconds ) )
-    {
-        Scoreboard::sendManagerMessage( "Press A" );
-        m_lastMessageTime.update();
-    }
+    Scoreboard::sendManagerMessage( "Waiting" );
 
     // make sure we aren't moving
 
--- a/main/robots/odr/StartupTask.h	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/StartupTask.h	Sat Jul 12 17:21:10 2014 -0700
@@ -5,9 +5,9 @@
 //  Bob Cook Development, Robotics Library
 //  http://www.bobcookdev.com/rl/
 //
-//  Subsumption task to enact safety measures under certain unsafe conditions.
+//  Subsumption task to hold operations until a specific program is selected.
 //
-//  Copyright (c) 2011 Bob Cook
+//  Copyright (c) 2011-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
@@ -46,10 +46,7 @@
         virtual void takeControl();
 
     private:
-        bool            m_lastControllerStatus;
-        Poco::Timestamp m_lastMessageTime;
-        bool            m_gotButtonPress;
-        bool            m_gotButtonRelease;
+        bool            m_haveStartedProgram;
         Poco::Timestamp m_startRunTime;
 };
 
--- a/main/robots/odr/jamfile	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/jamfile	Sat Jul 12 17:21:10 2014 -0700
@@ -34,10 +34,10 @@
 
 # -----------------------------------------------------------------------------------------
 
-COMMON_SOURCES = 
+ODRAPP_SOURCES = 
     main.cpp
     ODRApp.cpp
-    ImuReader.cpp MotorsAndServos.cpp Scoreboard.cpp Sonar.cpp
+    MotorsAndServos.cpp Scoreboard.cpp Sonar.cpp
     TaskObject.cpp
     AvoidBySonarTask.cpp CruisingTask.cpp NavigateTask.cpp SafetyTask.cpp StartupTask.cpp
     SquareCourseTask.cpp TrackWaypointsTask.cpp
@@ -45,13 +45,22 @@
     packages.linux.can.pkg
     ;
 
+IMUAPP_SOURCES =
+    ImuApp.cpp ImuReader.cpp
+    packages.common.can.pkg
+    packages.linux.can.pkg
+    ;
+
 POCO_SHARED = PocoFoundation.so PocoNet.so PocoUtil.so PocoXML.so ;
 POCO_STATIC = PocoFoundation.a  PocoNet.a  PocoUtil.a  PocoXML.a ;
 
 # -----------------------------------------------------------------------------------------
 
-ubuntu_executable odr_ubuntu : $(COMMON_SOURCES) $(POCO_SHARED) ;
-overo_executable  odr        : $(COMMON_SOURCES) $(POCO_STATIC) ;
+ubuntu_executable odr_ubuntu : $(ODRAPP_SOURCES) $(POCO_SHARED) ;
+overo_executable  odr        : $(ODRAPP_SOURCES) $(POCO_STATIC) ;
+
+ubuntu_executable imu_ubuntu : $(IMUAPP_SOURCES) $(POCO_SHARED) ;
+overo_executable  imu        : $(IMUAPP_SOURCES) $(POCO_STATIC) ;
 
 # -----------------------------------------------------------------------------------------
 
--- a/main/robots/odr/odr_ubuntu.xml	Sat Jul 12 17:18:55 2014 -0700
+++ b/main/robots/odr/odr_ubuntu.xml	Sat Jul 12 17:21:10 2014 -0700
@@ -32,8 +32,5 @@
         <logIncoming>no</logIncoming>
         <logOutgoing>no</logOutgoing>
     </can>
-    <imu>
-        <serialPortPath>/dev/ttyUSB0</serialPortPath>
-    </imu>
 </config>