Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 1 | // Copyright (c) FIRST and other WPILib contributors. |
| 2 | // Open Source Software; you can modify and/or share it under the terms of |
| 3 | // the WPILib BSD license file in the root directory of this project. |
| 4 | |
| 5 | #include "hal/REVPH.h" |
| 6 | |
| 7 | #include <fmt/format.h> |
| 8 | |
| 9 | #include "HALInitializer.h" |
| 10 | #include "HALInternal.h" |
| 11 | #include "PortsInternal.h" |
| 12 | #include "hal/CANAPI.h" |
| 13 | #include "hal/Errors.h" |
| 14 | #include "hal/handles/IndexedHandleResource.h" |
| 15 | #include "rev/PHFrames.h" |
| 16 | |
| 17 | using namespace hal; |
| 18 | |
| 19 | static constexpr HAL_CANManufacturer manufacturer = |
| 20 | HAL_CANManufacturer::HAL_CAN_Man_kREV; |
| 21 | |
| 22 | static constexpr HAL_CANDeviceType deviceType = |
| 23 | HAL_CANDeviceType::HAL_CAN_Dev_kPneumatics; |
| 24 | |
| 25 | static constexpr int32_t kDefaultControlPeriod = 20; |
| 26 | // static constexpr uint8_t kDefaultSensorMask = (1 << |
| 27 | // HAL_REV_PHSENSOR_DIGITAL); |
| 28 | static constexpr uint8_t kDefaultCompressorDuty = 255; |
| 29 | static constexpr uint8_t kDefaultPressureTarget = 120; |
| 30 | static constexpr uint8_t kDefaultPressureHysteresis = 60; |
| 31 | |
| 32 | #define HAL_REV_MAX_PULSE_TIME 65534 |
| 33 | #define HAL_REV_MAX_PRESSURE_TARGET 120 |
| 34 | #define HAL_REV_MAX_PRESSURE_HYSTERESIS HAL_REV_MAX_PRESSURE_TARGET |
| 35 | |
| 36 | static constexpr uint32_t APIFromExtId(uint32_t extId) { |
| 37 | return (extId >> 6) & 0x3FF; |
| 38 | } |
| 39 | |
| 40 | static constexpr uint32_t PH_SET_ALL_FRAME_API = |
| 41 | APIFromExtId(PH_SET_ALL_FRAME_ID); |
| 42 | static constexpr uint32_t PH_PULSE_ONCE_FRAME_API = |
| 43 | APIFromExtId(PH_PULSE_ONCE_FRAME_ID); |
| 44 | static constexpr uint32_t PH_STATUS0_FRAME_API = |
| 45 | APIFromExtId(PH_STATUS0_FRAME_ID); |
| 46 | static constexpr uint32_t PH_STATUS1_FRAME_API = |
| 47 | APIFromExtId(PH_STATUS1_FRAME_ID); |
| 48 | |
| 49 | static constexpr int32_t kPHFrameStatus0Timeout = 50; |
| 50 | static constexpr int32_t kPHFrameStatus1Timeout = 50; |
| 51 | |
| 52 | namespace { |
| 53 | |
| 54 | struct REV_PHObj { |
| 55 | int32_t controlPeriod; |
| 56 | PH_set_all_t desiredSolenoidsState; |
| 57 | wpi::mutex solenoidLock; |
| 58 | HAL_CANHandle hcan; |
| 59 | std::string previousAllocation; |
| 60 | }; |
| 61 | |
| 62 | } // namespace |
| 63 | |
| 64 | static IndexedHandleResource<HAL_REVPHHandle, REV_PHObj, 63, |
| 65 | HAL_HandleEnum::REVPH>* REVPHHandles; |
| 66 | |
| 67 | namespace hal::init { |
| 68 | void InitializeREVPH() { |
| 69 | static IndexedHandleResource<HAL_REVPHHandle, REV_PHObj, kNumREVPHModules, |
| 70 | HAL_HandleEnum::REVPH> |
| 71 | rH; |
| 72 | REVPHHandles = &rH; |
| 73 | } |
| 74 | } // namespace hal::init |
| 75 | |
| 76 | static PH_status0_t HAL_REV_ReadPHStatus0(HAL_CANHandle hcan, int32_t* status) { |
| 77 | uint8_t packedData[8] = {0}; |
| 78 | int32_t length = 0; |
| 79 | uint64_t timestamp = 0; |
| 80 | PH_status0_t result = {}; |
| 81 | |
| 82 | HAL_ReadCANPacketTimeout(hcan, PH_STATUS0_FRAME_API, packedData, &length, |
| 83 | ×tamp, kPHFrameStatus0Timeout * 2, status); |
| 84 | |
| 85 | if (*status != 0) { |
| 86 | return result; |
| 87 | } |
| 88 | |
| 89 | PH_status0_unpack(&result, packedData, PH_STATUS0_LENGTH); |
| 90 | |
| 91 | return result; |
| 92 | } |
| 93 | |
| 94 | static PH_status1_t HAL_REV_ReadPHStatus1(HAL_CANHandle hcan, int32_t* status) { |
| 95 | uint8_t packedData[8] = {0}; |
| 96 | int32_t length = 0; |
| 97 | uint64_t timestamp = 0; |
| 98 | PH_status1_t result = {}; |
| 99 | |
| 100 | HAL_ReadCANPacketTimeout(hcan, PH_STATUS1_FRAME_API, packedData, &length, |
| 101 | ×tamp, kPHFrameStatus1Timeout * 2, status); |
| 102 | |
| 103 | if (*status != 0) { |
| 104 | return result; |
| 105 | } |
| 106 | |
| 107 | PH_status1_unpack(&result, packedData, PH_STATUS1_LENGTH); |
| 108 | |
| 109 | return result; |
| 110 | } |
| 111 | |
| 112 | enum REV_SolenoidState { |
| 113 | kSolenoidDisabled = 0, |
| 114 | kSolenoidEnabled, |
| 115 | kSolenoidControlledViaPulse |
| 116 | }; |
| 117 | |
| 118 | static void HAL_REV_UpdateDesiredPHSolenoidState(REV_PHObj* hph, |
| 119 | int32_t solenoid, |
| 120 | REV_SolenoidState state) { |
| 121 | switch (solenoid) { |
| 122 | case 0: |
| 123 | hph->desiredSolenoidsState.channel_0 = state; |
| 124 | break; |
| 125 | case 1: |
| 126 | hph->desiredSolenoidsState.channel_1 = state; |
| 127 | break; |
| 128 | case 2: |
| 129 | hph->desiredSolenoidsState.channel_2 = state; |
| 130 | break; |
| 131 | case 3: |
| 132 | hph->desiredSolenoidsState.channel_3 = state; |
| 133 | break; |
| 134 | case 4: |
| 135 | hph->desiredSolenoidsState.channel_4 = state; |
| 136 | break; |
| 137 | case 5: |
| 138 | hph->desiredSolenoidsState.channel_5 = state; |
| 139 | break; |
| 140 | case 6: |
| 141 | hph->desiredSolenoidsState.channel_6 = state; |
| 142 | break; |
| 143 | case 7: |
| 144 | hph->desiredSolenoidsState.channel_7 = state; |
| 145 | break; |
| 146 | case 8: |
| 147 | hph->desiredSolenoidsState.channel_8 = state; |
| 148 | break; |
| 149 | case 9: |
| 150 | hph->desiredSolenoidsState.channel_9 = state; |
| 151 | break; |
| 152 | case 10: |
| 153 | hph->desiredSolenoidsState.channel_10 = state; |
| 154 | break; |
| 155 | case 11: |
| 156 | hph->desiredSolenoidsState.channel_11 = state; |
| 157 | break; |
| 158 | case 12: |
| 159 | hph->desiredSolenoidsState.channel_12 = state; |
| 160 | break; |
| 161 | case 13: |
| 162 | hph->desiredSolenoidsState.channel_13 = state; |
| 163 | break; |
| 164 | case 14: |
| 165 | hph->desiredSolenoidsState.channel_14 = state; |
| 166 | break; |
| 167 | case 15: |
| 168 | hph->desiredSolenoidsState.channel_15 = state; |
| 169 | break; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | static void HAL_REV_SendSolenoidsState(REV_PHObj* hph, int32_t* status) { |
| 174 | uint8_t packedData[PH_SET_ALL_LENGTH] = {0}; |
| 175 | PH_set_all_pack(packedData, &(hph->desiredSolenoidsState), PH_SET_ALL_LENGTH); |
| 176 | HAL_WriteCANPacketRepeating(hph->hcan, packedData, PH_SET_ALL_LENGTH, |
| 177 | PH_SET_ALL_FRAME_API, hph->controlPeriod, status); |
| 178 | } |
| 179 | |
| 180 | static HAL_Bool HAL_REV_CheckPHPulseTime(int32_t time) { |
| 181 | return ((time > 0) && (time <= HAL_REV_MAX_PULSE_TIME)) ? 1 : 0; |
| 182 | } |
| 183 | |
| 184 | HAL_REVPHHandle HAL_InitializeREVPH(int32_t module, |
| 185 | const char* allocationLocation, |
| 186 | int32_t* status) { |
| 187 | hal::init::CheckInit(); |
| 188 | if (!HAL_CheckREVPHModuleNumber(module)) { |
| 189 | hal::SetLastErrorIndexOutOfRange(status, "Invalid Index for REV PH", 1, |
| 190 | kNumREVPHModules, module); |
| 191 | return HAL_kInvalidHandle; |
| 192 | } |
| 193 | |
| 194 | HAL_REVPHHandle handle; |
| 195 | auto hph = REVPHHandles->Allocate(module, &handle, status); |
| 196 | if (*status != 0) { |
| 197 | if (hph) { |
| 198 | hal::SetLastErrorPreviouslyAllocated(status, "REV PH", module, |
| 199 | hph->previousAllocation); |
| 200 | } else { |
| 201 | hal::SetLastErrorIndexOutOfRange(status, "Invalid Index for REV PH", 1, |
| 202 | kNumREVPHModules, module); |
| 203 | } |
| 204 | return HAL_kInvalidHandle; // failed to allocate. Pass error back. |
| 205 | } |
| 206 | |
| 207 | HAL_CANHandle hcan = |
| 208 | HAL_InitializeCAN(manufacturer, module, deviceType, status); |
| 209 | |
| 210 | if (*status != 0) { |
| 211 | REVPHHandles->Free(handle); |
| 212 | return HAL_kInvalidHandle; |
| 213 | } |
| 214 | |
| 215 | hph->previousAllocation = allocationLocation ? allocationLocation : ""; |
| 216 | hph->hcan = hcan; |
| 217 | hph->controlPeriod = kDefaultControlPeriod; |
| 218 | |
| 219 | // Start closed-loop compressor control by starting solenoid state updates |
| 220 | HAL_REV_SendSolenoidsState(hph.get(), status); |
| 221 | |
| 222 | return handle; |
| 223 | } |
| 224 | |
| 225 | void HAL_FreeREVPH(HAL_REVPHHandle handle) { |
| 226 | auto hph = REVPHHandles->Get(handle); |
| 227 | if (hph == nullptr) |
| 228 | return; |
| 229 | |
| 230 | HAL_CleanCAN(hph->hcan); |
| 231 | |
| 232 | REVPHHandles->Free(handle); |
| 233 | } |
| 234 | |
| 235 | HAL_Bool HAL_CheckREVPHModuleNumber(int32_t module) { |
| 236 | return module >= 1 && module < kNumREVPDHModules; |
| 237 | } |
| 238 | |
| 239 | HAL_Bool HAL_CheckREVPHSolenoidChannel(int32_t channel) { |
| 240 | return channel >= 0 && channel < kNumREVPHChannels; |
| 241 | } |
| 242 | |
| 243 | HAL_Bool HAL_GetREVPHCompressor(HAL_REVPHHandle handle, int32_t* status) { |
| 244 | auto ph = REVPHHandles->Get(handle); |
| 245 | if (ph == nullptr) { |
| 246 | *status = HAL_HANDLE_ERROR; |
| 247 | return false; |
| 248 | } |
| 249 | |
| 250 | PH_status0_t status0 = HAL_REV_ReadPHStatus0(ph->hcan, status); |
| 251 | |
| 252 | if (*status != 0) { |
| 253 | return false; |
| 254 | } |
| 255 | |
| 256 | return status0.compressor_on; |
| 257 | } |
| 258 | |
| 259 | void HAL_SetREVPHClosedLoopControl(HAL_REVPHHandle handle, HAL_Bool enabled, |
| 260 | int32_t* status) { |
| 261 | // TODO |
| 262 | } |
| 263 | |
| 264 | HAL_Bool HAL_GetREVPHClosedLoopControl(HAL_REVPHHandle handle, |
| 265 | int32_t* status) { |
| 266 | return false; // TODO |
| 267 | } |
| 268 | |
| 269 | HAL_Bool HAL_GetREVPHPressureSwitch(HAL_REVPHHandle handle, int32_t* status) { |
| 270 | auto ph = REVPHHandles->Get(handle); |
| 271 | if (ph == nullptr) { |
| 272 | *status = HAL_HANDLE_ERROR; |
| 273 | return false; |
| 274 | } |
| 275 | |
| 276 | PH_status0_t status0 = HAL_REV_ReadPHStatus0(ph->hcan, status); |
| 277 | |
| 278 | if (*status != 0) { |
| 279 | return false; |
| 280 | } |
| 281 | |
| 282 | return status0.digital_sensor; |
| 283 | } |
| 284 | |
| 285 | double HAL_GetREVPHCompressorCurrent(HAL_REVPHHandle handle, int32_t* status) { |
| 286 | auto ph = REVPHHandles->Get(handle); |
| 287 | if (ph == nullptr) { |
| 288 | *status = HAL_HANDLE_ERROR; |
| 289 | return 0; |
| 290 | } |
| 291 | |
| 292 | PH_status1_t status1 = HAL_REV_ReadPHStatus1(ph->hcan, status); |
| 293 | |
| 294 | if (*status != 0) { |
| 295 | return 0; |
| 296 | } |
| 297 | |
| 298 | return PH_status1_compressor_current_decode(status1.compressor_current); |
| 299 | } |
| 300 | |
| 301 | double HAL_GetREVPHAnalogPressure(HAL_REVPHHandle handle, int32_t channel, |
| 302 | int32_t* status) { |
| 303 | auto ph = REVPHHandles->Get(handle); |
| 304 | if (ph == nullptr) { |
| 305 | *status = HAL_HANDLE_ERROR; |
| 306 | return 0; |
| 307 | } |
| 308 | |
| 309 | if (channel < 0 || channel > 1) { |
| 310 | *status = PARAMETER_OUT_OF_RANGE; |
| 311 | hal::SetLastErrorIndexOutOfRange(status, "Invalid REV Analog Index", 0, 2, |
| 312 | channel); |
| 313 | return 0; |
| 314 | } |
| 315 | |
| 316 | PH_status0_t status0 = HAL_REV_ReadPHStatus0(ph->hcan, status); |
| 317 | |
| 318 | if (*status != 0) { |
| 319 | return 0; |
| 320 | } |
| 321 | |
| 322 | if (channel == 1) { |
| 323 | return PH_status0_analog_0_decode(status0.analog_0); |
| 324 | } |
| 325 | return PH_status0_analog_1_decode(status0.analog_1); |
| 326 | } |
| 327 | |
| 328 | int32_t HAL_GetREVPHSolenoids(HAL_REVPHHandle handle, int32_t* status) { |
| 329 | auto ph = REVPHHandles->Get(handle); |
| 330 | if (ph == nullptr) { |
| 331 | *status = HAL_HANDLE_ERROR; |
| 332 | return 0; |
| 333 | } |
| 334 | |
| 335 | PH_status0_t status0 = HAL_REV_ReadPHStatus0(ph->hcan, status); |
| 336 | |
| 337 | if (*status != 0) { |
| 338 | return 0; |
| 339 | } |
| 340 | |
| 341 | uint32_t result = status0.channel_0; |
| 342 | result |= status0.channel_1 << 1; |
| 343 | result |= status0.channel_2 << 2; |
| 344 | result |= status0.channel_3 << 3; |
| 345 | result |= status0.channel_4 << 4; |
| 346 | result |= status0.channel_5 << 5; |
| 347 | result |= status0.channel_6 << 6; |
| 348 | result |= status0.channel_7 << 7; |
| 349 | result |= status0.channel_8 << 8; |
| 350 | result |= status0.channel_9 << 9; |
| 351 | result |= status0.channel_10 << 10; |
| 352 | result |= status0.channel_11 << 11; |
| 353 | result |= status0.channel_12 << 12; |
| 354 | result |= status0.channel_13 << 13; |
| 355 | result |= status0.channel_14 << 14; |
| 356 | result |= status0.channel_15 << 15; |
| 357 | |
| 358 | return result; |
| 359 | } |
| 360 | |
| 361 | void HAL_SetREVPHSolenoids(HAL_REVPHHandle handle, int32_t mask, int32_t values, |
| 362 | int32_t* status) { |
| 363 | auto ph = REVPHHandles->Get(handle); |
| 364 | if (ph == nullptr) { |
| 365 | *status = HAL_HANDLE_ERROR; |
| 366 | return; |
| 367 | } |
| 368 | |
| 369 | std::scoped_lock lock{ph->solenoidLock}; |
| 370 | for (int solenoid = 0; solenoid < kNumREVPHChannels; solenoid++) { |
| 371 | if (mask & (1 << solenoid)) { |
| 372 | // The mask bit for the solenoid is set, so we update the solenoid state |
| 373 | REV_SolenoidState desiredSolenoidState = |
| 374 | values & (1 << solenoid) ? kSolenoidEnabled : kSolenoidDisabled; |
| 375 | HAL_REV_UpdateDesiredPHSolenoidState(ph.get(), solenoid, |
| 376 | desiredSolenoidState); |
| 377 | } |
| 378 | } |
| 379 | HAL_REV_SendSolenoidsState(ph.get(), status); |
| 380 | } |
| 381 | |
| 382 | void HAL_FireREVPHOneShot(HAL_REVPHHandle handle, int32_t index, int32_t durMs, |
| 383 | int32_t* status) { |
| 384 | auto ph = REVPHHandles->Get(handle); |
| 385 | if (ph == nullptr) { |
| 386 | *status = HAL_HANDLE_ERROR; |
| 387 | return; |
| 388 | } |
| 389 | |
| 390 | if (index >= kNumREVPHChannels || index < 0) { |
| 391 | *status = PARAMETER_OUT_OF_RANGE; |
| 392 | hal::SetLastError( |
| 393 | status, |
| 394 | fmt::format("Only [0-15] are valid index values. Requested {}", index)); |
| 395 | return; |
| 396 | } |
| 397 | |
| 398 | if (!HAL_REV_CheckPHPulseTime(durMs)) { |
| 399 | *status = PARAMETER_OUT_OF_RANGE; |
| 400 | hal::SetLastError( |
| 401 | status, |
| 402 | fmt::format("Time not within expected range [0-65534]. Requested {}", |
| 403 | durMs)); |
| 404 | return; |
| 405 | } |
| 406 | |
| 407 | { |
| 408 | std::scoped_lock lock{ph->solenoidLock}; |
| 409 | HAL_REV_UpdateDesiredPHSolenoidState(ph.get(), index, |
| 410 | kSolenoidControlledViaPulse); |
| 411 | HAL_REV_SendSolenoidsState(ph.get(), status); |
| 412 | } |
| 413 | |
| 414 | if (*status != 0) { |
| 415 | return; |
| 416 | } |
| 417 | |
| 418 | PH_pulse_once_t pulse = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
| 419 | pulse.pulse_length_ms = durMs; |
| 420 | |
| 421 | // Specify which solenoid should be pulsed |
| 422 | // The protocol supports specifying any number of solenoids to be pulsed at |
| 423 | // the same time, should that functionality be exposed to users in the future. |
| 424 | switch (index) { |
| 425 | case 0: |
| 426 | pulse.channel_0 = true; |
| 427 | break; |
| 428 | case 1: |
| 429 | pulse.channel_1 = true; |
| 430 | break; |
| 431 | case 2: |
| 432 | pulse.channel_2 = true; |
| 433 | break; |
| 434 | case 3: |
| 435 | pulse.channel_3 = true; |
| 436 | break; |
| 437 | case 4: |
| 438 | pulse.channel_4 = true; |
| 439 | break; |
| 440 | case 5: |
| 441 | pulse.channel_5 = true; |
| 442 | break; |
| 443 | case 6: |
| 444 | pulse.channel_6 = true; |
| 445 | break; |
| 446 | case 7: |
| 447 | pulse.channel_7 = true; |
| 448 | break; |
| 449 | case 8: |
| 450 | pulse.channel_8 = true; |
| 451 | break; |
| 452 | case 9: |
| 453 | pulse.channel_9 = true; |
| 454 | break; |
| 455 | case 10: |
| 456 | pulse.channel_10 = true; |
| 457 | break; |
| 458 | case 11: |
| 459 | pulse.channel_11 = true; |
| 460 | break; |
| 461 | case 12: |
| 462 | pulse.channel_12 = true; |
| 463 | break; |
| 464 | case 13: |
| 465 | pulse.channel_13 = true; |
| 466 | break; |
| 467 | case 14: |
| 468 | pulse.channel_14 = true; |
| 469 | break; |
| 470 | case 15: |
| 471 | pulse.channel_15 = true; |
| 472 | break; |
| 473 | } |
| 474 | |
| 475 | // Send pulse command |
| 476 | uint8_t packedData[PH_PULSE_ONCE_LENGTH] = {0}; |
| 477 | PH_pulse_once_pack(packedData, &pulse, PH_PULSE_ONCE_LENGTH); |
| 478 | HAL_WriteCANPacket(ph->hcan, packedData, PH_PULSE_ONCE_LENGTH, |
| 479 | PH_PULSE_ONCE_FRAME_API, status); |
| 480 | } |