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 ! Hm6 6 sbot/__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 ! ʾW W sbot/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 ! HYY Y
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 ! ih h sbot/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 ! O k k sbot/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 !HnHT U sbot-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_#e0d|^o;
դpaS^c
6SmGY|m]sӥ0Wg,\&UcyIfsڦIb8l}d"~)h]>(IT(_DwW+hφ?~إ֊@'J8;Zi
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/JWbm8T:nwggB6VLt_h{|#hl$n-Abi=byz3J4*{~̏;
uTp>UHT ٳB-)X.}%Ϛ'I4nKZ/| P[PK !HQ ,L7 O sbot-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(ouf딊QQgy{&ը݅A>c
JAsܼyΖUwUk_j9h ,,%\2˸ɢhRnhj'#VnZqPQIO˾V2֮rHJ4GOҞŇ.V5l:uEadf$\z%pJJO*vɩQ画ʶP_PK ! `ͳO
sbot/C270.xmlPK ! Hm6 6 @ sbot/__init__.pyPK ! ʾW W sbot/logging.pyPK ! ( sbot/metadata.pyPK ! HYY Y
e sbot/py.typedPK !
sbot/robot.pyPK ! ih h % sbot/timeout.pyPK ! O k k u( sbot/vision.pyPK ! "pa- - - sbot-0.6.2.dist-info/LICENSEPK !HnHT U s1 sbot-0.6.2.dist-info/WHEELPK !H]] 1 sbot-0.6.2.dist-info/METADATAPK !HQ ,L7 O 5 sbot-0.6.2.dist-info/RECORDPK 8