#----------------------------------------------------------------------------
# Copyright (c) FIRST 2008-2014. 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 the root directory of
# the project.
#----------------------------------------------------------------------------
import hal
from hal import frccan
import struct
import warnings
import weakref
from .livewindowsendable import LiveWindowSendable
from .motorsafety import MotorSafety
from .resource import Resource
from .timer import Timer
from . import _canjaguar as _cj
__all__ = ["CANJaguar"]
def _packPercentage(value):
if value < -1.0:
value = -1.0
if value > 1.0:
value = 1.0
return [x for x in struct.pack("<h", int(value * 32767.0))]
def _packFXP8_8(value):
return [x for x in struct.pack("<h", int(value * 256.0))]
def _packFXP16_16(value):
return [x for x in struct.pack("<i", int(value * 65536.0))]
def _packINT16(value):
return [x for x in struct.pack("<h", int(value))]
def _packINT32(value):
return [x for x in struct.pack("<i", int(value))]
def _unpackPercentage(buffer):
return struct.unpack("<h", bytes(buffer[:2]))[0] / 32767.0
def _unpackFXP8_8(buffer):
return struct.unpack("<h", bytes(buffer[:2]))[0] / 256.0
def _unpackFXP16_16(buffer):
return struct.unpack("<i", bytes(buffer[:4]))[0] / 65536.0
def _unpackINT16(buffer):
return struct.unpack("<h", bytes(buffer[:2]))[0]
def _unpackINT32(buffer):
return struct.unpack("<i", bytes(buffer[:4]))[0]
def _FXP8_EQ(a, b):
"""Compare floats for equality as fixed point numbers"""
return int(a * 256.0) == int(b * 256.0)
def _FXP16_EQ(a, b):
"""Compare floats for equality as fixed point numbers"""
return int(a * 65536.0) == int(b * 65536.0)
def _sendMessageHelper(messageID, data, period):
if (CANJaguar.kFullMessageIDMask & messageID) in CANJaguar.kTrustedMessages:
# Make sure the data will still fit after adjusting for the token.
if data is not None and len(data) > CANJaguar.kMaxMessageDataSize - 2:
raise RuntimeError("CAN message has too much data.")
trustedData = [0, 0] # token placeholder
if data is not None:
trustedData.extend(data)
frccan.CANSessionMux_sendMessage(messageID, trustedData, period)
else:
frccan.CANSessionMux_sendMessage(messageID, data, period)
def _freeJaguar(deviceNumber, controlMode):
# Cancel periodic messages to the Jaguar, effectively disabling it.
# Disable periodic setpoints
CANJaguar.allocated.free(deviceNumber-1)
if controlMode == CANJaguar.ControlMode.PercentVbus:
messageID = deviceNumber | _cj.LM_API_VOLT_T_SET
elif controlMode == CANJaguar.ControlMode.Speed:
messageID = deviceNumber | _cj.LM_API_SPD_T_SET
elif controlMode == CANJaguar.ControlMode.Position:
messageID = deviceNumber | _cj.LM_API_POS_T_SET
elif controlMode == CANJaguar.ControlMode.Current:
messageID = deviceNumber | _cj.LM_API_ICTRL_T_SET
elif controlMode == CANJaguar.ControlMode.Voltage:
messageID = deviceNumber | _cj.LM_API_VCOMP_T_SET
else:
return
frccan.CANSessionMux_sendMessage(messageID, None,
frccan.CAN_SEND_PERIOD_STOP_REPEATING)
data = _packFXP8_8(CANJaguar.kApproxBusVoltage)
_sendMessageHelper(_cj.LM_API_CFG_MAX_VOUT | deviceNumber, data, frccan.CAN_SEND_PERIOD_NO_REPEAT)
[docs]class CANJaguar(LiveWindowSendable, MotorSafety):
"""Texas Instruments Jaguar Speed Controller as a CAN device."""
kMaxMessageDataSize = 8
# The internal PID control loop in the Jaguar runs at 1kHz.
kControllerRate = 1000
kApproxBusVoltage = 12.0
kReceiveStatusAttempts = 50
allocated = Resource(63)
kFullMessageIDMask = \
_cj.CAN_MSGID_API_M | _cj.CAN_MSGID_MFR_M | _cj.CAN_MSGID_DTYPE_M
kSendMessagePeriod = 20
kTrustedMessages = set([
_cj.LM_API_VOLT_T_EN, _cj.LM_API_VOLT_T_SET, _cj.LM_API_SPD_T_EN,
_cj.LM_API_SPD_T_SET, _cj.LM_API_VCOMP_T_EN, _cj.LM_API_VCOMP_T_SET,
_cj.LM_API_POS_T_EN, _cj.LM_API_POS_T_SET, _cj.LM_API_ICTRL_T_EN,
_cj.LM_API_ICTRL_T_SET])
[docs] class Mode:
"""Control Mode."""
#: Sets an encoder as the speed reference only.
kEncoder = 0
#: Sets a quadrature encoder as the position and speed reference.
kQuadEncoder = 1
#: Sets a potentiometer as the position reference only.
kPotentiometer = 2
[docs] class ControlMode:
"""Determines how the Jaguar is controlled, used internally."""
PercentVbus = 0
Current = 1
Speed = 2
Position = 3
Voltage = 4
kCurrentFault = 1
kTemperatureFault = 2
kBusVoltageFault = 4
kGateDriverFault = 8
# Limit switch masks
kForwardLimit = 1
kReverseLimit = 2
[docs] class NeutralMode:
"""Determines how the Jaguar behaves when sending a zero signal."""
#: Use the NeutralMode that is set by the jumper wire on the CAN device
Jumper = 0
#: Stop the motor's rotation by applying a force.
Brake = 1
#: Do not attempt to stop the motor. Instead allow it to coast
#: to a stop without applying resistance.
Coast = 2
[docs] class LimitMode:
"""Determines which sensor to use for position reference.
Limit switches will always be used to limit the rotation. This can
not be disabled.
"""
#: Disables the soft position limits and only uses
#: the limit switches to limit rotation. See :meth:`.getForwardLimitOK`
#: and :meth:`.getReverseLimitOK`.
SwitchInputsOnly = 0
#: Enables the soft position limits on the Jaguar.
#: These will be used in addition to the limit switches. This does
#: not disable the behavior of the limit switch input.
#: See :meth:`.configSoftPositionLimits`.
SoftPositionLimits = 1
def __init__(self, deviceNumber):
"""Constructor for the CANJaguar device.
By default the device is configured in Percent mode.
The control mode can be changed by calling one of the control mode
functions.
:param deviceNumber: The address of the Jaguar on the CAN bus.
"""
MotorSafety.__init__(self)
try:
CANJaguar.allocated.allocate(self, deviceNumber-1)
except IndexError as e:
raise IndexError("CANJaguar device %d in use (increment index by one)" % deviceNumber) from e
self.deviceNumber = deviceNumber
self.value = 0.0
# Parameters/configuration
self.controlMode = CANJaguar.ControlMode.PercentVbus
self.speedReference = _cj.LM_REF_NONE
self.positionReference = _cj.LM_REF_NONE
self.isInverted = False
self.p = 0.0
self.i = 0.0
self.d = 0.0
self.neutralMode = CANJaguar.NeutralMode.Jumper
self.encoderCodesPerRev = 0
self.potentiometerTurns = 0
self.limitMode = CANJaguar.LimitMode.SwitchInputsOnly
self.forwardLimit = 0.0
self.reverseLimit = 0.0
self.maxOutputVoltage = CANJaguar.kApproxBusVoltage
self.voltageRampRate = 0.0
self.faultTime = 0.0
# Which parameters have been verified since they were last set?
self.controlModeVerified = True
self.speedRefVerified = True
self.posRefVerified = True
self.pVerified = True
self.iVerified = True
self.dVerified = True
self.neutralModeVerified = True
self.encoderCodesPerRevVerified = True
self.potentiometerTurnsVerified = True
self.forwardLimitVerified = True
self.reverseLimitVerified = True
self.limitModeVerified = True
self.maxOutputVoltageVerified = True
self.voltageRampRateVerified = True
self.faultTimeVerified = True
# Status data
self.busVoltage = 0.0
self.outputVoltage = 0.0
self.outputCurrent = 0.0
self.temperature = 0.0
self.position = 0.0
self.speed = 0.0
self.limits = 0
self.faults = 0
self.firmwareVersion = 0
self.hardwareVersion = 0
# Which periodic status messages have we received at least once?
self.receivedStatusMessage0 = False
self.receivedStatusMessage1 = False
self.receivedStatusMessage2 = False
self.controlEnabled = True
receivedFirmwareVersion = False
# Request firmware and hardware version only once
self.requestMessage(frccan.CAN_IS_FRAME_REMOTE |
_cj.CAN_MSGID_API_FIRMVER)
self.requestMessage(_cj.LM_API_HWVER)
# Establish finalizer
self.__finalizer = weakref.finalize(self, _freeJaguar,
self.deviceNumber,
self.controlMode)
# Need this to free on unit test wpilib reset
Resource._add_global_resource(self)
# Wait until we've gotten all of the status data at least once.
for i in range(CANJaguar.kReceiveStatusAttempts):
Timer.delay(0.001)
self.setupPeriodicStatus()
self.updatePeriodicStatus()
if not receivedFirmwareVersion:
try:
data = self.getMessage(_cj.CAN_MSGID_API_FIRMVER,
_cj.CAN_MSGID_FULL_M)
self.firmwareVersion = _unpackINT32(data)
receivedFirmwareVersion = True
except frccan.CANMessageNotFound:
pass
if (self.receivedStatusMessage0 and
self.receivedStatusMessage1 and
self.receivedStatusMessage2 and
receivedFirmwareVersion):
break
else:
raise frccan.CANMessageNotFound("message not found")
try:
data = self.getMessage(_cj.LM_API_HWVER, _cj.CAN_MSGID_FULL_M)
self.hardwareVersion = data[0]
except frccan.CANError:
# Not all Jaguar firmware reports a hardware version.
self.hardwareVersion = 0
# 3330 was the first shipping RDK firmware version for the Jaguar
if self.firmwareVersion >= 3330 or self.firmwareVersion < 108:
from .driverstation import DriverStation
if self.firmwareVersion < 3330:
DriverStation.reportError("Jag %d firmware %d is too old (must be at least version 108 of the FIRST approved firmware)" % (self.deviceNumber, self.firmwareVersion), False)
else:
DriverStation.reportError("Jag %d firmware %d is not FIRST approved (must be at least version 108 of the FIRST approved firmware)" % (self.deviceNumber, self.firmwareVersion), False)
[docs] def free(self):
"""
Cancel periodic messages to the Jaguar, effectively disabling it.
No other methods should be called after this is called.
"""
self.__finalizer()
[docs] def getDeviceNumber(self):
""":returns: The CAN ID passed in the constructor
"""
return self.deviceNumber
[docs] def get(self):
"""Get the recently set outputValue set point.
The scale and the units depend on the mode the Jaguar is in.
- In percentVbus mode, the outputValue is from -1.0 to 1.0 (same as
PWM Jaguar).
- In voltage mode, the outputValue is in volts.
- In current mode, the outputValue is in amps.
- In speed mode, the outputValue is in rotations/minute.
- In position mode, the outputValue is in rotations.
:returns: The most recently set outputValue set point.
"""
return self.value
[docs] def getSetpoint(self):
"""
Equivalent to `get()`
"""
return self.get()
[docs] def getError(self):
"""
Get the difference between the setpoint and goal in closed loop modes.
Outside of position and velocity modes the return value of getError() has
relatively little meaning.
:returns: The difference between the setpoint and the current position.
"""
return self.get() - self.getPosition()
[docs] def set(self, outputValue, syncGroup=0):
"""Sets the output set-point value.
The scale and the units depend on the mode the Jaguar is in.
- In percentVbus Mode, the outputValue is from -1.0 to 1.0 (same as
PWM Jaguar).
- In voltage Mode, the outputValue is in volts.
- In current Mode, the outputValue is in amps.
- In speed mode, the outputValue is in rotations/minute.
- In position Mode, the outputValue is in rotations.
:param outputValue: The set-point to sent to the motor controller.
:param syncGroup: The update group to add this set() to, pending
UpdateSyncGroup(). If 0 (default), update immediately.
"""
if self.controlEnabled:
if self.controlMode == self.ControlMode.PercentVbus:
messageID = _cj.LM_API_VOLT_T_SET
data = _packPercentage(-outputValue if self.isInverted else outputValue)
elif self.controlMode == self.ControlMode.Speed:
messageID = _cj.LM_API_SPD_T_SET
data = _packFXP16_16(-outputValue if self.isInverted else outputValue)
elif self.controlMode == self.ControlMode.Position:
messageID = _cj.LM_API_POS_T_SET
data = _packFXP16_16(outputValue)
elif self.controlMode == self.ControlMode.Current:
messageID = _cj.LM_API_ICTRL_T_SET
data = _packFXP8_8(outputValue)
elif self.controlMode == self.ControlMode.Voltage:
messageID = _cj.LM_API_VCOMP_T_SET
data = _packFXP8_8(-outputValue if self.isInverted else outputValue)
else:
return
if syncGroup != 0:
data.append(syncGroup)
self.sendMessage(messageID, data, self.kSendMessagePeriod)
self.feed()
self.value = outputValue
self.verify()
[docs] def setSetpoint(self, value):
"""
Equivalent to `set()`. Implements PIDInterface.
"""
self.set(value)
[docs] def reset(self):
self.set(self.value)
self.disableControl()
[docs] def setInverted(self, isInverted):
"""
Inverts the direction of rotation of the motor.
Only works in percentVbus, Speed, and Voltage modes.
:param isInverted: The state of inversion (True is inverted).
"""
self.isInverted = isInverted
[docs] def getInverted(self):
"""
Common interface for the inverting direction of a speed controller.
:returns: The state of inversion (True is inverted).
"""
return self.isInverted
[docs] def verify(self):
"""Check all unverified params and make sure they're equal to their
local cached versions. If a value isn't available, it gets requested.
If a value doesn't match up, it gets set again.
"""
# If the Jaguar lost power, everything should be considered unverified
try:
data = self.getMessage(_cj.LM_API_STATUS_POWER,
_cj.CAN_MSGID_FULL_M)
if data[0] != 0: # power cycled
# Clear the power cycled bit
data[0] = 1
self.sendMessage(_cj.LM_API_STATUS_POWER, data[:1])
# Mark everything as unverified
self.controlModeVerified = False
self.speedRefVerified = False
self.posRefVerified = False
self.neutralModeVerified = False
self.encoderCodesPerRevVerified = False
self.potentiometerTurnsVerified = False
self.forwardLimitVerified = False
self.reverseLimitVerified = False
self.limitModeVerified = False
self.maxOutputVoltageVerified = False
self.faultTimeVerified = False
if (self.controlMode == self.ControlMode.PercentVbus or
self.controlMode == self.ControlMode.Voltage):
self.voltageRampRateVerified = False
else:
self.pVerified = False
self.iVerified = False
self.dVerified = False
# Verify periodic status messages again
self.receivedStatusMessage0 = False
self.receivedStatusMessage1 = False
self.receivedStatusMessage2 = False
# Remove any old values from netcomms. Otherwise, parameters
# are incorrectly marked as verified based on stale messages.
messages = [
_cj.LM_API_SPD_REF, _cj.LM_API_POS_REF,
_cj.LM_API_SPD_PC, _cj.LM_API_POS_PC,
_cj.LM_API_ICTRL_PC, _cj.LM_API_SPD_IC,
_cj.LM_API_POS_IC, _cj.LM_API_ICTRL_IC,
_cj.LM_API_SPD_DC, _cj.LM_API_POS_DC,
_cj.LM_API_ICTRL_DC, _cj.LM_API_CFG_ENC_LINES,
_cj.LM_API_CFG_POT_TURNS, _cj.LM_API_CFG_BRAKE_COAST,
_cj.LM_API_CFG_LIMIT_MODE, _cj.LM_API_CFG_LIMIT_REV,
_cj.LM_API_CFG_MAX_VOUT, _cj.LM_API_VOLT_SET_RAMP,
_cj.LM_API_VCOMP_COMP_RAMP, _cj.LM_API_CFG_FAULT_TIME,
_cj.LM_API_CFG_LIMIT_FWD]
for message in messages:
try:
data = self.getMessage(message, _cj.CAN_MSGID_FULL_M)
except frccan.CANMessageNotFound:
pass
except frccan.CANMessageNotFound:
self.requestMessage(_cj.LM_API_STATUS_POWER)
# Verify that any recently set parameters are correct
if not self.controlModeVerified and self.controlEnabled:
try:
data = self.getMessage(_cj.LM_API_STATUS_CMODE,
_cj.CAN_MSGID_FULL_M)
mode = data[0]
if self.controlMode == mode:
self.controlModeVerified = True
else:
# Enable control again to resend the control mode
self.enableControl()
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_STATUS_CMODE)
if not self.speedRefVerified:
try:
data = self.getMessage(_cj.LM_API_SPD_REF, _cj.CAN_MSGID_FULL_M)
speedRef = data[0]
if self.speedReference == speedRef:
self.speedRefVerified = True
else:
# It's wrong - set it again
self.setSpeedReference(self.speedReference)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_SPD_REF)
if not self.posRefVerified:
try:
data = self.getMessage(_cj.LM_API_POS_REF, _cj.CAN_MSGID_FULL_M)
posRef = data[0]
if self.positionReference == posRef:
self.posRefVerified = True
else:
# It's wrong - set it again
self.setPositionReference(self.positionReference)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_POS_REF)
if not self.pVerified:
message = 0
if self.controlMode == self.ControlMode.Speed:
message = _cj.LM_API_SPD_PC
elif self.controlMode == self.ControlMode.Position:
message = _cj.LM_API_POS_PC
elif self.controlMode == self.ControlMode.Current:
message = _cj.LM_API_ICTRL_PC
if message != 0:
try:
data = self.getMessage(message, _cj.CAN_MSGID_FULL_M)
p = _unpackFXP16_16(data)
if _FXP16_EQ(self.p, p):
self.pVerified = True
else:
# It's wrong - set it again
self.setP(self.p)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(message)
if not self.iVerified:
message = 0
if self.controlMode == self.ControlMode.Speed:
message = _cj.LM_API_SPD_IC
elif self.controlMode == self.ControlMode.Position:
message = _cj.LM_API_POS_IC
elif self.controlMode == self.ControlMode.Current:
message = _cj.LM_API_ICTRL_IC
if message != 0:
try:
data = self.getMessage(message, _cj.CAN_MSGID_FULL_M)
i = _unpackFXP16_16(data)
if _FXP16_EQ(self.i, i):
self.iVerified = True
else:
# It's wrong - set it again
self.setI(self.i)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(message)
if not self.dVerified:
message = 0
if self.controlMode == self.ControlMode.Speed:
message = _cj.LM_API_SPD_DC
elif self.controlMode == self.ControlMode.Position:
message = _cj.LM_API_POS_DC
elif self.controlMode == self.ControlMode.Current:
message = _cj.LM_API_ICTRL_DC
if message != 0:
try:
data = self.getMessage(message, _cj.CAN_MSGID_FULL_M)
d = _unpackFXP16_16(data)
if _FXP16_EQ(self.d, d):
self.dVerified = True
else:
# It's wrong - set it again
self.setD(self.d)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(message)
if not self.neutralModeVerified:
try:
data = self.getMessage(_cj.LM_API_CFG_BRAKE_COAST,
_cj.CAN_MSGID_FULL_M)
mode = data[0]
if mode == self.neutralMode:
self.neutralModeVerified = True
else:
#It's wrong - set it again
self.configNeutralMode(self.neutralMode)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_CFG_BRAKE_COAST)
if not self.encoderCodesPerRevVerified:
try:
data = self.getMessage(_cj.LM_API_CFG_ENC_LINES,
_cj.CAN_MSGID_FULL_M)
codes = _unpackINT16(data)
if codes == self.encoderCodesPerRev:
self.encoderCodesPerRevVerified = True
else:
#It's wrong - set it again
self.configEncoderCodesPerRev(self.encoderCodesPerRev)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_CFG_ENC_LINES)
if not self.potentiometerTurnsVerified:
try:
data = self.getMessage(_cj.LM_API_CFG_POT_TURNS,
_cj.CAN_MSGID_FULL_M)
turns = _unpackINT16(data)
if turns == self.potentiometerTurns:
self.potentiometerTurnsVerified = True
else:
#It's wrong - set it again
self.configPotentiometerTurns(self.potentiometerTurns)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_CFG_POT_TURNS)
if not self.limitModeVerified:
try:
data = self.getMessage(_cj.LM_API_CFG_LIMIT_MODE,
_cj.CAN_MSGID_FULL_M)
mode = data[0]
if mode == self.limitMode:
self.limitModeVerified = True
else:
#It's wrong - set it again
self.configLimitMode(self.limitMode)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_CFG_LIMIT_MODE)
if not self.forwardLimitVerified:
try:
data = self.getMessage(_cj.LM_API_CFG_LIMIT_FWD,
_cj.CAN_MSGID_FULL_M)
limit = _unpackFXP16_16(data)
if _FXP16_EQ(limit, self.forwardLimit):
self.forwardLimitVerified = True
else:
#It's wrong - set it again
self.configForwardLimit(self.forwardLimit)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_CFG_LIMIT_FWD)
if not self.reverseLimitVerified:
try:
data = self.getMessage(_cj.LM_API_CFG_LIMIT_REV,
_cj.CAN_MSGID_FULL_M)
limit = _unpackFXP16_16(data)
if _FXP16_EQ(limit, self.reverseLimit):
self.reverseLimitVerified = True
else:
#It's wrong - set it again
self.configReverseLimit(self.reverseLimit)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_CFG_LIMIT_REV)
if not self.maxOutputVoltageVerified:
try:
data = self.getMessage(_cj.LM_API_CFG_MAX_VOUT,
_cj.CAN_MSGID_FULL_M)
voltage = _unpackFXP8_8(data)
# The returned max output voltage is sometimes slightly higher
# or lower than what was sent. This should not trigger
# resending the message.
if abs(voltage - self.maxOutputVoltage) < 0.1:
self.maxOutputVoltageVerified = True
else:
# It's wrong - set it again
self.configMaxOutputVoltage(self.maxOutputVoltage)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_CFG_MAX_VOUT)
if not self.voltageRampRateVerified:
if self.controlMode == CANJaguar.ControlMode.PercentVbus:
try:
data = self.getMessage(_cj.LM_API_VOLT_SET_RAMP,
_cj.CAN_MSGID_FULL_M)
rate = _unpackPercentage(data)
if _FXP16_EQ(rate, self.voltageRampRate):
self.voltageRampRateVerified = True
else:
# It's wrong - set it again
self.setVoltageRampRate(self.voltageRampRate)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_VOLT_SET_RAMP)
elif self.controlMode == CANJaguar.ControlMode.Voltage:
try:
data = self.getMessage(_cj.LM_API_VCOMP_COMP_RAMP,
_cj.CAN_MSGID_FULL_M)
rate = _unpackFXP8_8(data)
if _FXP8_EQ(rate, self.voltageRampRate):
self.voltageRampRateVerified = True
else:
# It's wrong - set it again
self.setVoltageRampRate(self.voltageRampRate)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_VCOMP_COMP_RAMP)
if not self.faultTimeVerified:
try:
data = self.getMessage(_cj.LM_API_CFG_FAULT_TIME,
_cj.CAN_MSGID_FULL_M)
faultTime = _unpackINT16(data)
if int(self.faultTime * 1000.0) == faultTime:
self.faultTimeVerified = True
else:
#It's wrong - set it again
self.configFaultTime(self.faultTime)
except frccan.CANMessageNotFound:
# Verification is needed but not available - request it again.
self.requestMessage(_cj.LM_API_CFG_FAULT_TIME)
if (not self.receivedStatusMessage0 or
not self.receivedStatusMessage1 or
not self.receivedStatusMessage2):
# If the periodic status messages haven't been verified as
# received, request periodic status messages again and attempt
# to unpack any available ones.
self.setupPeriodicStatus()
self.getTemperature()
self.getPosition()
self.getFaults()
[docs] def disable(self):
"""Common interface for disabling a motor.
.. deprecated :: 2015
Use :func:`disableControl` instead.
"""
warnings.warn("use disableControl instead", DeprecationWarning)
self.disableControl()
# PIDOutput interface
[docs] def pidWrite(self, output):
if self.controlMode == self.ControlMode.PercentVbus:
self.set(output)
else:
raise ValueError("PID only supported in PercentVbus mode")
[docs] def setSpeedReference(self, reference):
"""Set the reference source device for speed controller mode.
Choose encoder as the source of speed feedback when in speed control
mode.
:param reference: Specify a speed reference.
"""
self.sendMessage(_cj.LM_API_SPD_REF, [reference])
self.speedReference = reference
self.speedRefVerified = False
[docs] def setPositionReference(self, reference):
"""Set the reference source device for position controller mode.
Choose between using and encoder and using a potentiometer
as the source of position feedback when in position control mode.
:param reference: Specify a position reference.
"""
self.sendMessage(_cj.LM_API_POS_REF, [reference])
self.positionReference = reference
self.posRefVerified = False
[docs] def setP(self, p):
"""Set the P constant for the closed loop modes.
:param p: The proportional gain of the Jaguar's PID controller.
"""
data = _packFXP16_16(p)
if self.controlMode == self.ControlMode.Speed:
self.sendMessage(_cj.LM_API_SPD_PC, data)
elif self.controlMode == self.ControlMode.Position:
self.sendMessage(_cj.LM_API_POS_PC, data)
elif self.controlMode == self.ControlMode.Current:
self.sendMessage(_cj.LM_API_ICTRL_PC, data)
else:
raise ValueError("PID constants only apply in Speed, Position, and Current mode")
self.p = p
self.pVerified = False
[docs] def setI(self, i):
"""Set the I constant for the closed loop modes.
:param i: The integral gain of the Jaguar's PID controller.
"""
data = _packFXP16_16(i)
if self.controlMode == self.ControlMode.Speed:
self.sendMessage(_cj.LM_API_SPD_IC, data)
elif self.controlMode == self.ControlMode.Position:
self.sendMessage(_cj.LM_API_POS_IC, data)
elif self.controlMode == self.ControlMode.Current:
self.sendMessage(_cj.LM_API_ICTRL_IC, data)
else:
raise ValueError("PID constants only apply in Speed, Position, and Current mode")
self.i = i
self.iVerified = False
[docs] def setD(self, d):
"""Set the D constant for the closed loop modes.
:param d: The derivative gain of the Jaguar's PID controller.
"""
data = _packFXP16_16(d)
if self.controlMode == self.ControlMode.Speed:
self.sendMessage(_cj.LM_API_SPD_DC, data)
elif self.controlMode == self.ControlMode.Position:
self.sendMessage(_cj.LM_API_POS_DC, data)
elif self.controlMode == self.ControlMode.Current:
self.sendMessage(_cj.LM_API_ICTRL_DC, data)
else:
raise ValueError("PID constants only apply in Speed, Position, and Current mode")
self.d = d
self.dVerified = False
[docs] def setPID(self, p, i, d):
"""Set the P, I, and D constants for the closed loop modes.
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.setP(p)
self.setI(i)
self.setD(d)
[docs] def getP(self):
"""Get the Proportional gain of the controller.
:returns: The proportional gain.
"""
if (self.controlMode == self.ControlMode.PercentVbus or
self.controlMode == self.ControlMode.Voltage):
raise ValueError("PID does not apply in Percent or Voltage control modes")
return self.p
[docs] def getI(self):
"""Get the Integral gain of the controller.
:returns: The integral gain.
"""
if (self.controlMode == self.ControlMode.PercentVbus or
self.controlMode == self.ControlMode.Voltage):
raise ValueError("PID does not apply in Percent or Voltage control modes")
return self.i
[docs] def getD(self):
"""Get the Derivative gain of the controller.
:returns: The derivative gain.
"""
if (self.controlMode == self.ControlMode.PercentVbus or
self.controlMode == self.ControlMode.Voltage):
raise ValueError("PID does not apply in Percent or Voltage control modes")
return self.d
[docs] def enableControl(self, encoderInitialPosition=0.0):
"""Enable the closed loop controller.
Start actually controlling the output based on the feedback.
If starting a position controller with an encoder reference,
use the encoderInitialPosition parameter to initialize the
encoder state.
:param encoderInitialPosition: Encoder position to set if position
with encoder reference (default of 0.0). Ignored otherwise.
"""
if self.controlMode == self.ControlMode.PercentVbus:
self.sendMessage(_cj.LM_API_VOLT_T_EN, None)
elif self.controlMode == self.ControlMode.Speed:
self.sendMessage(_cj.LM_API_SPD_T_EN, None)
elif self.controlMode == self.ControlMode.Position:
data = _packFXP16_16(encoderInitialPosition)
self.sendMessage(_cj.LM_API_POS_T_EN, data)
elif self.controlMode == self.ControlMode.Current:
self.sendMessage(_cj.LM_API_ICTRL_T_EN, None)
elif self.controlMode == self.ControlMode.Voltage:
self.sendMessage(_cj.LM_API_VCOMP_T_EN, None)
self.controlEnabled = True
[docs] def isEnabled(self):
"""
Return whether the controller is enabled.
:returns: True if enabled
"""
return self.controlEnabled
[docs] def disableControl(self):
"""Disable the closed loop controller.
Stop driving the output based on the feedback.
"""
# Disable all control modes.
self.sendMessage(_cj.LM_API_VOLT_DIS, None)
self.sendMessage(_cj.LM_API_SPD_DIS, None)
self.sendMessage(_cj.LM_API_POS_DIS, None)
self.sendMessage(_cj.LM_API_ICTRL_DIS, None)
self.sendMessage(_cj.LM_API_VCOMP_DIS, None)
# Stop all periodic setpoints
self.sendMessage(_cj.LM_API_VOLT_T_SET, None,
frccan.CAN_SEND_PERIOD_STOP_REPEATING)
self.sendMessage(_cj.LM_API_SPD_T_SET, None,
frccan.CAN_SEND_PERIOD_STOP_REPEATING)
self.sendMessage(_cj.LM_API_POS_T_SET, None,
frccan.CAN_SEND_PERIOD_STOP_REPEATING)
self.sendMessage(_cj.LM_API_ICTRL_T_SET, None,
frccan.CAN_SEND_PERIOD_STOP_REPEATING)
self.sendMessage(_cj.LM_API_VCOMP_T_SET, None,
frccan.CAN_SEND_PERIOD_STOP_REPEATING)
self.controlEnabled = False
[docs] def setPercentMode(self):
"""Enable controlling the motor voltage as a percentage of the bus
voltage without any position or speed feedback.
After calling this you must call :func:`enableControl` to enable
the device.
"""
self.changeControlMode(self.ControlMode.PercentVbus)
self.setPositionReference(_cj.LM_REF_NONE)
self.setSpeedReference(_cj.LM_REF_NONE)
[docs] def setPercentModeEncoder(self, codesPerRev):
"""Enable controlling the motor voltage as a percentage of the bus
voltage, and enable speed sensing from a non-quadrature encoder.
After calling this you must call :func:`enableControl` to enable
the device.
:param codesPerRev: The counts per revolution on the encoder
"""
self.changeControlMode(self.ControlMode.PercentVbus)
self.setPositionReference(_cj.LM_REF_NONE)
self.setSpeedReference(_cj.LM_REF_ENCODER)
self.configEncoderCodesPerRev(codesPerRev)
[docs] def setPercentModeQuadEncoder(self, codesPerRev):
"""Enable controlling the motor voltage as a percentage of the bus
voltage, and enable position and speed sensing from a quadrature
encoder.
After calling this you must call :func:`enableControl` to enable
the device.
:param tag: The constant {@link CANJaguar#kQuadEncoder}
:param codesPerRev: The counts per revolution on the encoder
"""
self.changeControlMode(self.ControlMode.PercentVbus)
self.setPositionReference(_cj.LM_REF_ENCODER)
self.setSpeedReference(_cj.LM_REF_QUAD_ENCODER)
self.configEncoderCodesPerRev(codesPerRev)
[docs] def setPercentModePotentiometer(self):
"""Enable controlling the motor voltage as a percentage of the bus
voltage, and enable position sensing from a potentiometer and no
speed feedback.
After calling this you must call :func:`enableControl` to enable
the device.
:param tag: The constant {@link CANJaguar#kPotentiometer}
"""
self.changeControlMode(self.ControlMode.PercentVbus)
self.setPositionReference(_cj.LM_REF_POT)
self.setSpeedReference(_cj.LM_REF_NONE)
self.configPotentiometerTurns(1)
[docs] def setCurrentModePID(self, p, i, d):
"""Enable controlling the motor current with a PID loop.
After calling this you must call :func:`enableControl` to enable
the device.
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.changeControlMode(self.ControlMode.Current)
self.setPositionReference(_cj.LM_REF_NONE)
self.setSpeedReference(_cj.LM_REF_NONE)
self.setPID(p, i, d)
[docs] def setCurrentModeEncoder(self, codesPerRev, p, i, d):
"""Enable controlling the motor current with a PID loop, and enable
speed sensing from a non-quadrature encoder.
After calling this you must call :func:`enableControl` to enable
the device.
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.changeControlMode(self.ControlMode.Current)
self.setPositionReference(_cj.LM_REF_NONE)
self.setSpeedReference(_cj.LM_REF_NONE)
self.configEncoderCodesPerRev(codesPerRev)
self.setPID(p, i, d)
[docs] def setCurrentModeQuadEncoder(self, codesPerRev, p, i, d):
"""Enable controlling the motor current with a PID loop, and enable
speed and position sensing from a quadrature encoder.
After calling this you must call :func:`enableControl` to enable
the device.
:param codesPerRev: The counts per revolution on the encoder
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.changeControlMode(self.ControlMode.Current)
self.setPositionReference(_cj.LM_REF_ENCODER)
self.setSpeedReference(_cj.LM_REF_QUAD_ENCODER)
self.configEncoderCodesPerRev(codesPerRev)
self.setPID(p, i, d)
[docs] def setCurrentModePotentiometer(self, p, i, d):
"""Enable controlling the motor current with a PID loop, and enable
position sensing from a potentiometer.
After calling this you must call :func:`enableControl` to enable
the device.
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.changeControlMode(self.ControlMode.Current)
self.setPositionReference(_cj.LM_REF_POT)
self.setSpeedReference(_cj.LM_REF_NONE)
self.configPotentiometerTurns(1)
self.setPID(p, i, d)
[docs] def setSpeedModeEncoder(self, codesPerRev, p, i, d):
"""Enable controlling the speed with a feedback loop from a
non-quadrature encoder.
After calling this you must call :func:`enableControl` to enable
the device.
:param codesPerRev: The counts per revolution on the encoder
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.changeControlMode(self.ControlMode.Speed)
self.setPositionReference(_cj.LM_REF_NONE)
self.setSpeedReference(_cj.LM_REF_ENCODER)
self.configEncoderCodesPerRev(codesPerRev)
self.setPID(p, i, d)
[docs] def setSpeedModeQuadEncoder(self, codesPerRev, p, i, d):
"""Enable controlling the speed with a feedback loop from a
quadrature encoder.
After calling this you must call :func:`enableControl` to enable
the device.
:param codesPerRev: The counts per revolution on the encoder
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.changeControlMode(self.ControlMode.Speed)
self.setPositionReference(_cj.LM_REF_ENCODER)
self.setSpeedReference(_cj.LM_REF_QUAD_ENCODER)
self.configEncoderCodesPerRev(codesPerRev)
self.setPID(p, i, d)
[docs] def setPositionModeQuadEncoder(self, codesPerRev, p, i, d):
"""Enable controlling the position with a feedback loop using an
encoder.
After calling this you must call :func:`enableControl` to enable
the device.
:param codesPerRev: The counts per revolution on the encoder
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.changeControlMode(self.ControlMode.Position)
self.setPositionReference(_cj.LM_REF_ENCODER)
self.configEncoderCodesPerRev(codesPerRev)
self.setPID(p, i, d)
[docs] def setPositionModePotentiometer(self, p, i, d):
"""Enable controlling the position with a feedback loop using a
potentiometer.
After calling this you must call :func:`enableControl` to enable
the device.
:param p: The proportional gain of the Jaguar's PID controller.
:param i: The integral gain of the Jaguar's PID controller.
:param d: The differential gain of the Jaguar's PID controller.
"""
self.changeControlMode(self.ControlMode.Position)
self.setPositionReference(_cj.LM_REF_POT)
self.configPotentiometerTurns(1)
self.setPID(p, i, d)
[docs] def setVoltageMode(self):
"""Enable controlling the motor voltage without any position or speed
feedback.
After calling this you must call :func:`enableControl` to enable
the device.
"""
self.changeControlMode(self.ControlMode.Voltage)
self.setPositionReference(_cj.LM_REF_NONE)
self.setSpeedReference(_cj.LM_REF_NONE)
[docs] def setVoltageModeEncoder(self, codesPerRev):
"""Enable controlling the motor voltage with speed feedback from a
non-quadrature encoder and no position feedback.<br>
After calling this you must call :func:`enableControl` to enable
the device.
:param codesPerRev: The counts per revolution on the encoder
"""
self.changeControlMode(self.ControlMode.Voltage)
self.setPositionReference(_cj.LM_REF_NONE)
self.setSpeedReference(_cj.LM_REF_ENCODER)
self.configEncoderCodesPerRev(codesPerRev)
[docs] def setVoltageModeQuadEncoder(self, codesPerRev):
"""Enable controlling the motor voltage with position and speed
feedback from a quadrature encoder.
After calling this you must call :func:`enableControl` to enable
the device.
:param tag: The constant {@link CANJaguar#kQuadEncoder}
:param codesPerRev: The counts per revolution on the encoder
"""
self.changeControlMode(self.ControlMode.Voltage)
self.setPositionReference(_cj.LM_REF_ENCODER)
self.setSpeedReference(_cj.LM_REF_QUAD_ENCODER)
self.configEncoderCodesPerRev(codesPerRev)
[docs] def setVoltageModePotentiometer(self):
"""Enable controlling the motor voltage with position feedback from a
potentiometer and no speed feedback.
After calling this you must call :func:`enableControl` to enable
the device.
"""
self.changeControlMode(self.ControlMode.Voltage)
self.setPositionReference(_cj.LM_REF_POT)
self.setSpeedReference(_cj.LM_REF_NONE)
self.configPotentiometerTurns(1)
[docs] def changeControlMode(self, controlMode):
"""Used internally. In order to set the control mode see the methods
listed below.
Change the control mode of this Jaguar object.
After changing modes, configure any PID constants or other settings
needed and then EnableControl() to actually change the mode on the
Jaguar.
:param controlMode: The new mode.
"""
# Disable the previous mode
self.disableControl()
# Update the local mode
self.controlMode = controlMode
self.controlModeVerified = False
# Update the finalizer
self.__finalizer.detach()
self.__finalizer = weakref.finalize(self, _freeJaguar,
self.deviceNumber,
self.controlMode)
[docs] def getControlMode(self):
"""Get the active control mode from the Jaguar.
Ask the Jaguar what mode it is in.
:return ControlMode: that the Jag is in.
"""
return self.controlMode
[docs] def getBusVoltage(self):
"""Get the voltage at the battery input terminals of the Jaguar.
:returns: The bus voltage in Volts.
"""
self.updatePeriodicStatus()
return self.busVoltage
[docs] def getOutputVoltage(self):
"""Get the voltage being output from the motor terminals of the Jaguar.
:returns: The output voltage in Volts.
"""
self.updatePeriodicStatus()
return self.outputVoltage
[docs] def getOutputCurrent(self):
"""Get the current through the motor terminals of the Jaguar.
:returns: The output current in Amps.
"""
self.updatePeriodicStatus()
return self.outputCurrent
[docs] def getTemperature(self):
"""Get the internal temperature of the Jaguar.
:returns: The temperature of the Jaguar in degrees Celsius.
"""
self.updatePeriodicStatus()
return self.temperature
[docs] def getPosition(self):
"""Get the position of the encoder or potentiometer.
:returns: The position of the motor in rotations based on the
configured feedback. See :func:`configPotentiometerTurns` and
:func:`configEncoderCodesPerRev`.
"""
self.updatePeriodicStatus()
return self.position
[docs] def getSpeed(self):
"""Get the speed of the encoder.
:returns: The speed of the motor in RPM based on the configured
feedback.
"""
self.updatePeriodicStatus()
return self.speed
[docs] def getForwardLimitOK(self):
"""Get the status of the forward limit switch.
:returns: True if the motor is allowed to turn in the forward direction.
"""
self.updatePeriodicStatus()
return (self.limits & self.kForwardLimit) != 0
[docs] def getReverseLimitOK(self):
"""Get the status of the reverse limit switch.
:returns: True if the motor is allowed to turn in the reverse direction.
"""
self.updatePeriodicStatus()
return (self.limits & self.kReverseLimit) != 0
[docs] def getFaults(self):
"""Get the status of any faults the Jaguar has detected.
:returns: A bit-mask of faults defined by the "Faults" constants.
- `kCurrentFault`
- `kBusVoltageFault`
- `kTemperatureFault`
- `GateDriverFault`
"""
self.updatePeriodicStatus()
return self.faults
[docs] def setVoltageRampRate(self, rampRate):
"""Set the maximum voltage change rate.
When in PercentVbus or Voltage output mode, the rate at which the
voltage changes can be limited to reduce current spikes. set this
to 0.0 to disable rate limiting.
:param rampRate: The maximum rate of voltage change in Percent
Voltage mode in V/s.
"""
if self.controlMode == self.ControlMode.PercentVbus:
data = _packPercentage(rampRate / (self.maxOutputVoltage *
self.kControllerRate))
message = _cj.LM_API_VOLT_SET_RAMP
elif self.controlMode == self.ControlMode.Voltage:
data = _packFXP8_8(rampRate / self.kControllerRate)
message = _cj.LM_API_VCOMP_COMP_RAMP
else:
raise ValueError("Voltage ramp rate only applies in Percentage and Voltage modes")
self.sendMessage(message, data)
[docs] def getFirmwareVersion(self):
"""Get the version of the firmware running on the Jaguar.
:returns: The firmware version. 0 if the device did not respond.
"""
return self.firmwareVersion
[docs] def getHardwareVersion(self):
"""Get the version of the Jaguar hardware.
:returns: The hardware version. 1: Jaguar, 2: Black Jaguar
"""
return self.hardwareVersion
[docs] def configNeutralMode(self, mode):
"""Configure what the controller does to the H-Bridge when neutral
(not driving the output).
This allows you to override the jumper configuration for brake or coast.
:param mode: Select to use the jumper setting or to override it to
coast or brake (see `NeutralMode`).
"""
self.sendMessage(_cj.LM_API_CFG_BRAKE_COAST, [mode])
self.neutralMode = mode
self.neutralModeVerified = False
[docs] def configEncoderCodesPerRev(self, codesPerRev):
"""Configure how many codes per revolution are generated by your
encoder.
:param codesPerRev: The number of counts per revolution in 1X mode.
"""
data = _packINT16(codesPerRev)
self.sendMessage(_cj.LM_API_CFG_ENC_LINES, data)
self.encoderCodesPerRev = codesPerRev
self.encoderCodesPerRevVerified = False
[docs] def configPotentiometerTurns(self, turns):
"""Configure the number of turns on the potentiometer.
There is no special support for continuous turn potentiometers.
Only integer numbers of turns are supported.
:param turns: The number of turns of the potentiometer
"""
data = _packINT16(turns)
self.sendMessage(_cj.LM_API_CFG_POT_TURNS, data)
self.potentiometerTurns = turns
self.potentiometerTurnsVerified = False
[docs] def configSoftPositionLimits(self, forwardLimitPosition,
reverseLimitPosition):
"""Configure Soft Position Limits when in Position Controller mode.
When controlling position, you can add additional limits on top of
the limit switch inputs that are based on the position feedback.
If the position limit is reached or the switch is opened, that
direction will be disabled.
:param forwardLimitPosition: The position that, if exceeded, will
disable the forward direction.
:param reverseLimitPosition: The position that, if exceeded, will
disable the reverse direction.
"""
self.configLimitMode(self.LimitMode.SoftPositionLimits)
self.configForwardLimit(forwardLimitPosition)
self.configReverseLimit(reverseLimitPosition)
[docs] def disableSoftPositionLimits(self):
"""Disable Soft Position Limits if previously enabled.<br>
Soft Position Limits are disabled by default.
"""
self.configLimitMode(self.LimitMode.SwitchInputsOnly)
[docs] def configLimitMode(self, mode):
"""Set the limit mode for position control mode.<br>
Use :func:`configSoftPositionLimits` or
:func:`disableSoftPositionLimits` to set this automatically.
:param mode: The `LimitMode` to use to limit the rotation of the device.
"""
self.sendMessage(_cj.LM_API_CFG_LIMIT_MODE, [mode])
[docs] def configForwardLimit(self, forwardLimitPosition):
"""Set the position that, if exceeded, will disable the forward
direction.
Use :func:`configSoftPositionLimits` to set this and the
`LimitMode` automatically.
:param forwardLimitPosition: The position that, if exceeded, will
disable the forward direction.
"""
data = _packFXP16_16(forwardLimitPosition)
data.append(1)
self.sendMessage(_cj.LM_API_CFG_LIMIT_FWD, data)
self.forwardLimit = forwardLimitPosition
self.forwardLimitVerified = False
[docs] def configReverseLimit(self, reverseLimitPosition):
"""Set the position that, if exceeded, will disable the reverse
direction.
Use :func:`configSoftPositionLimits` to set this and the
`LimitMode` automatically.
:param reverseLimitPosition: The position that, if exceeded, will
disable the reverse direction.
"""
data = _packFXP16_16(reverseLimitPosition)
data.append(1)
self.sendMessage(_cj.LM_API_CFG_LIMIT_REV, data)
self.reverseLimit = reverseLimitPosition
self.reverseLimitVerified = False
[docs] def configMaxOutputVoltage(self, voltage):
"""Configure the maximum voltage that the Jaguar will ever output.
This can be used to limit the maximum output voltage in all modes so
that motors which cannot withstand full bus voltage can be used safely.
:param voltage: The maximum voltage output by the Jaguar.
"""
data = _packFXP8_8(voltage)
self.sendMessage(_cj.LM_API_CFG_MAX_VOUT, data)
self.maxOutputVoltage = voltage
self.maxOutputVoltageVerified = False
[docs] def configFaultTime(self, faultTime):
"""Configure how long the Jaguar waits in the case of a fault before
resuming operation.
Faults include over temerature, over current, and bus under voltage.
The default is 3.0 seconds, but can be reduced to as low as 0.5
seconds.
:param faultTime: The time to wait before resuming operation, in
seconds.
"""
if faultTime < 0.5:
faultTime = 0.5
elif faultTime > 3.0:
faultTime = 3.0
data = _packINT16(int(faultTime * 1000.0))
self.sendMessage(_cj.LM_API_CFG_FAULT_TIME, data)
self.faultTime = faultTime
self.faultTimeVerified = False
[docs] def sendMessage(self, messageID, data,
period=frccan.CAN_SEND_PERIOD_NO_REPEAT):
"""Send a message to the Jaguar.
:param messageID: The messageID to be used on the CAN bus (device
number is added internally)
:param data: The up to 8 bytes of data to be sent with the message
:param period: If positive, tell Network Communications to send the
message every "period" milliseconds.
"""
_sendMessageHelper(messageID | self.deviceNumber, data, period)
[docs] def requestMessage(self, messageID,
period=frccan.CAN_SEND_PERIOD_NO_REPEAT):
"""Request a message from the Jaguar, but don't wait for it to arrive.
:param messageID: The message to request
:param periodic: If positive, tell Network Communications to request
the message every "period" milliseconds.
"""
_sendMessageHelper(messageID | self.deviceNumber, None, period)
[docs] def getMessage(self, messageID, messageMask):
"""Get a previously requested message.
Jaguar always generates a message with the same message ID when
replying.
:param messageID: The messageID to read from the CAN bus (device
number is added internally)
:returns: The up to 8 bytes of data that was received with the message
"""
messageID |= self.deviceNumber
messageID &= _cj.CAN_MSGID_FULL_M
# Get the data.
messageID, data, timeStamp = \
frccan.CANSessionMux_receiveMessage(messageID, messageMask)
return data
[docs] def setupPeriodicStatus(self):
"""Enables periodic status updates from the Jaguar
"""
# Message 0 returns bus voltage, output voltage, output current, and
# temperature.
kMessage0Data = [
_cj.LM_PSTAT_VOLTBUS_B0, _cj.LM_PSTAT_VOLTBUS_B1,
_cj.LM_PSTAT_VOLTOUT_B0, _cj.LM_PSTAT_VOLTOUT_B1,
_cj.LM_PSTAT_CURRENT_B0, _cj.LM_PSTAT_CURRENT_B1,
_cj.LM_PSTAT_TEMP_B0, _cj.LM_PSTAT_TEMP_B1]
# Message 1 returns position and speed
kMessage1Data = [
_cj.LM_PSTAT_POS_B0, _cj.LM_PSTAT_POS_B1,
_cj.LM_PSTAT_POS_B2, _cj.LM_PSTAT_POS_B3,
_cj.LM_PSTAT_SPD_B0, _cj.LM_PSTAT_SPD_B1,
_cj.LM_PSTAT_SPD_B2, _cj.LM_PSTAT_SPD_B3]
# Message 2 returns limits and faults
kMessage2Data = [
_cj.LM_PSTAT_LIMIT_CLR,
_cj.LM_PSTAT_FAULT,
_cj.LM_PSTAT_END,
0, 0, 0, 0, 0]
data = _packINT16(int(self.kSendMessagePeriod))
self.sendMessage(_cj.LM_API_PSTAT_PER_EN_S0, data)
self.sendMessage(_cj.LM_API_PSTAT_PER_EN_S1, data)
self.sendMessage(_cj.LM_API_PSTAT_PER_EN_S2, data)
self.sendMessage(_cj.LM_API_PSTAT_CFG_S0, kMessage0Data)
self.sendMessage(_cj.LM_API_PSTAT_CFG_S1, kMessage1Data)
self.sendMessage(_cj.LM_API_PSTAT_CFG_S2, kMessage2Data)
[docs] def updatePeriodicStatus(self):
"""Check for new periodic status updates and unpack them into local
variables.
"""
# Check if a new bus voltage/output voltage/current/temperature message
# has arrived and unpack the values into the cached member variables
try:
data = self.getMessage(_cj.LM_API_PSTAT_DATA_S0,
_cj.CAN_MSGID_FULL_M)
self.busVoltage = _unpackFXP8_8(data[0:2])
self.outputVoltage = _unpackPercentage(data[2:4]) * self.busVoltage
self.outputCurrent = _unpackFXP8_8(data[4:6])
self.temperature = _unpackFXP8_8(data[6:8])
self.receivedStatusMessage0 = True
except frccan.CANMessageNotFound:
pass
# Check if a new position/speed message has arrived and do the same
try:
data = self.getMessage(_cj.LM_API_PSTAT_DATA_S1,
_cj.CAN_MSGID_FULL_M)
self.position = _unpackFXP16_16(data[0:4])
self.speed = _unpackFXP16_16(data[4:8])
self.receivedStatusMessage1 = True
except frccan.CANMessageNotFound:
pass
# Check if a new limits/faults message has arrived and do the same
try:
data = self.getMessage(_cj.LM_API_PSTAT_DATA_S2,
_cj.CAN_MSGID_FULL_M)
self.limits = data[0]
self.faults = data[1]
self.receivedStatusMessage2 = True
except frccan.CANMessageNotFound:
pass
@staticmethod
[docs] def updateSyncGroup(syncGroup):
"""Update all the motors that have pending sets in the syncGroup.
:param syncGroup: A bitmask of groups to generate synchronous output.
"""
_sendMessageHelper(_cj.CAN_MSGID_API_SYNC, [syncGroup],
frccan.CAN_SEND_PERIOD_NO_REPEAT)
[docs] def getDescription(self):
return "CANJaguar ID %d" % self.deviceNumber
[docs] def getDeviceID(self):
return self.deviceNumber
[docs] def stopMotor(self):
"""Common interface for stopping a motor.
"""
self.disableControl()
# Live Window code, only does anything if live window is activated.
def getSmartDashboardType(self):
return "Speed Controller"
def updateTable(self):
table = self.getTable()
if table is not None:
table.putNumber("Value", self.get())
def valueChanged(self, itable, key, value, bln):
self.set(float(value))
def startLiveWindowMode(self):
self.set(0) # Stop for safety
super(CANJaguar, self).startLiveWindowMode()
def stopLiveWindowMode(self):
super(CANJaguar, self).stopLiveWindowMode()
self.set(0) # Stop for safety