Source code for pyax12.instruction_packet

# -*- coding : utf-8 -*-

# PyAX-12

# The MIT License
#
# Copyright (c) 2010,2015 Jeremie DECOCK (http://www.jdhp.org)
#
# 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.

"""
This module contain the `InstructionPacket` class which implements "instruction
packets" (the packets sent by the controller to the Dynamixel actuators to send
commands).
"""

__all__ = ['InstructionPacket']

import pyax12.packet as pk
from pyax12 import utils

# THE INSTRUCTION SET
# (see the official Dynamixel AX-12 User's manual p.19)

PING = 0x01
READ_DATA = 0x02
WRITE_DATA = 0x03
REG_WRITE = 0x04
ACTION = 0x05
RESET = 0x06
SYNC_WRITE = 0x83

INSTRUCTIONS = (PING, READ_DATA, WRITE_DATA, REG_WRITE, ACTION, RESET,
                SYNC_WRITE)

# THE NUMBER OF PARAMETERS EXPECTED FOR EACH INSTRUCTION
# (see the official Dynamixel AX-12 User's manual p.19)

MAX_NUM_PARAMS = 255 - 6 # TODO: what is the actual max value ?

NUMBER_OF_PARAMETERS = {
    PING:{
        'min': 0,
        'max': 0
    },
    READ_DATA:{
        'min': 2,
        'max': 2
    },
    WRITE_DATA:{
        'min': 2,
        'max': MAX_NUM_PARAMS
    },
    REG_WRITE:{
        'min': 2,
        'max': MAX_NUM_PARAMS
    },
    ACTION:{
        'min': 0,
        'max': 0
    },
    RESET:{
        'min': 0,
        'max': 0
    },
    SYNC_WRITE:{
        'min': 4,
        'max': MAX_NUM_PARAMS
    }
}


# THE IMPLEMENTATION OF "INSTRUCTION PACKETS"

[docs]class InstructionPacket(pk.Packet): """The "instruction packet" is the packet sent by the main controller to the Dynamixel units to send commands. The structure of the instruction packet is as the following: +----+----+--+------+-----------+----------+---+-----------+---------+ |0XFF|0XFF|ID|LENGTH|INSTRUCTION|PARAMETER1|...|PARAMETER N|CHECK SUM| +----+----+--+------+-----------+----------+---+-----------+---------+ :param int dynamixel_id: the the unique ID of the Dynamixel unit which have to execute this instruction packet. :param int instruction: the instruction for the Dynamixel actuator to perform. :param bytes parameters: a sequence of bytes used if there is additional information needed to be sent other than the instruction itself. """ def __init__(self, dynamixel_id, instruction, parameters=None): # Check the parameters byte. # "TypeError" and "ValueError" are raised by the "bytes" constructor if # necessary. # The statement "tuple(parameters)" implicitely rejects integers (and # all non-iterable objects) to compensate the fact that the bytes # constructor doesn't reject them: bytes(3) is valid and returns # b'\x00\x00\x00'. if parameters is None: parameters = bytes() else: parameters = bytes(tuple(parameters)) # Add the header bytes. self._bytes = bytearray((0xff, 0xff)) # Check and add the Dynamixel ID byte. # "TypeError" and "ValueError" are raised by the "bytearray.append()" # if necessary. if 0x00 <= dynamixel_id <= 0xfe: self._bytes.append(dynamixel_id) else: if isinstance(dynamixel_id, int): msg = ("Wrong dynamixel_id value, " "an integer in range(0x00, 0xfe) is required.") raise ValueError(msg) else: raise TypeError("Wrong dynamixel_id type (integer required).") # Add the length byte. self._bytes.append(len(parameters) + 2) # Check and add the instruction byte. # "TypeError" and "ValueError" are raised by the "bytearray.append()" # if necessary. if instruction in INSTRUCTIONS: self._bytes.append(instruction) else: if isinstance(instruction, int): msg = "Wrong instruction, should be in ({})." instructions_str = utils.pretty_hex_str(INSTRUCTIONS) raise ValueError(msg.format(instructions_str)) else: raise TypeError("Wrong instruction type (integer required).") # Check and add the parameter bytes. nb_param_min = NUMBER_OF_PARAMETERS[self.instruction]['min'] nb_param_max = NUMBER_OF_PARAMETERS[self.instruction]['max'] if nb_param_min <= len(parameters) <= nb_param_max: self._bytes.extend(parameters) else: msg = ("Wrong number of parameters: {} parameters " "(min expected={}; max expected={}).") nb_param = len(parameters) raise ValueError(msg.format(nb_param, nb_param_min, nb_param_max)) # Add the checksum byte. computed_checksum = pk.compute_checksum(self._bytes[2:]) self._bytes.append(computed_checksum) # READ ONLY PROPERTIES @property def instruction(self): """The instruction for the Dynamixel actuator to perform. This member is a read-only property. """ return self._bytes[4]