Program Listing for File ref_serial.cpp
↰ Return to documentation for file (src/tap/communication/serial/ref_serial.cpp
)
/*
* Copyright (c) 2020-2021 Advanced Robotics at the University of Washington <robomstr@uw.edu>
*
* This file is part of Taproot.
*
* Taproot is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Taproot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Taproot. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ref_serial.hpp"
#include "tap/algorithms/crc.hpp"
#include "tap/architecture/clock.hpp"
#include "tap/architecture/endianness_wrappers.hpp"
#include "tap/communication/serial/ref_serial_constants.hpp"
#include "tap/drivers.hpp"
#include "tap/errors/create_errors.hpp"
using namespace tap::arch;
namespace tap::communication::serial
{
RefSerial::RefSerial(Drivers* drivers)
: DJISerial(drivers, bound_ports::REF_SERIAL_UART_PORT),
robotData(),
gameData(),
receivedDpsTracker(),
transmissionSemaphore(1)
{
refSerialOfflineTimeout.stop();
}
bool RefSerial::getRefSerialReceivingData() const
{
return !(refSerialOfflineTimeout.isStopped() || refSerialOfflineTimeout.isExpired());
}
void RefSerial::messageReceiveCallback(const ReceivedSerialMessage& completeMessage)
{
refSerialOfflineTimeout.restart(TIME_OFFLINE_REF_DATA_MS);
updateReceivedDamage();
switch (completeMessage.messageType)
{
case REF_MESSAGE_TYPE_GAME_STATUS:
{
decodeToGameStatus(completeMessage);
break;
}
case REF_MESSAGE_TYPE_GAME_RESULT:
{
decodeToGameResult(completeMessage);
break;
}
case REF_MESSAGE_TYPE_ALL_ROBOT_HP:
{
decodeToAllRobotHP(completeMessage);
break;
}
case REF_MESSAGE_TYPE_SITE_EVENT_DATA:
{
decodeToSiteEventData(completeMessage);
break;
}
case REF_MESSAGE_TYPE_PROJECTILE_SUPPPLIER_ACTION:
{
decodeToProjectileSupplierAction(completeMessage);
break;
}
case REF_MESSAGE_TYPE_WARNING_DATA:
{
decodeToWarningData(completeMessage);
break;
}
case REF_MESSAGE_TYPE_DART_INFO:
{
decodeToDartInfo(completeMessage);
break;
}
case REF_MESSAGE_TYPE_ROBOT_STATUS:
{
decodeToRobotStatus(completeMessage);
break;
}
case REF_MESSAGE_TYPE_POWER_AND_HEAT:
{
decodeToPowerAndHeat(completeMessage);
break;
}
case REF_MESSAGE_TYPE_ROBOT_POSITION:
{
decodeToRobotPosition(completeMessage);
break;
}
case REF_MESSAGE_TYPE_ROBOT_BUFF_STATUS:
{
decodeToRobotBuffs(completeMessage);
break;
}
case REF_MESSAGE_TYPE_AERIAL_ENERGY_STATUS:
{
decodeToAerialEnergyStatus(completeMessage);
break;
}
case REF_MESSAGE_TYPE_RECEIVE_DAMAGE:
{
decodeToDamageStatus(completeMessage);
break;
}
case REF_MESSAGE_TYPE_PROJECTILE_LAUNCH:
{
decodeToProjectileLaunch(completeMessage);
break;
}
case REF_MESSAGE_TYPE_BULLETS_REMAIN:
{
decodeToBulletsRemain(completeMessage);
break;
}
case REF_MESSAGE_TYPE_RFID_STATUS:
{
decodeToRFIDStatus(completeMessage);
break;
}
case REF_MESSAGE_TYPE_DART_STATION_INFO:
{
decodeToDartStation(completeMessage);
break;
}
case REF_MESSAGE_TYPE_GROUND_ROBOT_POSITION:
{
decodeToGroundPositions(completeMessage);
break;
}
case REF_MESSAGE_TYPE_RADAR_PROGRESS:
{
decodeToRadarProgress(completeMessage);
break;
}
case REF_MESSAGE_TYPE_SENTRY_INFO:
{
decodeToSentryInfo(completeMessage);
break;
}
case REF_MESSAGE_TYPE_RADAR_INFO:
{
decodeToRadarInfo(completeMessage);
break;
}
case REF_MESSAGE_TYPE_CUSTOM_DATA:
{
handleRobotToRobotCommunication(completeMessage);
break;
}
// TODO: Other Custom Data stuff
default:
break;
}
}
const RefSerialData::Rx::RobotData& RefSerial::getRobotData() const { return robotData; }
const RefSerialData::Rx::GameData& RefSerial::getGameData() const { return gameData; }
bool RefSerial::decodeToGameStatus(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 11)
{
return false;
}
gameData.gameType = static_cast<Rx::GameType>(0xf & message.data[0]);
gameData.gameStage = static_cast<Rx::GameStage>(0xf & (message.data[0] >> 4));
convertFromLittleEndian(&gameData.stageTimeRemaining, message.data + 1);
// reinterpreting as a uint64_t doesn't work, so do this instead
gameData.unixTime = static_cast<uint64_t>(message.data[10]) << 56 |
static_cast<uint64_t>(message.data[9]) << 48 |
static_cast<uint64_t>(message.data[8]) << 40 |
static_cast<uint64_t>(message.data[7]) << 32 |
static_cast<uint64_t>(message.data[6]) << 24 |
static_cast<uint64_t>(message.data[5]) << 16 |
static_cast<uint64_t>(message.data[4]) << 8 |
static_cast<uint64_t>(message.data[3]);
return true;
}
bool RefSerial::decodeToGameResult(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 1)
{
return false;
}
gameData.gameWinner = static_cast<Rx::GameWinner>(message.data[0]);
return true;
}
bool RefSerial::decodeToAllRobotHP(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 32)
{
return false;
}
convertFromLittleEndian(&robotData.allRobotHp.red.hero1, message.data);
convertFromLittleEndian(&robotData.allRobotHp.red.engineer2, message.data + 2);
convertFromLittleEndian(&robotData.allRobotHp.red.standard3, message.data + 4);
convertFromLittleEndian(&robotData.allRobotHp.red.standard4, message.data + 6);
convertFromLittleEndian(&robotData.allRobotHp.red.standard5, message.data + 8);
convertFromLittleEndian(&robotData.allRobotHp.red.sentry7, message.data + 10);
convertFromLittleEndian(&robotData.allRobotHp.red.outpost, message.data + 12);
convertFromLittleEndian(&robotData.allRobotHp.red.base, message.data + 14);
convertFromLittleEndian(&robotData.allRobotHp.blue.hero1, message.data + 16);
convertFromLittleEndian(&robotData.allRobotHp.blue.engineer2, message.data + 18);
convertFromLittleEndian(&robotData.allRobotHp.blue.standard3, message.data + 20);
convertFromLittleEndian(&robotData.allRobotHp.blue.standard4, message.data + 22);
convertFromLittleEndian(&robotData.allRobotHp.blue.standard5, message.data + 24);
convertFromLittleEndian(&robotData.allRobotHp.blue.sentry7, message.data + 26);
convertFromLittleEndian(&robotData.allRobotHp.blue.outpost, message.data + 28);
convertFromLittleEndian(&robotData.allRobotHp.blue.base, message.data + 30);
return true;
}
bool RefSerial::decodeToSiteEventData(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 4)
{
return false;
}
uint32_t data = 0;
convertFromLittleEndian(&data, message.data);
gameData.eventData.siteData.value = data;
gameData.eventData.virtualShieldRemainingPercent = static_cast<uint8_t>((data >> 12) & 0x3F);
gameData.eventData.timeSinceLastDartHit = static_cast<uint8_t>((data >> 19) & 0xFF);
gameData.eventData.lastDartHit = static_cast<Rx::SiteDartHit>((data >> 28) & 0x03);
return true;
}
bool RefSerial::decodeToProjectileSupplierAction(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 4)
{
return false;
}
gameData.supplier.reloadingRobot = static_cast<RobotId>(message.data[1]);
gameData.supplier.outletStatus = static_cast<Rx::SupplierOutletStatus>(message.data[2]);
gameData.supplier.suppliedProjectiles = message.data[3];
return true;
}
bool RefSerial::decodeToWarningData(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 3)
{
return false;
}
robotData.refereeWarningData.level = message.data[0];
robotData.refereeWarningData.foulRobotID = static_cast<RobotId>(message.data[1]);
robotData.refereeWarningData.count = message.data[2];
robotData.refereeWarningData.lastReceivedWarningRobotTime = clock::getTimeMilliseconds();
return true;
}
bool RefSerial::decodeToDartInfo(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 3)
{
return false;
}
gameData.dartInfo.launchCountdown = message.data[0];
gameData.dartInfo.lastHit = static_cast<Rx::SiteDartHit>(message.data[1] & 0x03);
gameData.dartInfo.hits = (message.data[1] >> 2) & 0x07;
gameData.dartInfo.selectedTarget = static_cast<Rx::DartTarget>((message.data[1] >> 5) & 0x03);
return true;
}
bool RefSerial::decodeToRobotStatus(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 13)
{
return false;
}
robotData.robotId = static_cast<RobotId>(message.data[0]);
robotData.robotLevel = message.data[1];
convertFromLittleEndian(&robotData.currentHp, message.data + 2);
convertFromLittleEndian(&robotData.maxHp, message.data + 4);
convertFromLittleEndian(&robotData.turret.coolingRate, message.data + 6);
convertFromLittleEndian(&robotData.turret.heatLimit, message.data + 8);
convertFromLittleEndian(&robotData.chassis.powerConsumptionLimit, message.data + 10);
robotData.robotPower.value = message.data[12] & 0b111;
robotData.robotDataReceivedTimestamp = clock::getTimeMilliseconds();
processReceivedDamage(
robotData.robotDataReceivedTimestamp,
static_cast<int>(robotData.previousHp) - static_cast<int>(robotData.currentHp));
robotData.previousHp = robotData.currentHp;
return true;
}
bool RefSerial::decodeToPowerAndHeat(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 16)
{
return false;
}
convertFromLittleEndian(&robotData.chassis.volt, message.data);
convertFromLittleEndian(&robotData.chassis.current, message.data + 2);
convertFromLittleEndian(&robotData.chassis.power, message.data + 4);
convertFromLittleEndian(&robotData.chassis.powerBuffer, message.data + 8);
convertFromLittleEndian(&robotData.turret.heat17ID1, message.data + 10);
convertFromLittleEndian(&robotData.turret.heat17ID2, message.data + 12);
convertFromLittleEndian(&robotData.turret.heat42, message.data + 14);
return true;
}
bool RefSerial::decodeToRobotPosition(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 12)
{
return false;
}
convertFromLittleEndian(&robotData.chassis.position.x, message.data);
convertFromLittleEndian(&robotData.chassis.position.y, message.data + 4);
convertFromLittleEndian(&robotData.turret.yaw, message.data + 8);
return true;
}
bool RefSerial::decodeToRobotBuffs(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 6)
{
return false;
}
robotData.robotBuffStatus.recoveryBuff = message.data[0];
robotData.robotBuffStatus.coolingBuff = message.data[1];
robotData.robotBuffStatus.defenseBuff = message.data[2];
robotData.robotBuffStatus.vulnerabilityBuff = message.data[3];
convertFromLittleEndian(&robotData.robotBuffStatus.attackBuff, message.data + 4);
return true;
}
bool RefSerial::decodeToAerialEnergyStatus(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 2)
{
return false;
}
gameData.airSupportData.state = static_cast<Rx::AirSupportState>(message.data[0] & 0x03);
gameData.airSupportData.remainingStateTime = message.data[1];
return true;
}
bool RefSerial::decodeToDamageStatus(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 1)
{
return false;
}
robotData.damagedArmorId = static_cast<Rx::ArmorId>((message.data[0]) & 0xf);
robotData.damageType = static_cast<Rx::DamageType>((message.data[0] >> 4) & 0xf);
return true;
}
bool RefSerial::decodeToProjectileLaunch(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 7)
{
return false;
}
robotData.turret.bulletType = static_cast<Rx::BulletType>(message.data[0]);
robotData.turret.launchMechanismID = static_cast<Rx::MechanismID>(message.data[1]);
robotData.turret.firingFreq = message.data[2];
robotData.turret.lastReceivedLaunchingInfoTimestamp = clock::getTimeMilliseconds();
convertFromLittleEndian(&robotData.turret.bulletSpeed, message.data + 3);
return true;
}
bool RefSerial::decodeToBulletsRemain(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 6)
{
return false;
}
convertFromLittleEndian(&robotData.turret.bulletsRemaining17, message.data);
convertFromLittleEndian(&robotData.turret.bulletsRemaining42, message.data + 2);
convertFromLittleEndian(&robotData.remainingCoins, message.data + 4);
return true;
}
bool RefSerial::decodeToRFIDStatus(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 4)
{
return false;
}
convertFromLittleEndian(&robotData.rfidStatus.value, message.data);
return true;
}
bool RefSerial::decodeToDartStation(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 6)
{
return false;
}
gameData.dartStation.state = static_cast<Rx::DartStationState>(message.data[0] & 0x03);
convertFromLittleEndian(&gameData.dartStation.targetChangedTime, message.data + 2);
convertFromLittleEndian(&gameData.dartStation.lastLaunchedTime, message.data + 4);
return true;
}
bool RefSerial::decodeToGroundPositions(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 40)
{
return false;
}
convertFromLittleEndian(&gameData.positions.hero.x, message.data);
convertFromLittleEndian(&gameData.positions.hero.y, message.data + 4);
convertFromLittleEndian(&gameData.positions.engineer.x, message.data + 8);
convertFromLittleEndian(&gameData.positions.engineer.y, message.data + 12);
convertFromLittleEndian(&gameData.positions.standard3.x, message.data + 16);
convertFromLittleEndian(&gameData.positions.standard3.y, message.data + 20);
convertFromLittleEndian(&gameData.positions.standard4.x, message.data + 24);
convertFromLittleEndian(&gameData.positions.standard4.y, message.data + 28);
convertFromLittleEndian(&gameData.positions.standard5.x, message.data + 32);
convertFromLittleEndian(&gameData.positions.standard5.y, message.data + 36);
return true;
}
bool RefSerial::decodeToRadarProgress(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 6)
{
return false;
}
gameData.radarProgress.hero = message.data[1];
gameData.radarProgress.engineer = message.data[2];
gameData.radarProgress.standard3 = message.data[3];
gameData.radarProgress.standard4 = message.data[4];
gameData.radarProgress.standard5 = message.data[5];
gameData.radarProgress.sentry = message.data[6];
return true;
}
bool RefSerial::decodeToSentryInfo(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 4)
{
return false;
}
uint32_t data;
convertFromLittleEndian(&data, message.data);
gameData.sentry.projectileAllowance = static_cast<uint16_t>(data & 0x03FF);
gameData.sentry.remoteProjectileExchanges = static_cast<uint8_t>((data >> 11) & 0x0F);
gameData.sentry.remoteHealthExchanges = static_cast<uint8_t>((data >> 14) & 0x0F);
return true;
}
bool RefSerial::decodeToRadarInfo(const ReceivedSerialMessage& message)
{
if (message.header.dataLength != 1)
{
return false;
}
gameData.radar.availableDoubleVulnerablilityEffects = message.data[0] & 0x03;
gameData.radar.activeDoubleVulnerabilityEffect = (message.data[0] >> 2) & 0x01;
return true;
}
bool RefSerial::handleRobotToRobotCommunication(const ReceivedSerialMessage& message)
{
if (message.header.dataLength < sizeof(Tx::RobotToRobotMessage::interactiveHeader))
{
return false;
}
if (msgIdToRobotToRobotHandlerMap.size() == 0)
{
return true;
}
const Tx::InteractiveHeader* interactiveHeader =
reinterpret_cast<const Tx::InteractiveHeader*>(message.data);
if (msgIdToRobotToRobotHandlerMap.count(interactiveHeader->dataCmdId))
{
(*msgIdToRobotToRobotHandlerMap[interactiveHeader->dataCmdId])(message);
}
return true;
}
void RefSerial::processReceivedDamage(uint32_t timestamp, int32_t damageTaken)
{
if (damageTaken > 0)
{
// create a new DamageEvent with the damage_taken, and current time
Rx::DamageEvent damageEvent = {static_cast<uint16_t>(damageTaken), timestamp};
if (receivedDpsTracker.getSize() == DPS_TRACKER_DEQUE_SIZE)
{
receivedDpsTracker.removeBack();
}
robotData.receivedDps += damageTaken;
receivedDpsTracker.append(damageEvent);
}
}
void RefSerial::updateReceivedDamage()
{
// if current damage at head of circular array occurred more than a second ago,
// decrease receivedDps by that amount of damage and increment head index
while (receivedDpsTracker.getSize() > 0 &&
clock::getTimeMilliseconds() > receivedDpsTracker.getFront().timestampMs + 1000)
{
robotData.receivedDps -= receivedDpsTracker.getFront().damageAmount;
receivedDpsTracker.removeFront();
}
}
RefSerial::RobotId RefSerial::getRobotIdBasedOnCurrentRobotTeam(RobotId id)
{
if (id == RobotId::INVALID || robotData.robotId == RobotId::INVALID)
{
return id;
}
if (!isBlueTeam(robotData.robotId) && isBlueTeam(id))
{
id = id - RobotId::BLUE_HERO + RobotId::RED_HERO;
}
else if (isBlueTeam(robotData.robotId) && !isBlueTeam(id))
{
id = id - RobotId::RED_HERO + RobotId::BLUE_HERO;
}
return id;
}
void RefSerial::attachRobotToRobotMessageHandler(
uint16_t msgId,
RobotToRobotMessageHandler* handler)
{
if (msgIdToRobotToRobotHandlerMap.count(msgId) != 0 || msgId < 0x0200 || msgId > 0x02FF)
{
RAISE_ERROR(drivers, "error adding msg handler");
return;
}
msgIdToRobotToRobotHandlerMap[msgId] = handler;
}
bool RefSerial::operatorBlinded() const
{
const uint32_t blindTime = (robotData.refereeWarningData.foulRobotID == robotData.robotId)
? Rx::RefereeWarningData::OFFENDING_OPERATOR_BLIND_TIME
: Rx::RefereeWarningData::NONOFFENDING_OPERATOR_BLIND_TIME;
const uint32_t lastReceivedWarningRobotTime =
robotData.refereeWarningData.lastReceivedWarningRobotTime;
return getRefSerialReceivingData() && (lastReceivedWarningRobotTime != 0) &&
(arch::clock::getTimeMilliseconds() - lastReceivedWarningRobotTime <= blindTime);
}
} // namespace tap::communication::serial