/*----------------------------------------------------------------------------*/ | |
/* Copyright (c) FIRST 2008. All Rights Reserved. */ | |
/* Open Source Software - may be modified and shared by FRC teams. The code */ | |
/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */ | |
/*----------------------------------------------------------------------------*/ | |
#include "DriverStation.h" | |
#include "AnalogChannel.h" | |
#include "Synchronized.h" | |
#include "Timer.h" | |
#include "NetworkCommunication/FRCComm.h" | |
#include "NetworkCommunication/UsageReporting.h" | |
#include "MotorSafetyHelper.h" | |
#include "Utility.h" | |
#include "WPIErrors.h" | |
#include <strLib.h> | |
const UINT32 DriverStation::kBatteryModuleNumber; | |
const UINT32 DriverStation::kBatteryChannel; | |
const UINT32 DriverStation::kJoystickPorts; | |
const UINT32 DriverStation::kJoystickAxes; | |
const float DriverStation::kUpdatePeriod; | |
DriverStation* DriverStation::m_instance = NULL; | |
UINT8 DriverStation::m_updateNumber = 0; | |
/** | |
* DriverStation contructor. | |
* | |
* This is only called once the first time GetInstance() is called | |
*/ | |
DriverStation::DriverStation() | |
: m_controlData (NULL) | |
, m_digitalOut (0) | |
, m_batteryChannel (NULL) | |
, m_statusDataSemaphore (semMCreate(SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE)) | |
, m_task ("DriverStation", (FUNCPTR)DriverStation::InitTask) | |
, m_dashboardHigh(m_statusDataSemaphore) | |
, m_dashboardLow(m_statusDataSemaphore) | |
, m_dashboardInUseHigh(&m_dashboardHigh) | |
, m_dashboardInUseLow(&m_dashboardLow) | |
, m_newControlData(0) | |
, m_packetDataAvailableSem (0) | |
, m_enhancedIO() | |
, m_waitForDataSem(0) | |
, m_approxMatchTimeOffset(-1.0) | |
, m_userInDisabled(false) | |
, m_userInAutonomous(false) | |
, m_userInTeleop(false) | |
, m_userInTest(false) | |
{ | |
// Create a new semaphore | |
m_packetDataAvailableSem = semBCreate (SEM_Q_PRIORITY, SEM_EMPTY); | |
m_newControlData = semBCreate (SEM_Q_FIFO, SEM_EMPTY); | |
// Register that semaphore with the network communications task. | |
// It will signal when new packet data is available. | |
setNewDataSem(m_packetDataAvailableSem); | |
m_waitForDataSem = semBCreate (SEM_Q_PRIORITY, SEM_EMPTY); | |
m_controlData = new FRCCommonControlData; | |
// initialize packet number and control words to zero; | |
m_controlData->packetIndex = 0; | |
m_controlData->control = 0; | |
// set all joystick axis values to neutral; buttons to OFF | |
m_controlData->stick0Axis1 = m_controlData->stick0Axis2 = m_controlData->stick0Axis3 = 0; | |
m_controlData->stick1Axis1 = m_controlData->stick1Axis2 = m_controlData->stick1Axis3 = 0; | |
m_controlData->stick2Axis1 = m_controlData->stick2Axis2 = m_controlData->stick2Axis3 = 0; | |
m_controlData->stick3Axis1 = m_controlData->stick3Axis2 = m_controlData->stick3Axis3 = 0; | |
m_controlData->stick0Axis4 = m_controlData->stick0Axis5 = m_controlData->stick0Axis6 = 0; | |
m_controlData->stick1Axis4 = m_controlData->stick1Axis5 = m_controlData->stick1Axis6 = 0; | |
m_controlData->stick2Axis4 = m_controlData->stick2Axis5 = m_controlData->stick2Axis6 = 0; | |
m_controlData->stick3Axis4 = m_controlData->stick3Axis5 = m_controlData->stick3Axis6 = 0; | |
m_controlData->stick0Buttons = 0; | |
m_controlData->stick1Buttons = 0; | |
m_controlData->stick2Buttons = 0; | |
m_controlData->stick3Buttons = 0; | |
// initialize the analog and digital data. | |
m_controlData->analog1 = 0; | |
m_controlData->analog2 = 0; | |
m_controlData->analog3 = 0; | |
m_controlData->analog4 = 0; | |
m_controlData->dsDigitalIn = 0; | |
m_batteryChannel = new AnalogChannel(kBatteryModuleNumber, kBatteryChannel); | |
AddToSingletonList(); | |
if (!m_task.Start((INT32)this)) | |
{ | |
wpi_setWPIError(DriverStationTaskError); | |
} | |
} | |
DriverStation::~DriverStation() | |
{ | |
m_task.Stop(); | |
semDelete(m_statusDataSemaphore); | |
delete m_batteryChannel; | |
delete m_controlData; | |
m_instance = NULL; | |
semDelete(m_waitForDataSem); | |
// Unregister our semaphore. | |
setNewDataSem(0); | |
semDelete(m_packetDataAvailableSem); | |
} | |
void DriverStation::InitTask(DriverStation *ds) | |
{ | |
ds->Run(); | |
} | |
void DriverStation::Run() | |
{ | |
int period = 0; | |
while (true) | |
{ | |
semTake(m_packetDataAvailableSem, WAIT_FOREVER); | |
SetData(); | |
m_enhancedIO.UpdateData(); | |
GetData(); | |
semFlush(m_waitForDataSem); | |
if (++period >= 4) | |
{ | |
MotorSafetyHelper::CheckMotors(); | |
period = 0; | |
} | |
if (m_userInDisabled) | |
FRC_NetworkCommunication_observeUserProgramDisabled(); | |
if (m_userInAutonomous) | |
FRC_NetworkCommunication_observeUserProgramAutonomous(); | |
if (m_userInTeleop) | |
FRC_NetworkCommunication_observeUserProgramTeleop(); | |
if (m_userInTest) | |
FRC_NetworkCommunication_observeUserProgramTest(); | |
} | |
} | |
/** | |
* Return a pointer to the singleton DriverStation. | |
*/ | |
DriverStation* DriverStation::GetInstance() | |
{ | |
if (m_instance == NULL) | |
{ | |
m_instance = new DriverStation(); | |
} | |
return m_instance; | |
} | |
/** | |
* Copy data from the DS task for the user. | |
* If no new data exists, it will just be returned, otherwise | |
* the data will be copied from the DS polling loop. | |
*/ | |
void DriverStation::GetData() | |
{ | |
static bool lastEnabled = false; | |
getCommonControlData(m_controlData, WAIT_FOREVER); | |
if (!lastEnabled && IsEnabled()) | |
{ | |
// If starting teleop, assume that autonomous just took up 15 seconds | |
if (IsAutonomous()) | |
m_approxMatchTimeOffset = Timer::GetFPGATimestamp(); | |
else | |
m_approxMatchTimeOffset = Timer::GetFPGATimestamp() - 15.0; | |
} | |
else if (lastEnabled && !IsEnabled()) | |
{ | |
m_approxMatchTimeOffset = -1.0; | |
} | |
lastEnabled = IsEnabled(); | |
semGive(m_newControlData); | |
} | |
/** | |
* Copy status data from the DS task for the user. | |
*/ | |
void DriverStation::SetData() | |
{ | |
char *userStatusDataHigh; | |
INT32 userStatusDataHighSize; | |
char *userStatusDataLow; | |
INT32 userStatusDataLowSize; | |
Synchronized sync(m_statusDataSemaphore); | |
m_dashboardInUseHigh->GetStatusBuffer(&userStatusDataHigh, &userStatusDataHighSize); | |
m_dashboardInUseLow->GetStatusBuffer(&userStatusDataLow, &userStatusDataLowSize); | |
setStatusData(GetBatteryVoltage(), m_digitalOut, m_updateNumber, | |
userStatusDataHigh, userStatusDataHighSize, userStatusDataLow, userStatusDataLowSize, WAIT_FOREVER); | |
m_dashboardInUseHigh->Flush(); | |
m_dashboardInUseLow->Flush(); | |
} | |
/** | |
* Read the battery voltage from the specified AnalogChannel. | |
* | |
* This accessor assumes that the battery voltage is being measured | |
* through the voltage divider on an analog breakout. | |
* | |
* @return The battery voltage. | |
*/ | |
float DriverStation::GetBatteryVoltage() | |
{ | |
if (m_batteryChannel == NULL) | |
wpi_setWPIError(NullParameter); | |
// The Analog bumper has a voltage divider on the battery source. | |
// Vbatt *--/\/\/\--* Vsample *--/\/\/\--* Gnd | |
// 680 Ohms 1000 Ohms | |
return m_batteryChannel->GetAverageVoltage() * (1680.0 / 1000.0); | |
} | |
/** | |
* Get the value of the axis on a joystick. | |
* This depends on the mapping of the joystick connected to the specified port. | |
* | |
* @param stick The joystick to read. | |
* @param axis The analog axis value to read from the joystick. | |
* @return The value of the axis on the joystick. | |
*/ | |
float DriverStation::GetStickAxis(UINT32 stick, UINT32 axis) | |
{ | |
if (axis < 1 || axis > kJoystickAxes) | |
{ | |
wpi_setWPIError(BadJoystickAxis); | |
return 0.0; | |
} | |
INT8 value; | |
switch (stick) | |
{ | |
case 1: | |
value = m_controlData->stick0Axes[axis-1]; | |
break; | |
case 2: | |
value = m_controlData->stick1Axes[axis-1]; | |
break; | |
case 3: | |
value = m_controlData->stick2Axes[axis-1]; | |
break; | |
case 4: | |
value = m_controlData->stick3Axes[axis-1]; | |
break; | |
default: | |
wpi_setWPIError(BadJoystickIndex); | |
return 0.0; | |
} | |
float result; | |
if (value < 0) | |
result = ((float) value) / 128.0; | |
else | |
result = ((float) value) / 127.0; | |
wpi_assert(result <= 1.0 && result >= -1.0); | |
if (result > 1.0) | |
result = 1.0; | |
else if (result < -1.0) | |
result = -1.0; | |
return result; | |
} | |
/** | |
* The state of the buttons on the joystick. | |
* 12 buttons (4 msb are unused) from the joystick. | |
* | |
* @param stick The joystick to read. | |
* @return The state of the buttons on the joystick. | |
*/ | |
short DriverStation::GetStickButtons(UINT32 stick) | |
{ | |
if (stick < 1 || stick > 4) | |
wpi_setWPIErrorWithContext(ParameterOutOfRange, "stick must be between 1 and 4"); | |
switch (stick) | |
{ | |
case 1: | |
return m_controlData->stick0Buttons; | |
case 2: | |
return m_controlData->stick1Buttons; | |
case 3: | |
return m_controlData->stick2Buttons; | |
case 4: | |
return m_controlData->stick3Buttons; | |
} | |
return 0; | |
} | |
// 5V divided by 10 bits | |
#define kDSAnalogInScaling ((float)(5.0 / 1023.0)) | |
/** | |
* Get an analog voltage from the Driver Station. | |
* The analog values are returned as voltage values for the Driver Station analog inputs. | |
* These inputs are typically used for advanced operator interfaces consisting of potentiometers | |
* or resistor networks representing values on a rotary switch. | |
* | |
* @param channel The analog input channel on the driver station to read from. Valid range is 1 - 4. | |
* @return The analog voltage on the input. | |
*/ | |
float DriverStation::GetAnalogIn(UINT32 channel) | |
{ | |
if (channel < 1 || channel > 4) | |
wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 4"); | |
static UINT8 reported_mask = 0; | |
if (!(reported_mask & (1 >> channel))) | |
{ | |
nUsageReporting::report(nUsageReporting::kResourceType_DriverStationCIO, channel, nUsageReporting::kDriverStationCIO_Analog); | |
reported_mask |= (1 >> channel); | |
} | |
switch (channel) | |
{ | |
case 1: | |
return kDSAnalogInScaling * m_controlData->analog1; | |
case 2: | |
return kDSAnalogInScaling * m_controlData->analog2; | |
case 3: | |
return kDSAnalogInScaling * m_controlData->analog3; | |
case 4: | |
return kDSAnalogInScaling * m_controlData->analog4; | |
} | |
return 0.0; | |
} | |
/** | |
* Get values from the digital inputs on the Driver Station. | |
* Return digital values from the Drivers Station. These values are typically used for buttons | |
* and switches on advanced operator interfaces. | |
* @param channel The digital input to get. Valid range is 1 - 8. | |
*/ | |
bool DriverStation::GetDigitalIn(UINT32 channel) | |
{ | |
if (channel < 1 || channel > 8) | |
wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 8"); | |
static UINT8 reported_mask = 0; | |
if (!(reported_mask & (1 >> channel))) | |
{ | |
nUsageReporting::report(nUsageReporting::kResourceType_DriverStationCIO, channel, nUsageReporting::kDriverStationCIO_DigitalIn); | |
reported_mask |= (1 >> channel); | |
} | |
return ((m_controlData->dsDigitalIn >> (channel-1)) & 0x1) ? true : false; | |
} | |
/** | |
* Set a value for the digital outputs on the Driver Station. | |
* | |
* Control digital outputs on the Drivers Station. These values are typically used for | |
* giving feedback on a custom operator station such as LEDs. | |
* | |
* @param channel The digital output to set. Valid range is 1 - 8. | |
* @param value The state to set the digital output. | |
*/ | |
void DriverStation::SetDigitalOut(UINT32 channel, bool value) | |
{ | |
if (channel < 1 || channel > 8) | |
wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 8"); | |
static UINT8 reported_mask = 0; | |
if (!(reported_mask & (1 >> channel))) | |
{ | |
nUsageReporting::report(nUsageReporting::kResourceType_DriverStationCIO, channel, nUsageReporting::kDriverStationCIO_DigitalOut); | |
reported_mask |= (1 >> channel); | |
} | |
m_digitalOut &= ~(0x1 << (channel-1)); | |
m_digitalOut |= ((UINT8)value << (channel-1)); | |
} | |
/** | |
* Get a value that was set for the digital outputs on the Driver Station. | |
* @param channel The digital ouput to monitor. Valid range is 1 through 8. | |
* @return A digital value being output on the Drivers Station. | |
*/ | |
bool DriverStation::GetDigitalOut(UINT32 channel) | |
{ | |
if (channel < 1 || channel > 8) | |
wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 8"); | |
return ((m_digitalOut >> (channel-1)) & 0x1) ? true : false;; | |
} | |
bool DriverStation::IsEnabled() | |
{ | |
return m_controlData->enabled; | |
} | |
bool DriverStation::IsDisabled() | |
{ | |
return !m_controlData->enabled; | |
} | |
bool DriverStation::IsAutonomous() | |
{ | |
return m_controlData->autonomous; | |
} | |
bool DriverStation::IsOperatorControl() | |
{ | |
return !(m_controlData->autonomous || m_controlData->test); | |
} | |
bool DriverStation::IsTest() | |
{ | |
return m_controlData->test; | |
} | |
/** | |
* Has a new control packet from the driver station arrived since the last time this function was called? | |
* Warning: If you call this function from more than one place at the same time, | |
* you will not get the get the intended behavior | |
* @return True if the control data has been updated since the last call. | |
*/ | |
bool DriverStation::IsNewControlData() | |
{ | |
return semTake(m_newControlData, NO_WAIT) == 0; | |
} | |
/** | |
* Is the driver station attached to a Field Management System? | |
* Note: This does not work with the Blue DS. | |
* @return True if the robot is competing on a field being controlled by a Field Management System | |
*/ | |
bool DriverStation::IsFMSAttached() | |
{ | |
return m_controlData->fmsAttached; | |
} | |
/** | |
* Return the DS packet number. | |
* The packet number is the index of this set of data returned by the driver station. | |
* Each time new data is received, the packet number (included with the sent data) is returned. | |
* @return The driver station packet number | |
*/ | |
UINT32 DriverStation::GetPacketNumber() | |
{ | |
return m_controlData->packetIndex; | |
} | |
/** | |
* Return the alliance that the driver station says it is on. | |
* This could return kRed or kBlue | |
* @return The Alliance enum | |
*/ | |
DriverStation::Alliance DriverStation::GetAlliance() | |
{ | |
if (m_controlData->dsID_Alliance == 'R') return kRed; | |
if (m_controlData->dsID_Alliance == 'B') return kBlue; | |
wpi_assert(false); | |
return kInvalid; | |
} | |
/** | |
* Return the driver station location on the field | |
* This could return 1, 2, or 3 | |
* @return The location of the driver station | |
*/ | |
UINT32 DriverStation::GetLocation() | |
{ | |
wpi_assert ((m_controlData->dsID_Position >= '1') && (m_controlData->dsID_Position <= '3')); | |
return m_controlData->dsID_Position - '0'; | |
} | |
/** | |
* Wait until a new packet comes from the driver station | |
* This blocks on a semaphore, so the waiting is efficient. | |
* This is a good way to delay processing until there is new driver station data to act on | |
*/ | |
void DriverStation::WaitForData() | |
{ | |
semTake(m_waitForDataSem, WAIT_FOREVER); | |
} | |
/** | |
* Return the approximate match time | |
* The FMS does not currently send the official match time to the robots | |
* This returns the time since the enable signal sent from the Driver Station | |
* At the beginning of autonomous, the time is reset to 0.0 seconds | |
* At the beginning of teleop, the time is reset to +15.0 seconds | |
* If the robot is disabled, this returns 0.0 seconds | |
* Warning: This is not an official time (so it cannot be used to argue with referees) | |
* @return Match time in seconds since the beginning of autonomous | |
*/ | |
double DriverStation::GetMatchTime() | |
{ | |
if (m_approxMatchTimeOffset < 0.0) | |
return 0.0; | |
return Timer::GetFPGATimestamp() - m_approxMatchTimeOffset; | |
} | |
/** | |
* Return the team number that the Driver Station is configured for | |
* @return The team number | |
*/ | |
UINT16 DriverStation::GetTeamNumber() | |
{ | |
return m_controlData->teamID; | |
} |