===== martypy.Marty ===== Python library for communicating with Marty the Robot V1 and V2 by Robotical. **Reference version: MartyPy 3.8.0**, checked against the published package on 7 October 2026. Signatures below match this version; where an annotation differs from the actual return value, the entry explains that difference. [[https://pypi.org/project/martypy/3.8.0/|MartyPy on PyPI]] · [[https://github.com/robotical/martypy/tree/850f7c3f52c93853b711d17011bb24c4e31686c1|Source for this release]] Install or update in the Python environment that will run your program: python -m pip install --upgrade martypy Getting started on a computer (replace the example IP address with Marty's actual address): from martypy import Marty my_marty = Marty("wifi", "192.168.0.53") try: my_marty.get_ready() my_marty.dance() finally: my_marty.close() The tags 1️⃣ and 2️⃣ indicate Marty V1 and Marty V2 support. Features also depend on the connected hardware, add-ons and firmware. Methods without a robot-version tag, such as translation and colour helpers, may operate on the computer rather than send a robot command. **Desktop Python and Blocks** This is the reference for the installed MartyPy package. Native Marty V2 connections use WiFi, USB or the expansion port; 'socket' is the legacy Marty V1 connection. A native MartyPy 'bluetooth' connection method is not provided by this release. In Blocks, Python runs in the browser and supported Marty calls use the app's existing connection to the robot, including Bluetooth. The app supplies my_marty; do not copy desktop connection/installation code into its editor. Browser Python supports a selected set of Marty-style APIs, not every native MartyPy function. Browser speech uses browser audio support and does not require installing FFmpeg on your computer. **Speech and translation requirements** The speech and translation APIs use Robotical's online service, so internet access is needed. Text passed to these functions is sent to that service. Desktop speech needs FFmpeg; speak_on_computer also needs an audio playback backend such as ffplay. Translation and get_language do not require FFmpeg. Default speech settings are ALTO/FEMALE, English and speed 1, and persist on the Marty object until changed. **Waiting for sensor conditions** The foot_on_ground and foot_obstacle_sensed methods return Python Booleans: use True/False, not the strings 'true'/'false'. Sensor getters normally read the latest received telemetry rather than performing a fresh blocking measurement. Missing or stale telemetry can produce empty/zero readings; a missing foot sensor can raise MartyCommandException. Put subsequent movement after the wait loop, not inside it. Include a short sleep and a timeout so the loop does not spin indefinitely: import time from martypy import Marty my_marty = Marty("wifi", "192.168.0.53") try: my_marty.get_ready() deadline = time.monotonic() + 15 while not my_marty.foot_obstacle_sensed("right"): if time.monotonic() >= deadline: raise TimeoutError("No obstacle detected within 15 seconds") time.sleep(0.05) my_marty.kick("right") finally: my_marty.close() Support Marty safely with clear space around it when testing movements. Check Boolean command results and catch/report exceptions where appropriate: a generated command or accepted request is not by itself proof that the robot completed the movement. ===== Marty Objects ===== Call methods on your connected Marty instance, for example my_marty.walk(num_steps=10). In signatures, Optional means a value may be None, and Union means more than one input type is accepted. Marty.Disco group constants are written as Disco in the signatures. ===== Connections and blocking ===== ==== %%__%%init%%__%% ==== __init__(method: str, locator: str = "", extra_client_types: dict = dict(), blocking: Union[bool, None] = None, *args, **kwargs) -> None Start a connection to Marty 1️⃣ 2️⃣ For example: * ''%%Marty("wifi", "192.168.0.53")%%'' to connect to Marty via WiFi on IP Address 192.168.0.53 * ''%%Marty("usb", "COM2")%%'' on a Windows computer with Marty connected by USB cable to COM2 * ''%%Marty("usb", "/dev/tty.SLAB_USBtoUART")%%'' on a Mac computer with Marty connected by USB cable to /dev/tty.SLAB_USBtoUART * ''%%Marty("exp", "/dev/ttyAMA0")%%'' on a Raspberry Pi computer with Marty connected by expansion cable to /dev/ttyAMA0 **Blocking Mode** Movement commands that expose a blocking argument (e.g. ''%%walk()%%'', ''%%dance()%%'', ''%%move_joint()%%'', but also ''%%hold_position()%%'') comes with two modes - blocking and non-blocking. Issuing a command in blocking mode will make your program pause until Marty physically stops moving. Only then the next line of your code will be executed. In non-blocking mode, each movement command simply tells Marty what to do and returns immediately, meaning that your code will continue to execute while Marty is moving. Movement methods whose signatures include blocking take an optional ''%%blocking%%'' argument that can be used to choose the mode for that call. If you plan to use the same mode all or most of the time, you can use the ''%%Marty.set_blocking()%%'' method or use the ''%%blocking%%'' constructor argument. The latter defaults to ''%%True%%'' (blocking) if not provided. **Arguments**: * ''%%method%%'' - method of connecting to Marty - it may be: "usb", "wifi", "socket" (Marty V1) or "exp" (expansion port used to connect to a Raspberry Pi, etc) * ''%%locator%%'' - location to connect to, depending on the method of connection this is the serial port name, network (IP) Address or network name (hostname) of Marty that the computer should use to communicate with Marty. If ''%%method%%'' is ''%%"usb"%%'' and there is only a single Marty connected, ''%%locator%%'' can be left out and the Marty will be found automatically. If multiple Martys are detected, one of them will be chosen arbitrarily and connected to. * ''%%blocking%%'' - Default movement command mode for this ''%%Marty%%'' instance. * ''%%True%%'' (default): blocking mode * ''%%False%%'': non-blocking mode **Raises**: * MartyConfigException if the parameters are invalid * MartyConnectException if Marty couldn't be contacted ==== is_conn_ready ==== is_conn_ready() -> bool Check if the robot is connected and the connection is ready to accept commands 2️⃣ **Arguments**: None **Returns**: True if the robot is connected and ready ==== is_blocking ==== is_blocking() -> bool Check the default movement command behaviour of this Marty. 1️⃣ 2️⃣ **Returns**: ''%%True%%'' if movement commands block by default ==== set_blocking ==== set_blocking(blocking: bool) Change whether movement commands default to blocking or non-blocking behaviour for this Marty. 1️⃣ 2️⃣ The blocking behaviour can also be specified on a per-command basis using the ''%%blocking=%%'' argument which takes precedence over Marty's overall setting. **Arguments**: * ''%%blocking%%'' - whether or not to block by default ==== close ==== close() -> None Close the connection to Marty. Call this explicitly in a finally block when a desktop program ends. Closing the connection is not a replacement for intentionally stopping movement with stop. ===== Movement ===== ==== get_ready ==== get_ready(blocking: Optional[bool] = None) -> bool Move Marty to the normal standing position and wiggle eyebrows 1️⃣ 2️⃣ Will also enable motors for Marty v1 1️⃣ **Arguments**: * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. **Returns**: True if Marty accepted the request ==== stand_straight ==== stand_straight(move_time: int = 2000, blocking: Optional[bool] = None) -> bool Move Marty to the normal standing position 1️⃣ 2️⃣ **Arguments**: * ''%%move_time%%'' - How long (in milliseconds) Marty will take to reach the normal standing position. (Higher number means slower movement.) * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== walk ==== walk(num_steps: int = 2, start_foot: str = 'auto', turn: int = 0, step_length: int = 25, move_time: int = 1500, blocking: Optional[bool] = None) -> bool Make Marty walk 1️⃣ 2️⃣ **Arguments**: * ''%%num_steps%%'' - how many steps to take * ''%%start_foot%%'' - 'left', 'right' or 'auto', start walking with this foot Note: 2️⃣ Unless you specify 'auto', all steps are taken with the same foot so it only makes sense to use the start_foot argument with ''%%num_steps=1%%''. * ''%%turn%%'' - How much to turn (-100 to 100 in degrees), 0 is straight. * ''%%step_length%%'' - How far to step (approximately in mm) * ''%%move_time%%'' - duration of each step in milliseconds (not the total duration for all steps) * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== sidestep ==== sidestep(side: str, steps: int = 1, step_length: int = 35, move_time: int = 1000, blocking: Optional[bool] = None) -> bool Take sidesteps 1️⃣ 2️⃣ **Arguments**: * ''%%side%%'' - 'left' or 'right', direction to step in * ''%%steps%%'' - number of steps to take * ''%%step_length%%'' - how broad the steps are (up to 127) * ''%%move_time%%'' - duration of each sidestep in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== lift_foot ==== lift_foot(side: str) -> bool Lift a foot. 2️⃣ * ''side'' - 'left' or 'right'. Returns True if Marty accepts the request. This method has no blocking argument and does not wait for the movement to finish. ==== lower_foot ==== lower_foot(side: str) -> bool Lower a foot. 2️⃣ * ''side'' - 'left' or 'right'. Returns True if Marty accepts the request. This method has no blocking argument and does not wait for the movement to finish. ==== wave ==== wave(side: str) -> bool Wave an arm. 2️⃣ * ''side'' - 'left' or 'right'. The argument is required on Marty (for example, my_marty.wave("right")). Returns True if Marty accepts the request. This method has no blocking argument and does not wait for the movement to finish. ==== dance ==== dance(side: str = 'right', move_time: int = 3000, blocking: Optional[bool] = None) -> bool Boogie, Marty! 1️⃣ 2️⃣ **Arguments**: * ''%%side%%'' - 'left' or 'right', which side to start on * ''%%move_time%%'' - how long this movement should last, in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== celebrate ==== celebrate(move_time: int = 4000, blocking: Optional[bool] = None) -> bool Celebrate. 1️⃣ 2️⃣ On Marty V2 this currently uses the wiggle trajectory. **Arguments**: * ''%%move_time%%'' - how long this movement should last, in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== wiggle ==== wiggle(move_time: int = 4000, blocking: Optional[bool] = None) -> bool Wiggle. 1️⃣ 2️⃣ **Arguments**: * ''%%move_time%%'' - how long this movement should last, in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== circle_dance ==== circle_dance(side: str = 'right', move_time: int = 2500, blocking: Optional[bool] = None) -> bool Circle dance. 1️⃣ 2️⃣ **Arguments**: * ''%%side%%'' - 'left' or 'right', which side to start on * ''%%move_time%%'' - how long this movement should last, in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== eyes ==== eyes(pose_or_angle: Union[str, int], move_time: int = 1000, blocking: Optional[bool] = None) -> bool Move the eyes to a pose or an angle 1️⃣ 2️⃣ **Arguments**: * ''%%pose_or_angle%%'' - 'angry', 'excited', 'normal', 'wide', or 'wiggle' 2️⃣ - alternatively this can be an angle in degrees (which can be a negative number) * ''%%move_time%%'' - how long this movement should last, in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== kick ==== kick(side: str = 'right', twist: int = 0, move_time: int = 2500, blocking: Optional[bool] = None) -> bool Kick one of Marty's feet 1️⃣ 2️⃣ **Arguments**: * ''%%side%%'' - 'left' or 'right', which foot to use * ''%%twist%%'' - the amount of twisting do do while kicking (in degrees) * ''%%move_time%%'' - how long this movement should last, in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== arms ==== arms(left_angle: int, right_angle: int, move_time: int, blocking: Optional[bool] = None) -> bool Move both of Marty's arms to angles you specify 1️⃣ 2️⃣ **Arguments**: * ''%%left_angle%%'' - Angle of the left arm (degrees -100 to 100) * ''%%right_angle%%'' - Position of the right arm (degrees -100 to 100) * ''%%move_time%%'' - how long this movement should last, in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== lean ==== lean(direction: str, amount: Optional[int] = None, move_time: int = 1000, blocking: Optional[bool] = None) -> bool Lean over in a direction 1️⃣ 2️⃣ **Arguments**: * ''%%direction%%'' - ''%%'left'%%'', ''%%'right'%%'', ''%%'forward'%%'', or ''%%'back'%%'' * ''%%amount%%'' - How much to lean. The defaults and the exact meaning is different between Marty V1 and V2: * 1️⃣ If not specified or ''%%None%%'', ''%%amount%%'' defaults to ''%%50%%'' (no specific unit). Limit is -60/60 in either direction. * 2️⃣ If not specified or ''%%None%%'', ''%%amount%%'' defaults to ''%%29%%'' degrees. Limit for foward and back is 45 and limit for left and right is 60. * ''%%move_time%%'' - How long this movement should last, in milliseconds. * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request ==== move_joint ==== move_joint(joint_name_or_num: Union[int, str], position: int, move_time: int, blocking: Optional[bool] = None) -> bool Move a specific joint to a position 1️⃣ 2️⃣ **Arguments**: * ''%%joint_name_or_num%%'' - joint to move, see the Marty.JOINT_IDS dictionary (can be name or number) * ''%%position%%'' - angle in degrees * ''%%move_time%%'' - how long this movement should last, in milliseconds * ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. **Returns**: True if Marty accepted the request **Raises**: MartyCommandException if the joint_name_or_num is unknown ==== hold_position ==== hold_position(hold_time: int, blocking: Optional[bool] = None) -> bool Hold Marty at its current position 2️⃣ **Arguments**: hold_time, time to hold position in milli-seconds - ''%%blocking%%'' - Blocking mode override; whether to wait for physical movement to finish before returning. Defaults to the value returned by ''%%self.is_blocking()%%''. Holding position counts as movement because Marty is using its motors to actively resist any attempts to move its joints. **Returns**: True if Marty accepted the request ==== is_moving ==== is_moving() -> bool Check if Marty is moving 2️⃣ **Arguments**: none **Returns**: True if Marty is moving ==== is_paused ==== is_paused() -> bool Check if Marty is paused 2️⃣ **Returns**: True if Marty is paused ==== stop ==== stop(stop_type: Optional[str] = None) -> bool Stop Marty's movement 1️⃣ 2️⃣ You can also control what way to "stop" you want with the parameter stop_type. For instance: * 'clear queue' to finish the current movement before stopping (clear any queued movements) * 'clear and stop' stop immediately (and clear queues) * 'clear and disable' 1️⃣ stop and disable the robot * 'clear and zero' stop and move back to get_ready pose * 'pause' pause motion * 'pause and disable' 1️⃣ pause motion and disable the robot **Arguments**: * ''%%stop_type%%'' - the way to stop - see the options above **Raises**: MartyCommandException if the stop_type is unknown With stop_type omitted, this uses 'clear and stop'. Pass the string name, not a numeric stop code. Returns True if the stop request succeeds. ==== resume ==== resume() -> bool Resume Marty's movement after a pause 2️⃣ **Returns**: True if Marty accepted the request ===== Sound, speech and translation ===== ==== set_volume ==== set_volume(volume: int) -> bool Set Marty's speaker volume. 2️⃣ * ''volume'' - from 0 (silent) to 100 (maximum). Returns True if Marty accepts the request. This does not change the computer speaker volume. ==== get_volume ==== get_volume() -> int Read Marty's speaker volume (0 to 100). 2️⃣ ==== play_sound ==== play_sound(name_or_freq_start: Union[str,int], freq_end: Optional[int] = None, duration: Optional[int] = None) -> bool Play a named sound (Marty V2 2️⃣) or make a tone (Marty V1 1️⃣) **Arguments**: * ''%%name_or_freq_start%%'' - name of the sound, e.g. 'excited' or 'no_way' 2️⃣ * ''%%name_or_freq_start%%'' - starting frequency, min 20, max 20000, Hz 1️⃣ * ''%%freq_end%%'' - ending frequency, min 20, max 20000, Hz 1️⃣ * ''%%duration%%'' - milliseconds, maximum 5000 1️⃣ **Returns**: True if Marty accepted the request ==== play_mp3 ==== play_mp3(filename: str, progress_callback: Callable[[int, int], bool] = None) -> bool Stream an MP3 file from the computer to Marty's speaker. 2️⃣ * ''filename'' - local path to the MP3 file on the computer. * ''progress_callback'' - optional function called with (bytes_sent, total_bytes). Return True to continue streaming or False to abort. Returns True if streaming succeeds. This method has no blocking argument; it waits for the streaming operation, not a separate movement-completion wait. File, connection and streaming failures may return False or raise exceptions. ==== set_voice ==== set_voice(voice: str) Set the default voice for subsequent calls to speak and speak_on_computer. Available from MartyPy 3.8.0. **Arguments**: * ''voice'' - a voice name (case-insensitive). Common choices are FEMALE, MALE, KITTEN, ALIEN, GIANT, BOLT, STARLIGHT and WHIRLWIND. FEMALE is an alias for ALTO; MALE is an alias for TENOR; SQUEAK and CHIPMUNK are aliases for KITTEN. Other supported names are ALTO, TENOR, THUNDER, STONE, RUMBLE, ECHO, DRIFT, BREEZE, WAVE, BLAZE, MIST, DAWN, CRYSTAL, LULLABY, AURORA, RADIANCE and FLASH. KITTEN produces meows rather than spoken words. The initial voice is ALTO (FEMALE). Returns None. An unknown voice raises ValueError. ==== set_voice_speed ==== set_voice_speed(speed: float) Set the default speed for subsequent speech. Available from MartyPy 3.8.0. **Arguments**: * ''speed'' - a number from 0.1 to 2 inclusive. 1 is normal speed; a smaller number is slower and a larger number is faster. Returns None. Invalid or non-finite speeds raise ValueError. Desktop MartyPy changes playback rate, which can also change pitch. ==== set_speech_language ==== set_speech_language(language: str) Set the language/accent used for subsequent speech synthesis. Available from MartyPy 3.8.0. This does not translate the words. **Arguments**: * ''language'' - a supported code such as ''en'' or ''fr'', or a recognised language name such as ''French''. The initial language is English (en). Supported speech codes are ar, zh-cn, da, nl, en, fr, de, hi, is, it, ja, ko, nb, pl, pt-br, pt, ro, ru, es, es-419, sv, tr and cy. Returns None. An unsupported speech language raises ValueError. Translation supports additional languages; a language accepted by translate is not necessarily supported for speech. ==== speak ==== speak(words: str = 'hello', voice: Optional[str] = None, blocking: Optional[bool] = False, language: Optional[str] = None, speed: Optional[float] = None) -> bool Speak through Marty's speaker. 2️⃣ **Arguments**: * ''words'' - text to say. The online synthesis request uses at most the first 128 characters (after any KITTEN voice substitution). * ''voice'' - voice name, or None to use the setting from set_voice. * ''blocking'' - True requests waiting for speech to finish. The default is False: unlike movement commands, this does not inherit set_blocking. Passing None also does not request waiting in MartyPy 3.8.0. * ''language'' - supported speech language/accent, or None to use set_speech_language. * ''speed'' - speech speed from 0.1 to 2, or None to use set_voice_speed. **Returns**: True if the audio stream was accepted; False if streaming failed. Explicit voice, language and speed values apply to this call without changing the saved defaults. Language and speed overrides and saved speech settings are available from MartyPy 3.8.0. Desktop MartyPy requires internet access and FFmpeg. Audio is generated using Robotical's online service, processed on the computer and streamed to Marty. Network, synthesis, audio-processing and connection errors may raise exceptions. my_marty.set_voice("FEMALE") my_marty.set_voice_speed(1.2) my_marty.speak("Hello from Marty", blocking=True) ==== speak_on_computer ==== speak_on_computer(words: str = 'hello', voice: Optional[str] = None, language: Optional[str] = None, speed: Optional[float] = None) -> bool Speak through the computer's speakers, not Marty's. Available from MartyPy 3.8.0. **Arguments**: * ''words'' - text to say (the same synthesis limit as speak). * ''voice'', ''language'', ''speed'' - optional overrides with the same meanings as speak. None uses the saved speech defaults. **Returns**: True after computer playback finishes. This call waits and has no blocking argument. It does not send an audio command to Marty. Desktop use requires internet, FFmpeg and a supported playback backend such as ffplay. Invalid settings or network/audio failures can raise exceptions. In Blocks, browser audio is used instead and FFmpeg is not required. ==== translate ==== translate(words: str, language: str) -> str Translate text using Robotical's online service. Available from MartyPy 3.8.0. Internet access is required; no robot command is sent. **Arguments**: * ''words'' - text to translate. * ''language'' - target language code, such as ''fr'' (French), ''es'' (Spanish) or ''el'' (Greek). Recognised language names can also be used. Translation supports more languages than speech synthesis. **Returns**: The translated text as a string. Empty text and text consisting only of digits are returned unchanged. This function does not speak, change the speech accent or change the selected speech language. Malformed service responses raise ValueError; network and HTTP failures raise exceptions rather than being reported as successful translations. my_marty.set_speech_language("fr") translated = my_marty.translate("Hello", "fr") my_marty.speak(translated, blocking=True) ==== get_language ==== get_language() -> str Return the computer's configured locale language. Available from MartyPy 3.8.0. For a recognised locale, the result is a language name such as English or French; otherwise it is the normalised locale code. If the locale is unavailable, English is used. This does not read a language setting from Marty and does not report the current speech language. In Blocks, the corresponding function reports the editor language instead of the desktop Python locale. ===== Sensors and joint readings ===== ==== get_accelerometer ==== get_accelerometer(axis: Optional[str] = None) -> float Read the latest accelerometer telemetry. 1️⃣ 2️⃣ * ''axis'' - 'x', 'y' or 'z'. On Marty V2, omit it to read all three axes. On Marty V1, an axis is required. **Actual return value**: With an axis, returns that acceleration reading. On V2 without an axis, returns a three-value sequence [x, y, z] (a list for normal telemetry, or (0, 0, 0) before any telemetry arrives), despite the float annotation. Individual-axis reads return 0 when data is unavailable or stale. An unknown axis raises MartyCommandException. ==== get_distance_sensor ==== get_distance_sensor() -> Union[int, float] Get the latest value from the distance sensor 1️⃣ 2️⃣ **Returns**: The distance sensor reading. The meaning of the returned value is different between Marty V1 and V2: * 1️⃣ Returns a raw distance sensor reading as a ''%%float%%''. * 2️⃣ Returns the distance in millimeters as ''%%int%%''. Both will return 0 if no distance sensor is found. ==== foot_on_ground ==== foot_on_ground(add_on_or_side: str) -> bool Checks whether the foot is on a surface 2️⃣ **Arguments**: * ''%%add_on_or_side%%'' - Takes in the name of a color sensor, name of an IR sensor, ''%%'left'%%'' for the add on connected to the left foot, or ''%%'right'%%'' for the add on connected to the right foot. **Returns**: A boolean for whether the addon detects the ground. ''%%True%%'' for ground detected, ''%%False%%'' otherwise. Returns a Python Boolean, not a 'true'/'false' string. ==== foot_obstacle_sensed ==== foot_obstacle_sensed(add_on_or_side: str) -> bool Checks whether there is an obstacle in front of the foot 2️⃣ **Arguments**: * ''%%add_on_or_side%%'' - Takes in the name of a color sensor, name of an IR sensor, ''%%'left'%%'' for the add on connected to the left foot, or ''%%'right'%%'' for the add on connected to the right foot. **Returns**: A boolean for whether the addon detects an obstacle. ''%%True%%'' for obstacle detected, ''%%False%%'' otherwise. This returns a Python Boolean, not the text 'true'/'false'. Use the value directly in conditions. ==== get_obstacle_sensor_reading ==== get_obstacle_sensor_reading(add_on_or_side: str) -> int Gets a raw obstacle sensor reading from an IR or color sensor 2️⃣ **Arguments**: * ''%%add_on_or_side%%'' - Takes in the name of a color sensor, name of an IR sensor, ''%%'left'%%'' for the add on connected to the left foot, or ''%%'right'%%'' for the add on connected to the right foot. **Returns**: Raw reading of obstacle sensor data from the add on. ==== get_ground_sensor_reading ==== get_ground_sensor_reading(add_on_or_side: str) -> int Gets a raw ground sensor reading from an IR or color sensor 2️⃣ **Arguments**: * ''%%add_on_or_side%%'' - Takes in the name of a color sensor, name of an IR sensor, ''%%'left'%%'' for the add on connected to the left foot, or ''%%'right'%%'' for the add on connected to the right foot. **Returns**: Raw reading of ground sensor data from the add on. ==== get_color_sensor_color ==== get_color_sensor_color(add_on_or_side: str) -> int Read the colour classification from a colour sensor. 2️⃣ * ''add_on_or_side'' - a colour sensor's name, or 'left'/'right' for the corresponding foot sensor. **Actual return value**: A string: yellow, green, blue, purple, red, air or unclear. 'air' means the sensor does not detect the ground; 'unclear' means the colour cannot be classified. If there is no current sensor data, 0 can be returned. A missing or invalid sensor can raise MartyCommandException. The published 3.8.0 annotation says int, but a successful colour classification is a string, not an integer. ==== get_color_sensor_hex ==== get_color_sensor_hex(add_on_or_side: str) -> str Read a colour sensor's RGB channels as a hexadecimal colour string, without a leading #. 2️⃣ * ''add_on_or_side'' - a colour sensor name, or 'left'/'right'. If there is no current sensor data, 0 can be returned despite the str annotation. A missing or invalid sensor can raise MartyCommandException. ==== get_color_sensor_value_by_channel ==== get_color_sensor_value_by_channel(add_on_or_side: str, channel: str) -> int Read a raw channel from a colour sensor. 2️⃣ * ''add_on_or_side'' - a colour sensor name, or 'left'/'right'. * ''channel'' - 'clear', 'red', 'green' or 'blue' (case-insensitive). Returns the channel reading, or 0 if no current data is available. Invalid channels or missing/invalid sensors can raise MartyCommandException. ==== get_joint_position ==== get_joint_position(joint_name_or_num: Union[int, str]) -> float Get the position (angle in degrees) of a joint 2️⃣ **Arguments**: * ''%%joint_name_or_num%%'' - see the Marty.JOINT_IDS dictionary (can be name or number) **Returns**: Angle of the joint in degrees **Raises**: MartyCommandException if the joint_name_or_num is unknown ==== get_joint_current ==== get_joint_current(joint_name_or_num: Union[int, str]) -> float Get the current (in milli-Amps) of a joint 1️⃣ 2️⃣ This can be useful in detecting when the joint is working hard and is related to the force which the joint's motor is exerting to stay where it is **Arguments**: * ''%%joint_name_or_num%%'' - see the Marty.JOINT_IDS dictionary (can be name or number) **Returns**: the current of the joint in milli-Amps (this will be 0 if the joint current is unknown) **Raises**: MartyCommandException if the joint_name_or_num is unknown ==== get_joint_status ==== get_joint_status(joint_name_or_num: Union[int, str]) -> int Get information about a joint 2️⃣ This can be helpful to find out if the joint is working correctly and if it is moving at the moment, etc **Arguments**: * ''%%joint_name_or_num%%'' - see the Marty.JOINT_IDS dictionary (can be name or number) **Returns**: a code number which is the sum of codes in the Marty.JOINT_STATUS dictionary will be 0 if the joint status is unknown **Raises**: MartyCommandException if the joint_name_or_num is unknown ==== get_motor_current ==== get_motor_current(motor_id: int) -> float Get current flowing through a joint motor 1️⃣ 2️⃣ **Arguments**: motor_id, integer >= 0 (non-negative) selects which motor to query **Returns**: Instantaneous current sense reading from motor ''%%motor_id%%'' ==== get_battery_remaining ==== get_battery_remaining() -> float Get the battery remaining percentage 2️⃣ **Returns**: The battery remaining capacity in percent ===== Status, naming and calibration ===== ==== get_robot_status ==== get_robot_status() -> Dict Read the latest robot-status telemetry. 2️⃣ The dictionary can be empty when telemetry is unavailable or stale. Typical keys are: * ''workQCount'' - queued work items/movements. * ''isMoving'' - whether Marty is moving. * ''isPaused'' - whether movement is paused. * ''isFwUpdating'' - whether a firmware update is in progress. * ''flags'' - raw status flags. * ''updateTime'' - time at which this telemetry was received. Additional fields such as heapFree, heapMin, pixRGBT, loopMsAvg and loopMsMax depend on the status packet/firmware. ==== get_joints ==== get_joints() -> Dict Read the latest telemetry for Marty's joints. 2️⃣ Returns a dictionary keyed by joint ID, or an empty dictionary if telemetry is unavailable/stale. Each joint entry can contain: * ''IDNo'' - joint ID (see Marty.JOINT_IDS). * ''name'' - joint name, when known. * ''pos'' - joint angle in degrees. * ''current'' - motor current in milliamps. * ''enabled'' - whether the servo is enabled. * ''commsOK'' - whether the servo is communicating. * ''flags'' - joint status bitmask (see Marty.JOINT_STATUS). * ''updateTime'' - last update time, when available. ==== get_power_status ==== get_power_status() -> Dict Read the latest battery/power telemetry. 2️⃣ Returns an empty dictionary if telemetry is unavailable/stale. Possible keys include: * ''battRemainCapacityPercent'' - remaining charge percentage. * ''battTempDegC'' - battery temperature in degrees Celsius. * ''battRemainCapacityMAH'' - remaining capacity in milliamp-hours. * ''battFullCapacityMAH'' - full capacity in milliamp-hours. * ''battCurrentMA'' - battery current in milliamps (including charging). * ''power5VOnTimeSecs'' - time the joint/add-on supply has been on. * ''powerUSBIsConnected'' - whether USB power is connected. * ''power5VIsOn'' - whether the joint/add-on supply is on. * ''battInfoValid'', ''powerUSBIsValid'', ''powerFlags'' - validity/raw status information. * ''updateTime'' - last telemetry update time. Fields may be omitted when the corresponding information is not valid or not reported. ==== get_add_ons_status ==== get_add_ons_status() -> Dict Read the latest add-on telemetry. 2️⃣ Returns a dictionary keyed by add-on ID, with an additional top-level updateTime entry. It can be empty if telemetry is unavailable/stale. Add-on entries can include IDNo, name, type, whoAmI, whoAmITypeCode, valid and data (10 raw bytes whose meaning depends on the add-on). When iterating over values, filter out non-dictionary entries such as updateTime: for add_on_id, status in my_marty.get_add_ons_status().items(): if isinstance(status, dict): print(add_on_id, status.get("name"), status.get("whoAmI")) ==== get_add_on_status ==== get_add_on_status(add_on_name_or_id: Union[int, str]) -> Dict Read the latest status for one add-on. 2️⃣ * ''add_on_name_or_id'' - numeric add-on ID or configured name. Use the numeric ID returned by get_add_ons_status when a name does not resolve. Returns a dictionary with IDNo, valid and raw data, plus available hardware metadata such as name, type, whoAmI and whoAmITypeCode. Returns an empty dictionary when no matching current status is available. ==== get_system_info ==== get_system_info() -> Dict Read cached firmware/hardware identification information. 2️⃣ The returned dictionary can include: * ''HardwareVersion'' - hardware version string. * ''SystemName'' - firmware/system identifier (for example RIC); the exact value depends on the firmware and must not be assumed to be RicFirmwareESP32. * ''SystemVersion'' - firmware version, for example '1.3.21'. * ''SerialNo'' - serial number. * ''MAC'' - base MAC address. * ''RicHwRevNo'' - RIC hardware revision. Available fields depend on the firmware. The Marty V1 client returns legacy placeholder identification values; use get_firmware_version/get_chatter for V1 firmware reporting. ==== get_hw_elems_list ==== get_hw_elems_list() -> List Get a list of all of the hardware elements on Marty 2️⃣ **Arguments**: none **Returns**: List containing a dictionary for each hardware element, each element is in the form: * ''%%"name"%%'' - name of the hardware element * ''%%"type"%%'' - type of element, see Marty.HW_ELEM_TYPES, other types may appear as add-ons * ''%%"busName"%%'' - name of the bus the element is connected to * ''%%"addr"%%'' - address of the element if it is connected to a bus * ''%%"addrValid"%%'' - 1 if the address is valid, else 0 * ''%%"IDNo"%%'' - identification number of the element * ''%%"whoAmI"%%'' - string from the hardware which may contain additional identification * ''%%"whoAmITypeCode"%%'' - string indicating the type of hardware * ''%%"SN"%%'' - serial number of the hardware element * ''%%"versionStr"%%'' - version string of hardware element in semantic versioning (semver) format * ''%%"commsOK"%%'' - 1 if the element is communicating ok, 0 if not ==== set_marty_name ==== set_marty_name(name: str) -> bool Set Marty's name 2️⃣ **Arguments**: name to call Marty **Returns**: True if successful in setting the name ==== get_marty_name ==== get_marty_name() -> str Get Marty's name 2️⃣ **Arguments**: none **Returns**: the name given to Marty ==== is_marty_name_set ==== is_marty_name_set() -> bool Check if Marty's name is set 2️⃣ **Arguments**: none **Returns**: True if Marty's name is set ==== is_calibrated ==== is_calibrated() -> bool Check if Marty is calibrated 2️⃣ ==== save_calibration ==== save_calibration() -> bool Set the current motor positions as the zero positions 1️⃣ 2️⃣ BE CAREFUL, this can cause unexpected movement or self-interference ==== clear_calibration ==== clear_calibration() -> bool Mark the current calibration as invalid 1️⃣ 2️⃣ This has no immediate physical effect. Marty will still remember the last calibration but will report that it needs to be calibrated again. (You may notice that a "Calibrate" button appears in the app for example.) ===== Disco and indicator lights ===== ==== disco_off ==== disco_off(add_on: Union[Disco, str] = Disco.ALL, api='led') -> bool Turn off a Disco add-on or group. 2️⃣ * ''add_on'' - configured add-on name, or Marty.Disco.ALL, Marty.Disco.EYES, Marty.Disco.ARMS or Marty.Disco.FEET. * ''api'' - 'led' (default) for the firmware LED API, or 'raw_query' for the legacy raw add-on protocol. Most programs should omit this argument. Returns True if the requests succeed. ==== disco_pattern ==== disco_pattern(pattern: int, add_on: Union[Disco, str] = Disco.ALL) -> bool **Deprecated:** use disco_named_pattern for named firmware LED patterns. 2️⃣ * ''pattern'' - 1 or 2 (legacy raw add-on pattern). * ''add_on'' - configured add-on name or a Marty.Disco group. Returns True if the requests succeed. Other pattern numbers raise an exception. ==== disco_named_pattern ==== disco_named_pattern(add_on: str, pattern: str) -> bool Run a named light pattern on a Disco add-on. 2️⃣ Preferred over the deprecated disco_pattern method. * ''add_on'' - the configured add-on name, such as 'LEDeye'. A Marty.Disco group is also accepted by the implementation, despite the str annotation. * ''pattern'' - 'show-off', 'pinwheel' or 'off'. Names are case-sensitive. Returns True if the request is accepted. An unknown pattern raises MartyCommandException. my_marty.disco_named_pattern("LEDeye", "pinwheel") ==== disco_color ==== disco_color(color: Union[str, Tuple[int, int, int]] = 'white', add_on: Union[Disco, str] = Disco.ALL, region: Union[int, str] = 'all', api='led') -> bool Set the colour of a Disco add-on or group. 2️⃣ * ''color'' - six-digit hex string, RGB tuple of three integers from 0 to 255, or a built-in name: white, red, blue, yellow, green, teal, pink, purple or orange. * ''add_on'' - configured add-on name (such as 'LEDeye'), or Marty.Disco.ALL, Marty.Disco.EYES, Marty.Disco.ARMS or Marty.Disco.FEET. * ''region'' - 'all' (default) or a region number supported by that add-on/firmware. Region availability depends on the hardware; it is not universally limited to 0, 1 and 2. Use disco_color_specific_led to address an individual LED. * ''api'' - 'led' (default) or 'raw_query' (legacy raw add-on protocol). This is an optional MartyPy argument, not a colour value; most programs should omit it. Returns True if the requests succeed. Use one of the two supported api values; other values do not select a valid operation. my_marty.disco_color((255, 0, 12), add_on="LEDeye") my_marty.disco_color("blue", add_on=Marty.Disco.EYES) ==== disco_color_specific_led ==== disco_color_specific_led(color: Union[str, Tuple[int, int, int]], add_on: str, add_on_who_am_i: str, led_id: int) -> bool Set one LED on a Disco add-on. 2️⃣ * ''color'' - a named colour, six-digit hex string or RGB tuple (0–255 per channel). * ''add_on'' - the configured add-on name. * ''add_on_who_am_i'' - the hardware type string, such as 'LEDeye', 'LEDarm' or 'LEDfoot'; this determines LED position mapping. This is not the add-on's configured name. * ''led_id'' - zero-based LED position, in range for that hardware. LEDeye has 12 positions, 0–11; the library maps these to the hardware LED order. Returns True if Marty accepts the request. Use get_hw_elems_list to inspect hardware metadata. ==== disco_color_eyepicker ==== disco_color_eyepicker(colours: Union[str, List[str]], add_on: str) -> bool Set individual colours for Disco eyes. 2️⃣ * ''colours'' - one six-digit hex string, or a list of up to 12 six-digit hex strings. A leading # is optional. List positions are mapped to eye LED positions; do not use named colours here. * ''add_on'' - the configured eye add-on name. Returns True if Marty accepts the request. ==== rgb_operator ==== rgb_operator(r: int, g: int, b: int) -> list[int] Create an RGB colour from red, green and blue channel values. Use values from 0 to 255 for LED commands. **Actual return value**: the tuple (r, g, b), not a list. The list[int] annotation in MartyPy 3.8.0 is inaccurate. The function does not send a robot command or validate/clamp the input. ==== hsv_operator ==== hsv_operator(h: int, s: int, v: int) -> list[int] Convert HSV (hue, saturation, value) to an RGB list. This is HSV, not HSL. * ''h'' - hue in degrees; wraps around at 360. * ''s'' - saturation: 0–1, or values greater than 1 interpreted as percentages (0–100). * ''v'' - value/brightness: 0–1, or values greater than 1 interpreted as percentages (0–100). Saturation and value are clamped to 0–1 after conversion. Returns [red, green, blue] with integer channels from 0 to 255. Convert to a tuple when passing to disco_color, whose RGB input is a tuple. colour = tuple(my_marty.hsv_operator(120, 100, 100)) my_marty.disco_color(colour, add_on="LEDeye") ==== function_led ==== function_led(colour: Tuple[int, int, int], breathe: str = 'on', breath_ms: int = 1000) -> bool Set Marty's function/indicator LED, not a Disco add-on. 2️⃣ * ''colour'' - RGB tuple, with each channel from 0 to 255. * ''breathe'' - 'on' for steady light or 'breath' for the breathing effect. * ''breath_ms'' - breathing period in milliseconds. Returns True if Marty accepts the request. A colour with other than three channels raises MartyCommandException. ==== function_led_off ==== function_led_off() -> bool Turn off Marty's function/indicator LED. 2️⃣ Returns True if Marty accepts the request. ===== Advanced APIs, files and callbacks ===== ==== add_on_query ==== add_on_query(add_on_name: str, data_to_write: bytes, num_bytes_to_read: int) -> Dict Write and read an add-on directly (raw-mode) 2️⃣ **Arguments**: * ''%%add_on_name%%'' - name of the add-on (see get_add_ons_status() at the top level or response to ''%%addon/list%%'' REST API command) * ''%%data_to_write%%'' - can be zero length if nothing is to be written, the first byte will generally be the register or opcode of the add-on * ''%%num_bytes_to_read%%'' - number of bytes to read from the device - can be zero **Returns**: Dict with keys including: "rslt" - the result which will be "ok" if the query succeeded "dataRead" - the data read from the add-on ==== send_ric_rest_cmd ==== send_ric_rest_cmd(ricRestCmd: str) -> None Send a command in RIC REST format to Marty 2️⃣ This is a special purpose command which you can use to do advanced control of Marty **Arguments**: * ''%%ricRestCmd%%'' - string containing the command to send to Marty **Returns**: None ==== send_ric_rest_cmd_sync ==== send_ric_rest_cmd_sync(ricRestCmd: str) -> Dict Send a command in RIC REST format to Marty and wait for reply 2️⃣ This is a special purpose command which you can use to do advanced control of Marty **Arguments**: * ''%%ricRestCmd%%'' - string containing the command to send to Marty **Returns**: Dictionary containing the response received from Marty ==== send_file ==== send_file(filename: str, progress_callback: Callable[[int, int], bool] = None, file_dest: str = 'fs') -> bool Upload a file from the computer to Marty. 2️⃣ * ''filename'' - local path to the file to upload. * ''progress_callback'' - optional function taking exactly two arguments: (bytes_sent, total_bytes). Return True to continue or False to abort. * ''file_dest'' - 'fs' for file storage, or 'ricfw' for a RIC firmware update. Use 'ricfw' only when intentionally updating firmware with the correct image. Returns True if uploading succeeds. Operating-system or connection failures can raise exceptions. def upload_progress(bytes_sent, total_bytes): print(bytes_sent, "of", total_bytes) return True my_marty.send_file("example.mp3", progress_callback=upload_progress) ==== get_file_list ==== get_file_list() -> List[str] Request the files stored on Marty. 2️⃣ Returns the list supplied by firmware, or an empty list if the file-list request fails. Although the 3.8.0 annotation is List[str], entries can be file-information dictionaries rather than plain filename strings. Inspect the returned entries instead of assuming a string-only list. ==== delete_file ==== delete_file(filename: str) -> bool Delete a stored file from Marty. 2️⃣ * ''filename'' - the filename on Marty, not a path on the computer. Returns True if the delete request succeeds. Deletion is destructive; only delete files you intentionally want to remove. ==== register_logging_callback ==== register_logging_callback(loggingCallback: Callable[[str], None]) -> None Register a function to receive each RIC log message. 2️⃣ Primarily useful for debugging. * ''loggingCallback'' - a function taking one string (the log message). Returns None. Keep callbacks short; do not run long movement/wait operations inside communication callbacks. ==== register_publish_callback ==== register_publish_callback(messageCallback: Callable[[int], None]) -> None Register a function called after each published telemetry message has been decoded. 2️⃣ * ''messageCallback'' - a function taking one integer topic code. Compare the topic with Marty.PUBLISH_TOPIC_ACCELEROMETER, Marty.PUBLISH_TOPIC_SERVOS, Marty.PUBLISH_TOPIC_POWER, Marty.PUBLISH_TOPIC_ADDONS or Marty.PUBLISH_TOPIC_ROBOT_STATUS, then read the updated data with the normal getter methods. Returns None. Keep callbacks short. ==== register_report_callback ==== register_report_callback(messageCallback: Callable[[str], None]) -> None Register a function to receive RIC report messages, including alerts. 2️⃣ * ''messageCallback'' - a function taking one JSON string. Reports may include msgType (such as 'warn'), msgBody (such as 'freeFallDet' or 'overCurrentDet') and IDNo for the hardware element. Returns None. Parse with json.loads when needed, and keep callbacks short. ==== get_interface_stats ==== get_interface_stats() -> Dict Read communication statistics for debugging. 2️⃣ The dictionary may include roundTripAvgMS, msgRxRatePS, msgTxRatePS, unmatched, matched, unnumbered, timedOut, uploadBPS, rxCount and txCount. Published telemetry rates can appear as servosPS, imuPS, robotPS, powerPS or addonsPS. Available keys depend on the interface. ==== get_test_output ==== get_test_output() -> str Read output captured by MartyPy's test interface. For automated library testing with method='test', not for reading a normal robot connection's program output. ===== Legacy MartyV1 commands ===== These are legacy interfaces. Use get_ready rather than hello for new programs. ==== enable_motors ==== enable_motors(enable: bool = True, clear_queue: bool = True) -> bool Toggle power to motors 1️⃣ **Arguments**: * ''%%enable%%'' - True/False toggle * ''%%clear_queue%%'' - Default True, prevents unfinished but 'muted' motions from jumping as soon as motors are enabled On V1, disabling motors returns False even when the disable command succeeds; do not treat that return value alone as proof of a communication failure. ==== enable_safeties ==== enable_safeties(enable: bool = True) -> bool Tell the board to turn on 'normal' safeties 1️⃣ ==== fall_protection ==== fall_protection(enable: bool = True) -> bool Toggle fall protections 1️⃣ **Arguments**: * ''%%enable%%'' - True/False toggle ==== motor_protection ==== motor_protection(enable: bool = True) -> bool Toggle motor current protections 1️⃣ **Arguments**: * ''%%enable%%'' - True/False toggle ==== battery_protection ==== battery_protection(enable: bool = True) -> bool Toggle low battery protections 1️⃣ **Arguments**: * ''%%enable%%'' - True/False toggle ==== buzz_prevention ==== buzz_prevention(enable: bool = True) -> bool Toggle motor buzz prevention 1️⃣ **Arguments**: * ''%%enable%%'' - True/False toggle ==== lifelike_behaviour ==== lifelike_behaviour(enable: bool = True) -> bool Tell the robot whether it can or can't move now and then in a lifelike way when idle. 1️⃣ **Arguments**: * ''%%enable%%'' - True/False toggle ==== ros_command ==== ros_command(*byte_array: int) -> bool Low level proxied access to the ROS Serial API between the modem and main controller 1️⃣ ==== keyframe ==== keyframe(time: float, num_of_msgs: int, msgs) -> List[bytes] Takes in information about movements and generates keyframes returns a list of bytes 1️⃣ **Arguments**: * ''%%time%%'' - time (in seconds) taken to complete movement * ''%%num_of_msgs%%'' - number of commands sent * ''%%msgs%%'' - commands sent in the following format [(ID CMD), (ID CMD), etc...] ==== get_chatter ==== get_chatter() -> bytes Return chatter topic data (variable length) 1️⃣ ==== get_firmware_version ==== get_firmware_version() -> bool Ask the board to print the firmware version over chatter 1️⃣ ==== ros_serial_formatter ==== ros_serial_formatter(topicID: int, send: bool = False, *message: int) -> List[int] Formats message into ROS serial format and returns formatted message as a list 1️⃣ Calls ros_command with the processed message if send is True. More information about the ROS serial format can be found here: http:%%//%%wiki.ros.org/rosserial/Overview/Protocol ==== pinmode_gpio ==== pinmode_gpio(gpio: int, mode: str) -> bool Configure a GPIO pin 1️⃣ **Arguments**: * ''%%gpio%%'' - pin number between 0 and 7 * ''%%mode%%'' - choose from: 'digital in','analog in' or 'digital out' ==== write_gpio ==== write_gpio(gpio: int, value: int) -> bool Write a value to a GPIO port 1️⃣ ==== digitalread_gpio ==== digitalread_gpio(gpio: int) -> bool Read from GPIO 1️⃣ **Arguments**: GPIO pin number, >= 0 (non-negative) **Returns**: Returns High/Low state of a GPIO pin ==== set_parameter ==== set_parameter(*byte_array: int) -> bool Set board parameters 1️⃣ **Arguments**: * ''%%byte_array%%'' - a list in the following format [paramID, params] ==== i2c_write ==== i2c_write(*byte_array: int) -> bool Write a bytestream to the i2c port. 1️⃣ The first byte should be the address, following from that the datagram folows standard i2c spec ==== i2c_write_to_ric ==== i2c_write_to_ric(address: int, byte_array: bytes) -> bool Write a formatted bytestream to the i2c port. 1️⃣ The bytestream is formatted in the ROS serial format. address: the other device's address ==== i2c_write_to_rick ==== i2c_write_to_rick(address: int, byte_array: bytes) -> bool Write a formatted bytestream to the i2c port. 1️⃣ The bytestream is formatted in the ROS serial format. address: the other device's address ==== get_battery_voltage ==== get_battery_voltage() -> float Get the voltage of the battery 1️⃣ **Returns**: The battery voltage reading as a float in Volts ==== hello ==== hello(blocking: Optional[bool] = None) -> bool Zero joints and wiggle eyebrows. 1️⃣ 2️⃣ On V2 this calls the get-ready trajectory; use get_ready for new programs. The optional blocking override works like other movement methods that expose blocking. ==== discover ==== discover() -> List[str] Try and find us some Martys! 1️⃣ ===== Helper commands ===== ==== %%__%%del%%__%% ==== __del__() -> None Object cleanup hook, which calls close. Normally invoked by Python; do not rely on its timing to stop a robot or release a connection. Use close explicitly.