# -*- 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]