From 8b2ee7984b6d3da9d8470a9543f13caf1cfc6d81 Mon Sep 17 00:00:00 2001 From: Jacob Williams Date: Thu, 6 Aug 2026 18:24:49 -0400 Subject: [PATCH 1/2] Use Board API to read battery voltage Import Board and obtain the default board in Dashboard. Replace direct ADC creation on 'BOARD_VIN_MEASURE' and manual scaling with board.get_battery_voltage() to report battery voltage. Centralizes voltage measurement through the Board abstraction. --- XRPLib/dashboard.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/XRPLib/dashboard.py b/XRPLib/dashboard.py index 385e14e..e8e498a 100644 --- a/XRPLib/dashboard.py +++ b/XRPLib/dashboard.py @@ -2,6 +2,7 @@ from .rangefinder import Rangefinder from .imu import IMU from .reflectance import Reflectance +from .board import Board from .puppet import Puppet, VAR_TYPE_INT, VAR_TYPE_FLOAT, PERM_READ_ONLY from machine import Timer, ADC, Pin @@ -82,7 +83,7 @@ def __init__(self): self.imu = IMU.get_default_imu() self.rangefinder = Rangefinder.get_default_rangefinder() self.reflectance = Reflectance.get_default_reflectance() - self.VoltageADC = ADC(Pin('BOARD_VIN_MEASURE')) + self.board = Board.get_default_board() #self.CurrLADC = ADC(Pin('ML_CUR')) #self.CurrRADC = ADC(Pin('MR_CUR')) #self.Curr3ADC = ADC(Pin('M3_CUR')) @@ -188,7 +189,7 @@ def _dashboard_update(self): self._puppet.set_variable('$reflectance.right', self.reflectance.get_right()) # Voltage - voltage = self.VoltageADC.read_u16() / (1024*64/14) + voltage = self.board.get_battery_voltage() self._puppet.set_variable('$voltage', voltage) def start(self, rate_hz=3): From fefa4d96eead8d93f4fbe205495dd4cddab9c552 Mon Sep 17 00:00:00 2001 From: Jacob Williams Date: Thu, 6 Aug 2026 19:39:49 -0400 Subject: [PATCH 2/2] Route per-board logic through Board.get_type() Add Board.get_type() with Board.XRP/BETA/NANO constants as the single board-detection point, and replace the scattered 'NanoXRP'/'Beta' in implementation._machine string checks across board, differential_drive, encoded_motor, motor, imu, and encoder with comparisons against those constants. --- XRPLib/board.py | 26 +++++++++++++++++++++++--- XRPLib/differential_drive.py | 24 +++++++----------------- XRPLib/encoded_motor.py | 9 +++------ XRPLib/encoder.py | 4 ++-- XRPLib/imu.py | 5 +++-- XRPLib/motor.py | 7 ++----- 6 files changed, 40 insertions(+), 35 deletions(-) diff --git a/XRPLib/board.py b/XRPLib/board.py index 1a17ead..10233b8 100644 --- a/XRPLib/board.py +++ b/XRPLib/board.py @@ -7,6 +7,26 @@ class Board: _DEFAULT_BOARD_INSTANCE = None + # Board identity. The one place a machine name is matched; everything else compares + # against these constants (see get_type). + XRP = 0 + BETA = 1 + NANO = 2 + _type = None + + @classmethod + def get_type(cls) -> int: + """ + Identify which XRP board this code is running on. + + :return: One of Board.XRP, Board.BETA, or Board.NANO + :rtype: int + """ + if cls._type is None: + machine = sys.implementation._machine + cls._type = cls.NANO if "NanoXRP" in machine else cls.BETA if "Beta" in machine else cls.XRP + return cls._type + @classmethod def get_default_board(cls): """ @@ -47,9 +67,9 @@ def are_motors_powered(self) -> bool: :return: Returns true if the batteries are connected and powering the motors, false otherwise :rytpe: bool """ - if "NanoXRP" in sys.implementation._machine: + if Board.get_type() == Board.NANO: return True - + threshold_voltage = 4.272 return self.get_battery_voltage() > threshold_voltage @@ -141,7 +161,7 @@ def get_battery_voltage(self, vin_pin="BOARD_VIN_MEASURE") -> float: :rtype: float """ - if "NanoXRP" in sys.implementation._machine: + if Board.get_type() == Board.NANO: # VIN pin on NanoXRP is also used for RM2. self.on_switch = ADC(Pin(vin_pin)) diff --git a/XRPLib/differential_drive.py b/XRPLib/differential_drive.py index cd803e1..50e9b04 100644 --- a/XRPLib/differential_drive.py +++ b/XRPLib/differential_drive.py @@ -6,7 +6,6 @@ from .timeout import Timeout import time import math -from sys import implementation class DifferentialDrive: @@ -38,9 +37,9 @@ def __init__(self, left_motor: EncodedMotor, right_motor: EncodedMotor, imu: IMU :type rightMotor: EncodedMotor :param imu: The IMU of the robot. If None, the robot will not use the IMU for turning or maintaining heading. :type imu: IMU - :param wheelDiam: The diameter of the wheels in inches. Defaults to 6 cm. + :param wheelDiam: The diameter of the wheels in cm. :type wheelDiam: float - :param wheelTrack: The distance between the wheels in inches. Defaults to 15.5 cm. + :param wheelTrack: The distance between the wheels in cm. :type wheelTrack: float """ @@ -53,27 +52,18 @@ def __init__(self, left_motor: EncodedMotor, right_motor: EncodedMotor, imu: IMU # Resolve hardware defaults when the caller leaves the arg at its 0.0 sentinel; # honor any explicit non-zero value that is passed in. if wheel_diam == 0.0: - if "NanoXRP" in implementation._machine: - self.wheel_diam = 3.46 - else: - self.wheel_diam = 6.0 + self.wheel_diam = 3.46 if Board.get_type() == Board.NANO else 6.0 else: self.wheel_diam = wheel_diam if wheel_track == 0.0: - if "NanoXRP" in implementation._machine: - self.wheel_track = 7.8 - else: - self.wheel_track = 15.5 + self.wheel_track = 7.8 if Board.get_type() == Board.NANO else 15.5 else: self.wheel_track = wheel_track # Effort is raw PWM duty, so torque scales with pack voltage. Gains are tuned against # nominal_voltage and voltage_scale corrects the duty for the pack actually installed. - if "NanoXRP" in implementation._machine: - self.nominal_voltage = 4.2 - else: - self.nominal_voltage = 6.0 + self.nominal_voltage = 4.2 if Board.get_type() == Board.NANO else 6.0 self.voltage_scale = 1.0 self.update_voltage_compensation() @@ -220,9 +210,9 @@ def _move(self, distance_target: float, heading_target: float, max_effort: float Shared translation/rotation controller for straight() and turn(). """ - if "NanoXRP" in implementation._machine: + if Board.get_type() == Board.NANO: if min_effort is None: - min_effort = 0.10 + min_effort = 0.10 if distance_controller is None: distance_controller = PID( diff --git a/XRPLib/encoded_motor.py b/XRPLib/encoded_motor.py index da3ece7..6f42b2c 100644 --- a/XRPLib/encoded_motor.py +++ b/XRPLib/encoded_motor.py @@ -3,7 +3,7 @@ from machine import Timer, Pin from .controller import Controller from .pid import PID -from sys import implementation +from .board import Board class EncodedMotor: @@ -25,10 +25,7 @@ def get_default_encoded_motor(cls, index:int = 1): :type index: int """ - if "Beta" in implementation._machine: - MotorImplementation = SinglePWMMotor - else: - MotorImplementation = DualPWMMotor + MotorImplementation = SinglePWMMotor if Board.get_type() == Board.BETA else DualPWMMotor if index == 1: if cls._DEFAULT_LEFT_MOTOR_INSTANCE is None: @@ -70,7 +67,7 @@ def __init__(self, motor, encoder: Encoder): self.brake_at_zero = False self.target_speed = None - if "NanoXRP" in implementation._machine: + if Board.get_type() == Board.NANO: self.DEFAULT_SPEED_CONTROLLER = PID( kp=0.015, ki=0.06, diff --git a/XRPLib/encoder.py b/XRPLib/encoder.py index 8b1f6a2..4370283 100644 --- a/XRPLib/encoder.py +++ b/XRPLib/encoder.py @@ -3,11 +3,11 @@ import machine import rp2 import time -from sys import implementation +from .board import Board import re class Encoder: - if "NanoXRP" in implementation._machine: + if Board.get_type() == Board.NANO: _gear_ratio = (68/1) _counts_per_motor_shaft_revolution = 12 else: diff --git a/XRPLib/imu.py b/XRPLib/imu.py index 449db42..aec1287 100644 --- a/XRPLib/imu.py +++ b/XRPLib/imu.py @@ -11,7 +11,7 @@ # Import wrapped in a try/except so that autodoc generation can process properly pass from machine import I2C, Pin, Timer, disable_irq, enable_irq -from sys import implementation +from .board import Board import time, math class IMU(): @@ -30,7 +30,8 @@ def get_default_imu(cls): return cls._DEFAULT_IMU_INSTANCE def __init__(self, scl_pin: int|str = "I2C_SCL_1", sda_pin: int|str = "I2C_SDA_1", addr=LSM_ADDR_PRIMARY): - self._is_nanoxrp = "NanoXRP" in implementation._machine + # Cached once at construction so the hard-IRQ update handler never calls get_type(). + self._is_nanoxrp = Board.get_type() == Board.NANO # I2C values self.i2c = I2C(id=1, scl=Pin(scl_pin), sda=Pin(sda_pin), freq=400000) diff --git a/XRPLib/motor.py b/XRPLib/motor.py index b641439..8c0a870 100644 --- a/XRPLib/motor.py +++ b/XRPLib/motor.py @@ -1,5 +1,5 @@ from machine import Pin, PWM -from sys import implementation +from .board import Board class SinglePWMMotor: @@ -57,10 +57,7 @@ class DualPWMMotor: def __init__(self, in1_pwm_forward: int|str, in2_pwm_backward: int|str, flip_dir:bool=False): - if "NanoXRP" in implementation._machine: - self.flip_dir = not flip_dir - else: - self.flip_dir = flip_dir + self.flip_dir = (not flip_dir) if Board.get_type() == Board.NANO else flip_dir self._MAX_PWM = 65535 # Motor holds when actually at full power