PK!`ͳO sbot/C270.xml 3 3
d
2.3940919140996248e+03 0. 1.4899068228527042e+02 0. 2.4496928583865656e+03 3.8132959892100865e+02 0. 0. 1.
1 5
d
1.2485153233775250e+00 -2.4457198312117654e+01 5.2688341759851091e-02 -3.2418703425196670e-02 2.7772712238492727e+02
PK!Hm66sbot/__init__.py"""SourceBots API.""" from j5.boards.sb.arduino import AnaloguePin from j5.boards.sr.v4.power_board import PowerOutputPosition from j5.components.gpio_pin import GPIOPinMode from j5.components.motor import MotorSpecialState from j5.components.piezo import Note from .logging import logger_setup from .robot import Robot, __version__ logger_setup() COAST = MotorSpecialState.COAST BRAKE = MotorSpecialState.BRAKE __all__ = [ "__version__", "AnaloguePin", "BRAKE", "COAST", "GPIOPinMode", "Note", "PowerOutputPosition", "Robot", ] PK!ʾWWsbot/logging.py"""Logger Setup.""" import logging import sys def logger_setup() -> None: """Setup the logger.""" root = logging.getLogger() root.setLevel(logging.INFO) handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter('%(name)s - %(message)s') handler.setFormatter(formatter) root.addHandler(handler) PK!  sbot/metadata.py""" Implementation of loading metadata. Metadata is a dictionary of arbitrary information about the environment that the robot is running in. It usually includes the starting zone and a flag indicating whether we are in competition or development mode. Metadata is stored in a JSON file, typically on a competition USB stick. The environment variable SBOT_METADATA_PATH specifies a directory that is (recursively) searched for a JSON file to load. Example metadata file: { "arena": "A", "zone": 2, "is_competition": true } """ import json import logging import os from typing import Any, Dict, Optional LOGGER = logging.getLogger(__name__) METADATA_ENV_VAR = "SBOT_METADATA_PATH" class MetadataKeyError(KeyError): """Raised when trying to access a metadata key for which no value exists.""" def __init__(self, key: str): self.key = key def __str__(self) -> str: return f"Key {self.key!r} not present in metadata, or no metadata was available" def load(*, fallback: Dict[str, Any] = {}) -> Dict[str, Any]: """ Searches the path identified by METADATA_ENV_VAR for a JSON file and reads it. If no file is found, it falls back to the `fallback` dict. """ search_path = os.environ.get(METADATA_ENV_VAR) if search_path: path = _find_file(search_path) if path: LOGGER.info(f"Loading metadata from {path}") return _read_file(path) else: LOGGER.info(f"No JSON metadata files found in {search_path}") else: LOGGER.info(f"{METADATA_ENV_VAR} not set, not loading metadata") return fallback def _find_file(search_path: str) -> Optional[str]: for dir_path, dir_names, file_names in os.walk(search_path): for file_name in file_names: if file_name.endswith(".json"): return os.path.join(dir_path, file_name) return None def _read_file(path: str) -> Dict[str, Any]: with open(path) as file: try: obj = json.load(file) except json.decoder.JSONDecodeError: raise RuntimeError("Unable to decode metadata. Ask a volunteer for help.") if isinstance(obj, dict): return obj else: raise TypeError("Top-level value in metadata file must be a JSON object") PK!HYYY sbot/py.typed# Marker file for PEP 561. sbot uses inline type hints. See PEP 484 for more information.PK!  sbot/robot.py"""SourceBots Robot Definition.""" import logging import warnings from datetime import timedelta from typing import Any, Dict, Optional, TypeVar, cast # See https://github.com/j5api/j5/issues/149 import j5.backends.hardware.sb.arduino # noqa: F401 import j5.backends.hardware.sr.v4 # noqa: F401 from j5 import BaseRobot, BoardGroup from j5 import __version__ as j5_version from j5.backends import CommunicationError from j5.backends.hardware import HardwareEnvironment from j5.boards import Board from j5.boards.sb import SBArduinoBoard from j5.boards.sr.v4 import MotorBoard, PowerBoard, ServoBoard from j5.components import MarkerCamera from j5.components.piezo import Note from . import metadata from .timeout import kill_after_delay try: import j5.backends.hardware.zoloto # noqa: F401 from j5.boards.zoloto import ZolotoCameraBoard from .vision import SbotCameraBackend ENABLE_VISION = True except ImportError: warnings.warn( "Zoloto not installed, disabling vision support", category=ImportWarning, ) ENABLE_VISION = False __version__ = "0.6.2" LOGGER = logging.getLogger(__name__) GAME_LENGTH = 120 T = TypeVar("T", bound=Board) class Robot(BaseRobot): """SourceBots robot.""" def __init__( self, debug: bool = False, wait_start: bool = True, require_all_boards: bool = True, ) -> None: self._require_all_boards = require_all_boards if debug: LOGGER.setLevel(logging.DEBUG) LOGGER.info(f"SourceBots API v{__version__}") LOGGER.debug("Debug Mode is enabled") LOGGER.debug(f"j5 Version: {j5_version}") self._init_power_board() self._init_auxilliary_boards() self._log_connected_boards() default_metadata: Dict[str, Any] = { "is_competition": False, "zone": 0, } self.metadata = metadata.load(fallback=default_metadata) if wait_start: self.wait_start() def _init_power_board(self) -> None: self._power_boards = BoardGroup[PowerBoard]( HardwareEnvironment.get_backend(PowerBoard), ) self.power_board: PowerBoard = self._power_boards.singular() # Power on robot, so that we can find other boards. self.power_board.outputs.power_on() def _init_auxilliary_boards(self) -> None: self.motor_boards = BoardGroup[MotorBoard]( HardwareEnvironment.get_backend(MotorBoard), ) self.servo_boards = BoardGroup[ServoBoard]( HardwareEnvironment.get_backend(ServoBoard), ) self.arduinos = BoardGroup[SBArduinoBoard]( HardwareEnvironment.get_backend(SBArduinoBoard), ) if ENABLE_VISION: self._cameras = BoardGroup[ZolotoCameraBoard]( SbotCameraBackend, ) self._camera: Optional[ZolotoCameraBoard] = ( self._get_optional_board(self._cameras) ) def _get_optional_board(self, board_group: BoardGroup[T]) -> Optional[T]: try: return board_group.singular() except CommunicationError: if self._require_all_boards: raise else: board_name = board_group.backend_class.board.__name__ LOGGER.info(f"Did not find a {board_name} (not required)") return None def _log_connected_boards(self) -> None: for board in Board.BOARDS: LOGGER.info(f"Found {board.name}, serial: {board.serial}") LOGGER.debug(f"Firmware Version of {board.serial}: {board.firmware_version}") @property def motor_board(self) -> MotorBoard: """ Get the motor board. A CommunicationError is raised if there isn't exactly one attached. """ return self.motor_boards.singular() @property def servo_board(self) -> ServoBoard: """ Get the servo board. A CommunicationError is raised if there isn't exactly one attached. """ return self.servo_boards.singular() @property def arduino(self) -> SBArduinoBoard: """ Get the arduino. A CommunicationError is raised if there isn't exactly one attached. """ return self.arduinos.singular() @property def camera(self) -> Optional[MarkerCamera]: """Alias to the camera.""" if self._camera is None: return None else: return self._camera.camera # Metadata @property def zone(self) -> int: """The robot's starting zone in the arena (0, 1, 2 or 3).""" try: return cast(int, self.metadata["zone"]) except KeyError: raise metadata.MetadataKeyError("zone") from None @property def is_competition(self) -> bool: """Whether the robot is in a competition or development environment.""" try: return cast(bool, self.metadata["is_competition"]) except KeyError: raise metadata.MetadataKeyError("is_competition") from None # Custom functionality def wait_start(self) -> None: """Wait for the start button to be pressed.""" LOGGER.info("Waiting for start button.") self.power_board.piezo.buzz(timedelta(seconds=0.1), Note.A6) self.power_board.wait_for_start_flash() LOGGER.info("Start button pressed.") if self.is_competition: kill_after_delay(GAME_LENGTH) PK!ihhsbot/timeout.py"""Code to kill robot after certain amount of time.""" import logging from signal import SIGALRM, Signals, alarm, signal from types import FrameType LOGGER = logging.getLogger(__name__) def timeout_handler(signal_type: Signals, stack_frame: FrameType) -> None: """Handle the `SIGALRM` to kill the current process.""" raise SystemExit("Timeout expired: Game Over!") def kill_after_delay(timeout_seconds: int) -> None: """Interrupts main process after the given delay.""" LOGGER.debug(f"Kill Signal Timeout set: {timeout_seconds}s") signal(SIGALRM, timeout_handler) alarm(timeout_seconds) PK!Okksbot/vision.py"""Vision Camera definitions.""" from pathlib import Path from typing import Set from j5.backends.hardware.zoloto.camera_board import ( ZolotoCameraBoardHardwareBackend, ) from j5.boards import Board from zoloto import MarkerDict from zoloto.cameras import Camera class SbotCamera(Camera): """Camera definition for SourceBots kit.""" def __init__(self, camera_id: int): super().__init__( camera_id, marker_dict=MarkerDict.DICT_APRILTAG_36H11, calibration_file=Path(__file__).parent.joinpath('C270.xml'), ) def get_marker_size(self, marker_id: int) -> int: """Get the size of a marker, given it's ID.""" if marker_id in range(0, 40): # WALL_MARKER return 250 else: return 100 class SbotCameraBackend(ZolotoCameraBoardHardwareBackend): """Camera Backend to override the settings.""" @classmethod def discover(cls) -> Set[Board]: # type: ignore """Discover boards, overriding the parent classes method.""" return ZolotoCameraBoardHardwareBackend.discover(SbotCamera) PK!" pa--sbot-0.6.2.dist-info/LICENSEMIT License Copyright (c) 2019 Dan Trickey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HnHTUsbot-0.6.2.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H]]sbot-0.6.2.dist-info/METADATATmo6_qC>,,MtfYbLjmQ̔tP"GRq_#e 0d|^o; դpaS^c 6SmGY|m]sӥ0Wg,\&UcyIfsڦIb8l}d"~)h]>(š IT(_DwW+ hφ?~إ֊@'J8;Z i o! +~hM :=v@h|fTix]{kޔ-MzEk٫-X|RZCj^[skn=77h%CN{QixbW+a] p~<рQ0:; L76K)\fTzoЁWhs#'T#w/.v|,ԺaXfq)L.rANr\?<8xon6~Pv/JWbm 8T„:nwggB6VLt_h{|#hl$n-Abi=byz3J4*{~̏; uTp>UHTٳB-)X.}%Ϛ 'I4nKZ/| P[PK!HQ ,L7Osbot-0.6.2.dist-info/RECORDmIs@{~ 0BXK,~2X!w_0y^uEypOvN!CBĭtExQl C1=m%ű[Ah в<|hsݘT[z^K)D&,x v*eE0)n4×[ssEx4#G]ԩ! _]U3Xd-5cT j/)7NK2ITR!{S6 Tֹe8O#_)()W4I&D)zuw,6oLwټC(ou﯎f딊QQgy{&ը݅A>c JAs ܼyΖUwUk_j9h,,%\2˸ɢhRnhj'#VnZqPQIO˾V2֮rHJ4GOҞŇ. V5l:uEadf$\z%pJJO*vɩ Q画ʶP_PK!`ͳO sbot/C270.xmlPK!Hm66@sbot/__init__.pyPK!ʾWWsbot/logging.pyPK!  (sbot/metadata.pyPK!HYYY esbot/py.typedPK!  sbot/robot.pyPK!ihh%sbot/timeout.pyPK!Okku(sbot/vision.pyPK!" pa-- -sbot-0.6.2.dist-info/LICENSEPK!HnHTUs1sbot-0.6.2.dist-info/WHEELPK!H]]1sbot-0.6.2.dist-info/METADATAPK!HQ ,L7O5sbot-0.6.2.dist-info/RECORDPK 8