Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ConnectionHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import threading

from pslab import ScienceLab
from pslab.external.motor import Servo
from pslab.instrument.logic_analyzer import LogicAnalyzer
from pslab.instrument.multimeter import Multimeter
from pslab.instrument.oscilloscope import Oscilloscope
Expand Down Expand Up @@ -51,3 +52,10 @@ def getLogicAnalyzer(self) -> LogicAnalyzer:

def getMultimeter(self) -> Multimeter:
return self.__getScienceLab().multimeter

def getServos(self, MinAnglePulse: int, MaxAnglePulse: int, AngleRange: int, Frequency: int) -> [Servo]:
pwm_generator = self.getPWMGenerator()
return [Servo("SQ1", pwm_generator, MinAnglePulse, MaxAnglePulse, AngleRange, Frequency),
Servo("SQ2", pwm_generator, MinAnglePulse, MaxAnglePulse, AngleRange, Frequency),
Servo("SQ3", pwm_generator, MinAnglePulse, MaxAnglePulse, AngleRange, Frequency),
Servo("SQ4", pwm_generator, MinAnglePulse, MaxAnglePulse, AngleRange, Frequency)]
79 changes: 79 additions & 0 deletions Servos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Instrument wrapper for PSLab python API
"""

from time import sleep

from enum import Enum

from OpenTap import Display
from OpenTap import Unit
from opentap import *

from .ConnectionHandler import ConnectionHandler

SquareWavePin = Enum('SquareWavePin', ['SQ1', 'SQ2', 'SQ3', 'SQ4'])

@attribute(Display("Servo", "Servo Instrument", "PSLab"))
class Servos(Instrument):
min_angle_pulse = property(float, 500)\
.add_attribute(Unit("ms"))\
.add_attribute(Display("Min. Angle Pulse", "Pulse length corresponding to the minimum (usually 0 degree) angle of the servo.", "", -50))

max_angle_pulse = property(float, 2500)\
.add_attribute(Unit("ms"))\
.add_attribute(Display("Max. Angle Pulse", "Pulse length corresponding to the maximum (usually 180 degree) angle of the servo.", "", -40))

angle_range = property(float, 180)\
.add_attribute(Unit("°"))\
.add_attribute(Display("Angle Range", "Range of the servo.", "", -30))

frequency = property(float, 50)\
.add_attribute(Unit("Hz"))\
.add_attribute(Display("Frequency", "Frequency of the control signal.", "", -20))

def __init__(self):
"""Set up the properties, methods and default values of the instrument."""
super(Servos, self).__init__() # The base class initializer must be invoked.
self.instrument = None
self.Name = "Servo"
self.Rules.Add(Rule("min_angle_pulse", lambda: self.min_angle_pulse > 0, lambda: 'Angle pulse must be positive.'))
self.Rules.Add(Rule("max_angle_pulse", lambda: self.max_angle_pulse > 0, lambda: 'Angle pulse must be positive.'))
self.Rules.Add(Rule("angle_range", lambda: self.angle_range > 0, lambda: 'Angle range must be positive.'))
self.Rules.Add(Rule("angle_range", lambda: self.angle_range <= 360, lambda: 'Angle range must not exceed 360°.'))
self.Rules.Add(Rule("frequency", lambda: self.frequency > 0, lambda: 'Frequency must be positive.'))

def Open(self):
super(Servos, self).Open()
# Open COM connection to instrument using ConnectionHandler
self.instrument = ConnectionHandler.instance().getServos(self.min_angle_pulse, self.max_angle_pulse, self.angle_range, self.frequency)
"""Called by TAP when the test plan starts"""

def Close(self):
"""Called by TAP when the test plan ends."""
super(Servos, self).Close()

def get_min_angle(self):
return 0

def get_max_angle(self):
return self.angle_range

def _get_servo(self, pin: SquareWavePin):
match pin:
case SquareWavePin.SQ1:
return self.instrument[0]
case SquareWavePin.SQ2:
return self.instrument[1]
case SquareWavePin.SQ3:
return self.instrument[2]
case SquareWavePin.SQ4:
return self.instrument[3]
case _:
raise Exception("Unsupported pin " + pin)

def set_angle(self, pin: SquareWavePin, angle: int):
""" Wait a few milliseconds to allow the command to be processed between steps.
Otherwise, movements could be omitted if the commands are sent too quickly one after the other. """
sleep(20/1000)
self._get_servo(pin).angle = angle
37 changes: 37 additions & 0 deletions SetServoAngleStep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Test step to set the angle of a connected servo on a SQ1-4 pin
"""

from OpenTap import Display, Unit, Verdict
from System import Double
from opentap import *

from .Servos import *


@attribute(Display("Set Servo Angle", "Sets angle of a connected servo", Groups=["PSLab", "Servo"]))
class SetServoAngleStep(TestStep):
# Properties
pin = property(SquareWavePin, SquareWavePin.SQ1) \
.add_attribute(Display("Pin", "Pin on which the square wave is generated", "", -50))

angle = property(float, 0) \
.add_attribute(Display("Angle", "The angle to be set on a chosen SQ pin", "", -40)) \
.add_attribute(Unit("°"))

Servos = property(Servos, None) \
.add_attribute(Display("Servo", "", "Resources", 0))

def __init__(self):
super(SetServoAngleStep, self).__init__()

self.Rules.Add(
Rule("angle", lambda: self.angle >= self.Servos.get_min_angle(),
lambda: f'Angle must be at least {self.Servos.get_min_angle()}°.'))
self.Rules.Add(
Rule("angle", lambda: self.angle <= self.Servos.get_max_angle(),
lambda: f'Angle must not exceed {self.Servos.get_max_angle()}°.'))

def Run(self):
self.Servos.set_angle(self.pin, self.angle)
self.UpgradeVerdict(Verdict.Pass)