Source code for brace.Server.Core.RobotHelpers
""" This is a separate file for helper functions that have uses in both ControlLogic level and RobotABC level which correspond to
a more specific vs global scope of parameters that would be used (e.g. RobotABC level enforces a maximum slew limit, while ControlLogic enforces a more local one
depending on state).
"""
from __future__ import annotations
from collections.abc import Callable, Iterable
import itertools
from typing import NamedTuple
import numpy as np
[docs]
def checkSlewRate(torqueNow: float, torquePast: float, deltaTime: float, maxSlew: float) -> float:
"""
Function that caps the slew rate of an output value of the current iteration
to prevent excess changes in acceleration.
:param torqueNow: The calculated output value before checks this iteration.
:type torqueNow: float
:param torquePast: The output value used last iteration.
:type torquePast: float
:param deltaTime: The time between the last iteration and the current one in seconds.
:type deltaTime: float
:param maxSlew: The maximum change between the two output values per second.
:type maxSlew: float
:return: The output value adjusted based on the max slew rate (capped if above limit).
:rtype: float
"""
#input: Nm, Nm, sec, Nm/sec
#output: Nm
with np.errstate(divide='ignore', invalid='ignore'):
try:
Slew = (torqueNow - torquePast) / deltaTime
except RuntimeWarning: # May trigger when deltatime is 0 (dividing by zero)
Slew = 0 # Just set this to 0 (may trigger at the beginning during simulation)
if abs(Slew) > maxSlew:
if Slew > 0:
TorqueIn = torquePast + maxSlew*deltaTime
else:
TorqueIn = torquePast - maxSlew*deltaTime
else:
TorqueIn = torqueNow
return TorqueIn
[docs]
def capTorqueFromAngle(torqueDes: float, currentAngle: float, extAngleLimit: float, flexAngleLimit: float) -> float:
"""
Sets torque to 0 if beyond extension and flexion angle limits.
:params torqueDes: The torque to use for this iteration cycle
:type torqueDes: float
:params currentAngle: The current angle in this iteration cycle.
:type currentAngle: float
:params extAngleLimit: The minimum angle limit before torque is set to 0.
:type extAngleLimit: float
:params flexAngleLimit: The maximum angle limit before torque is set to 0.
:type flexAngleLimit: float
:return: The adjusted torque after comparing between these limits.
:rtype: float
"""
return torqueDes if extAngleLimit < currentAngle < flexAngleLimit else 0.0
[docs]
def hysteresis2(timeData: Iterable[float], functionConditions: HysteresisParameters) -> bool:
"""
Returns whether or not a condition has been maintained for a desired
time before a state is changed. This is performed by looking back in time.
:params timeData: Time data that is stored in RobotAssemblyABC.
:type timeData: Iterable[float]
:params functionConditions: A HysteresisParameters object that defines the conditions \
collection, and amount of time the condition must be satisfied before considered True.
:type functionConditions: HysteresisParameters
:return: Whether or not the conditions have been satisifed for long enough.
:rtype: HysteresisParameters
"""
currentTime = timeData[-1]
hysteresisTime = functionConditions.hysteresisTime
i = -1
while ((i - 1) > -(len(timeData) + 1)) and currentTime - timeData[i - 1] <= hysteresisTime:
i = i - 1
measurementCollectionLength = len(functionConditions.measurementCollection)
# Empirically faster than numpy eq. (even despite vectorization? Takes half the time)
iterationSlice = itertools.islice(functionConditions.measurementCollection,
measurementCollectionLength + i, measurementCollectionLength - 1)
testCondition = [functionConditions.lambdaCondition(i) for i in iterationSlice]
return all(testCondition)
[docs]
def hysteresisMultiple(timeData: Iterable[float], functionConditions: list[HysteresisParameters]) -> bool:
"""
Checks multiple hysteresis conditions to verify all conditions are true.
:params timeData: Time data that is stored in RobotAssemblyABC
:type timeData: Iterable[float]
:params functionConditions: A list of HysteresisParameters whose conditions should be checked.
:type functionConditions: list[HysteresisParameters]
:return: Whether or not all hysteresis conditions are satisfied.
:rtype: bool
"""
hysteresisTrue = True
for hysteresisParameter in functionConditions:
hysteresisTrue = hysteresisTrue and hysteresis2(timeData, hysteresisParameter)
if not hysteresisTrue:
break
return hysteresisTrue
[docs]
class HysteresisParameters(NamedTuple):
"""
HysteresisParameters are for evaluating all the values of a certain measurement (up to a certain hysteresisTime) back in time.
In order for hysteresis to be considered true, all values have to return true when evaluated by the lambdaCondition.
A successful hysteresis signals long enough stability to switch a state.
NamedTuple that contains
lambdaCondition - a callable that contains a float and returns a bool (for evaluating across all values in measurementCollection).
measurementCollection - an iterable of floats in the measurementList that should be measured against. Values checked are paired with time iterable.
hysteresisTime - the length of how long hysteresis should be checked for previous measurement values.
"""
lambdaCondition: Callable[[float], bool]
measurementCollection: Iterable[float]
hysteresisTime: float