diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f4a94..5b7b312 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [4.0.1] - 2025-06-16 + +### Fixed + +- Fix UART passthrough ([`1346356`](https://github.com/fossasia/pslab-python/commit/1346356d3cf95691ea0e3e87be335659d5513ec8)) (Anashuman Singh) + ## [4.0.0] - 2025-02-19 ### Changed @@ -41,6 +47,7 @@ _Changelog added in following release._ +[4.0.1]: https://github.com/fossasia/pslab-python/releases/tag/4.0.1 [4.0.0]: https://github.com/fossasia/pslab-python/releases/tag/4.0.0 [3.1.1]: https://github.com/fossasia/pslab-python/releases/tag/3.1.1 [3.1.0]: https://github.com/fossasia/pslab-python/releases/tag/3.1.0 diff --git a/pslab/__init__.py b/pslab/__init__.py index 174ca77..fde015c 100644 --- a/pslab/__init__.py +++ b/pslab/__init__.py @@ -17,4 +17,4 @@ "ScienceLab", ) -__version__ = "4.0.0" +__version__ = "4.0.1" diff --git a/pslab/external/motor.py b/pslab/external/motor.py index 71cff0c..1fba065 100644 --- a/pslab/external/motor.py +++ b/pslab/external/motor.py @@ -7,9 +7,14 @@ >>> servo.angle = 30 # Turn motor to 30 degrees position. """ +import time +from typing import List from typing import Union +import csv +import os from pslab.instrument.waveform_generator import PWMGenerator +from datetime import datetime MICROSECONDS = 1e6 @@ -70,3 +75,95 @@ def _get_duty_cycle(self, angle): angle *= self._max_angle_pulse - self._min_angle_pulse # Scale angle += self._min_angle_pulse # Offset return angle / (self._frequency**-1 * MICROSECONDS) + + +class RoboticArm: + """Robotic arm controller for up to 4 servos.""" + + MAX_SERVOS = 4 + + def __init__(self, servos: List[Servo]) -> None: + if len(servos) > RoboticArm.MAX_SERVOS: + raise ValueError( + f"At most {RoboticArm.MAX_SERVOS} servos can be used, got {len(servos)}" + ) + self.servos = servos + + def run_schedule(self, timeline: List[List[int]], time_step: float = 1.0) -> None: + """Run a time-based schedule to move servos. + + Parameters + ---------- + timeline : List[List[int]] + A list of timesteps,where each sublist represents one timestep, + with angles corresponding to each servo. + + time_step : float, optional + Delay in seconds between each timestep. Default is 1.0. + """ + if len(timeline[0]) != len(self.servos): + raise ValueError("Each timestep must specify an angle for every servo") + + tl_len = len(timeline[0]) + if not all(len(tl) == tl_len for tl in timeline): + raise ValueError("All timeline entries must have the same length") + + for tl in timeline: + for i, s in enumerate(self.servos): + if tl[i] is not None: + s.angle = tl[i] + time.sleep(time_step) + + def import_timeline_from_csv(self, filepath: str) -> List[List[int]]: + """Import timeline from a CSV file. + + Parameters + ---------- + filepath : str + Absolute or relative path to the CSV file to be read. + + Returns + ------- + List[List[int]] + A timeline consisting of servo angle values per timestep. + """ + timeline = [] + + with open(filepath, mode="r", newline="") as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + angles = [] + for key in ["Servo1", "Servo2", "Servo3", "Servo4"]: + value = row[key] + if value == "null": + angles.append(None) + else: + angles.append(int(value)) + timeline.append(angles) + + return timeline + + def export_timeline_to_csv( + self, timeline: List[List[Union[int, None]]], folderpath: str + ) -> None: + """Export timeline to a CSV file. + + Parameters + ---------- + timeline : List[List[Union[int, None]]] + A list of timesteps where each sublist contains servo angles. + + folderpath : str + Directory path where the CSV file will be saved. The filename + will include a timestamp to ensure uniqueness. + """ + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + filename = f"Robotic_Arm{timestamp}.csv" + filepath = os.path.join(folderpath, filename) + + with open(filepath, mode="w", newline="") as csvfile: + writer = csv.writer(csvfile) + writer.writerow(["Timestep", "Servo1", "Servo2", "Servo3", "Servo4"]) + for i, row in enumerate(timeline): + pos = ["null" if val is None else val for val in row] + writer.writerow([i] + pos) diff --git a/pslab/sciencelab.py b/pslab/sciencelab.py index bf4b16f..1cbc81e 100644 --- a/pslab/sciencelab.py +++ b/pslab/sciencelab.py @@ -36,6 +36,7 @@ class ScienceLab: def __init__(self, device: ConnectionHandler | None = None): self.device = device if device is not None else autoconnect() + self.firmware = self.device.get_firmware_version() self.logic_analyzer = LogicAnalyzer(device=self.device) self.oscilloscope = Oscilloscope(device=self.device) self.waveform_generator = WaveformGenerator(device=self.device) @@ -293,7 +294,7 @@ def _uart_passthrough(self, baudrate: int) -> None: self.device.send_byte(CP.PASSTHROUGHS) self.device.send_byte(CP.PASS_UART) self.device.send_int(self._get_brgval(baudrate)) - self.device.interface.baudrate = baudrate + self.device.baudrate = baudrate def _uart_passthrough_legacy(self, baudrate: int) -> None: self.device.send_byte(CP.PASSTHROUGHS_LEGACY) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy