// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #include "XRP.h" #include #include #include #include #include using namespace wpilibxrp; XRP::XRP() : m_gyro_name{"XRPGyro"}, m_wpilib_update_func([](const wpi::json&) {}) { // Set up the inputs and outputs m_motor_outputs.emplace(0, 0.0f); m_motor_outputs.emplace(1, 0.0f); m_motor_outputs.emplace(2, 0.0f); m_motor_outputs.emplace(3, 0.0f); m_servo_outputs.emplace(4, 0.5f); m_servo_outputs.emplace(5, 0.5f); m_encoder_inputs.emplace(1, 0); m_encoder_inputs.emplace(2, 0); m_encoder_inputs.emplace(0, 0); m_encoder_inputs.emplace(3, 0); } void XRP::HandleWPILibUpdate(const wpi::json& data) { if (data.count("type") == 0) { return; } if (data["type"] == "DriverStation") { HandleDriverStationSimValueChanged(data); } else if (data["type"] == "XRPMotor") { HandleMotorSimValueChanged(data); } else if (data["type"] == "XRPServo") { HandleServoSimValueChanged(data); } else if (data["type"] == "DIO") { HandleDIOSimValueChanged(data); } else if (data["type"] == "Gyro") { HandleGyroSimValueChanged(data); } else if (data["type"] == "Encoder") { HandleEncoderSimValueChanged(data); } } void XRP::HandleXRPUpdate(std::span packet) { uint16_t seq = (packet[0] << 8) + packet[1]; if (seq <= m_wpilib_bound_seq) { // If the old sequence was within 3 or uint16_t max and the new // sequence is < 3 - we've prob rolled over if (!((0xFFFF - m_wpilib_bound_seq < 3) && seq < 3)) { return; } } m_wpilib_bound_seq = seq; // Tagged data starts at byte 3 packet = packet.subspan(3); // Loop to handle multiple tags while (!packet.empty()) { auto tagLength = packet[0]; auto tagPacket = packet.subspan(0, tagLength + 1); // NOTE: tagPacket contains the size and tag bytes as well // Verify that the packet is indeed the right size if (tagPacket.size() != static_cast(tagLength + 1)) { break; } switch (packet[1]) { case XRP_TAG_GYRO: ReadGyroTag(tagPacket); break; case XRP_TAG_ACCEL: ReadAccelTag(tagPacket); break; case XRP_TAG_DIO: ReadDIOTag(tagPacket); break; case XRP_TAG_ENCODER: ReadEncoderTag(tagPacket); break; case XRP_TAG_ANALOG: ReadAnalogTag(tagPacket); break; } packet = packet.subspan(tagLength + 1); } } void XRP::SetupXRPSendBuffer(wpi::raw_uv_ostream& buf) { SetupSendHeader(buf); SetupMotorTag(buf); SetupServoTag(buf); SetupDigitalOutTag(buf); m_xrp_bound_seq++; } // WPILib Sim Handlers void XRP::HandleDriverStationSimValueChanged(const wpi::json& data) { auto dsData = data["data"]; if (dsData.find(">enabled") != dsData.end()) { m_robot_enabled = dsData[">enabled"]; } } void XRP::HandleMotorSimValueChanged(const wpi::json& data) { int deviceId = -1; auto motorData = data["data"]; if (data["device"] == "motorL") { deviceId = 0; } else if (data["device"] == "motorR") { deviceId = 1; } else if (data["device"] == "motor3") { deviceId = 2; } else if (data["device"] == "motor4") { deviceId = 3; } if (deviceId != -1 && motorData.find("()); } catch (const std::invalid_argument&) { deviceId = -1; } // Bail out early if device ID is invalid or if it's "spoken for" if (deviceId == -1) { return; } if (dioData.find("value") != dioData.end() && m_digital_outputs.count(deviceId) > 0) { m_digital_outputs[deviceId] = dioData["<>value"]; } } void XRP::HandleGyroSimValueChanged(const wpi::json& data) { m_gyro_name = data["device"].get(); } void XRP::HandleEncoderSimValueChanged(const wpi::json& data) { // We need to handle the various encoder cases // 4/5 -> Encoder 0 // 6/7 -> Encoder 1 // 8/9 -> Encoder 2 // 10/11 -> Encoder 3 int deviceId = -1; auto encData = data["data"]; try { deviceId = std::stoi(data["device"].get()); } catch (const std::invalid_argument&) { deviceId = -1; } if (deviceId == -1) { return; } if (encData.find("(m_robot_enabled ? 1 : 0); } void XRP::SetupMotorTag(wpi::raw_uv_ostream& buf) { uint8_t value[4]; for (auto motor : m_motor_outputs) { // Motor payload is 6 bytes buf << static_cast(6) // Size << static_cast(XRP_TAG_MOTOR) // Tag << static_cast(motor.first); // Channel // Convert the value wpi::support::endian::write32be(value, wpi::bit_cast(motor.second)); buf << value[0] << value[1] << value[2] << value[3]; } } void XRP::SetupServoTag(wpi::raw_uv_ostream& buf) { uint8_t value[4]; for (auto servo : m_servo_outputs) { // Servo payload is 6 bytes buf << static_cast(6) // Size << static_cast(XRP_TAG_SERVO) // Tag << static_cast(servo.first); // Channel // Convert the value wpi::support::endian::write32be(value, wpi::bit_cast(servo.second)); buf << value[0] << value[1] << value[2] << value[3]; } } void XRP::SetupDigitalOutTag(wpi::raw_uv_ostream& buf) { for (auto digitalOut : m_digital_outputs) { // DIO payload is 3 bytes buf << static_cast(3) // Size << static_cast(XRP_TAG_DIO) // Tag << static_cast(digitalOut.first) // Channel << static_cast(digitalOut.second ? 1 : 0); // Value } } void XRP::ReadGyroTag(std::span packet) { if (packet.size() < 26) { return; // size(1) + tag(1) + 6x 4byte } packet = packet.subspan(2); // Skip past the size and tag float rate_x = wpi::bit_cast(wpi::support::endian::read32be(&packet[0])); float rate_y = wpi::bit_cast(wpi::support::endian::read32be(&packet[4])); float rate_z = wpi::bit_cast(wpi::support::endian::read32be(&packet[8])); float angle_x = wpi::bit_cast(wpi::support::endian::read32be(&packet[12])); float angle_y = wpi::bit_cast(wpi::support::endian::read32be(&packet[16])); float angle_z = wpi::bit_cast(wpi::support::endian::read32be(&packet[20])); // Make the json object wpi::json gyroJson; gyroJson["type"] = "Gyro"; gyroJson["device"] = m_gyro_name; gyroJson["data"] = {{">rate_x", rate_x}, {">rate_y", rate_y}, {">rate_z", rate_z}, {">angle_x", angle_x}, {">angle_y", angle_y}, {">angle_z", angle_z}}; // Update WPILib m_wpilib_update_func(gyroJson); } void XRP::ReadAccelTag(std::span packet) { if (packet.size() < 14) { return; // size(1) + tag(1) + 3x 4 byte } packet = packet.subspan(2); // Skip past the size and tag float accel_x = wpi::bit_cast(wpi::support::endian::read32be(&packet[0])); float accel_y = wpi::bit_cast(wpi::support::endian::read32be(&packet[4])); float accel_z = wpi::bit_cast(wpi::support::endian::read32be(&packet[8])); wpi::json accelJson; accelJson["type"] = "Accel"; accelJson["device"] = "BuiltInAccel"; accelJson["data"] = {{">x", accel_x}, {">y", accel_y}, {">z", accel_z}}; // Update WPILib m_wpilib_update_func(accelJson); } void XRP::ReadDIOTag(std::span packet) { if (packet.size() < 4) { return; // size(1) + tag(1) + id(1) + value(1) } wpi::json dioJson; dioJson["type"] = "DIO"; dioJson["device"] = std::to_string(packet[2]); dioJson["data"] = {{"<>value", packet[3] == 1}}; m_wpilib_update_func(dioJson); } void XRP::ReadEncoderTag(std::span packet) { if (packet.size() < 7) { return; // size(1) + tag(1) + id(1) + count(4) } // size(1) + tag(1) + id(1) + count(4) + period_numerator(4) + // period_denominator(4) bool containsPeriod = packet.size() >= 15; uint8_t encoderId = packet[2]; packet = packet.subspan(3); // Skip past the size and tag and ID int32_t count = static_cast(wpi::support::endian::read32be(&packet[0])); // Look up the registered encoders if (m_encoder_channel_map.count(encoderId) == 0) { return; } uint8_t wpilibEncoderChannel = m_encoder_channel_map[encoderId]; wpi::json encJson; encJson["type"] = "Encoder"; encJson["device"] = std::to_string(wpilibEncoderChannel); encJson["data"] = {{">count", count}}; if (containsPeriod) { // Older versions of XRP firmware do not provide Encoder Period. // Only calculate period if the Encoder packet contains the period data. // The period is a fraction consisting of two integers: a numerator and // denominator. The least significant bit of the numerator specifies the // direction: 1 -> forward, 0 -> reverse. The direction bit must be removed // to calculate the magnitude of the period. size_t i = sizeof(count); uint32_t period_numerator = static_cast(wpi::support::endian::read32be(&packet[i])); i += sizeof(period_numerator); uint32_t period_denominator = static_cast(wpi::support::endian::read32be(&packet[i])); double period = static_cast(period_numerator >> 1) / period_denominator; // If direction is not forward, return negative value for period. if (!(period_numerator & 1)) { period = -period; } encJson["data"].push_back({wpi::json(">period"), wpi::json(period)}); } m_wpilib_update_func(encJson); } void XRP::ReadAnalogTag(std::span packet) { if (packet.size() < 7) { return; // size(1) + tag(1) + id(1) + float } uint8_t analogId = packet[2]; packet = packet.subspan(3); float voltage = wpi::bit_cast(wpi::support::endian::read32be(&packet[0])); wpi::json analogJson; analogJson["type"] = "AI"; analogJson["device"] = std::to_string(analogId); analogJson["data"] = {{">voltage", voltage}}; m_wpilib_update_func(analogJson); }