Files
opencv/ev3/direct_command.py
jens 7412e813bc - added ev3
git-svn-id: http://moon:8086/svn/software/trunk/projects/opencv@329 b431acfa-c32f-4a4a-93f1-934dc6c82436
2016-10-22 06:43:13 +00:00

2214 lines
71 KiB
Python
Executable File

"""A simple interface for executing bytecodes over a Bluetooth serial port.
From the lms2012 source code documentation:
Beside running user programs the VM is able to execute direct commands from
the Communication Module. In fact direct commands are small programs that
consist of regular byte codes and they are executed in parallel with a running
user program. Special care MUST be taken when writing direct commands because
the decision until now is NOT to restrict the use of "dangerous" codes and
constructions (loops in a direct command are allowed).
If a new direct command from the same source is going to be executed an actual
running direct command is terminated.
Because of a small header objects are limited to one VMTHREAD only - SUBCALLs
and BLOCKs are, of course, not possible. This header contains information about
the number of global variables (for response), number of local variables, and
command size.
Direct commands that have data responses can place the data in the global
variable space. The global variable space is equal to the communication
response buffer. The composition of the direct command defines at which
offset the result is placed (global variable 0 is placed at offset 0 in
the buffer).
Offsets in the response buffer (global variables) must be aligned (i.e. 32bit
variable offsets are divisible by 4, 16bit variable offsets are divisible by 2).
All multi-byte words are little endian.
Direct Command bytes:
------------------------------
Byte 0 - 1: Command size
Byte 2 - 3: Message counter
Byte 4: CommandType
Byte 5 - 6: Number of global and local variables (compressed).
Byte 6 Byte 5
76543210 76543210
-------- --------
llllllgg gggggggg
gg gggggggg Global variables [0..MAX_COMMAND_GLOBALS]
llllll Local variables [0..MAX_COMMAND_LOCALS]
Byte 7 - n: Byte codes
Direct Command response Bytes:
------------------------------
Byte 0 - 1: Reply size
Byte 2 - 3: Message counter
Byte 4: ReplyType
Byte 5 - n: Response buffer (global variable values)
"""
import ev3
import message
MAX_CMD_LEN = 1019 # The size of the brick's txBuf is 1024 bytes but
# the header requires 5 bytes.
MAX_STR_LEN = 255
MAX_VERSION_STR_LEN = 64
MAX_LOCAL_VARIABLE_BYTES = 0xFFFFFFFF
MAX_NAME_STR_LEN = 64
MOTOR_MIN_POWER = -100
MOTOR_MAX_POWER = 100
MOTOR_MIN_SPEED = -100
MOTOR_MAX_SPEED = 100
USB_CHAIN_LAYER_MASTER = 0
USB_CHAIN_LAYER_SLAVE = 1
MOTOR_MIN_RATIO = -200
MOTOR_MAX_RATIO = 200
MIN_VOLUME = 0
MAX_VOLUME = 100
LCD_HEIGHT_PIXELS = 128
LCD_WIDTH_PIXELS = 178
class DirectCommandError(Exception):
"""Subclass for reporting errors."""
pass
class CommandType(object):
"""Every System Command must be one of these two types."""
DIRECT_COMMAND_REPLY = 0x00
DIRECT_COMMAND_NO_REPLY = 0x80
class ReplyType(object):
"""Every reply to a System Command must be one of these two types."""
DIRECT_REPLY = 0x02
DIRECT_REPLY_ERROR = 0x04
class OutputPort(object):
"""These can be OR'd together to operate on multiple ports at once."""
PORT_A = 0x01
PORT_B = 0x02
PORT_C = 0x04
PORT_D = 0x08
ALL = (PORT_A | PORT_B | PORT_C | PORT_D)
class InputPort(object):
"""These can be OR'd together to operate on multiple ports at once."""
PORT_1 = 0x00
PORT_2 = 0x01
PORT_3 = 0x02
PORT_4 = 0x03
PORT_A = 0x10
PORT_B = 0x11
PORT_C = 0x12
PORT_D = 0x13
class StopType(object):
"""When an OutputPort is stopped it can be told to brake or coast."""
COAST = 0
BRAKE = 1
class PolarityType(object):
""""""
BACKWARD = -1
TOGGLE = 0
FORWARD = 1
class TouchMode(object):
""""""
TOUCH = 0
BUMPS = 1
class NXTLightMode(object):
""""""
REFLECT = 0
AMBIENT = 1
class NXTSoundMode(object):
""""""
DECIBELS = 0
ADJUSTED_DECIBLES = 1
class NXTColorMode(object):
""""""
REFLECTIVE = 0
AMBIENT = 1
COLOR = 2
GREEN = 3
BLUE = 4
RAW = 5
class NXTUltrasonicMode(object):
""""""
CM = 0
INCHES = 1
class NXTTemperatureMode(object):
""""""
CELSIUS = 0
FAHRENHEIT = 1
class MotorMode(object):
""""""
DEGREES = 0
ROTATIONS = 1
PERCENT = 2
class UltrasonicMode(object):
""""""
CM = 0
INCH = 1
LISTEN = 2
class GyroMode(object):
""""""
ANGLE = 0
RATE = 1
FAS = 2
G_AND_A = 3
class IRMode(object):
""""""
PROXIMITY = 0
SEEK = 1
REMOTE = 2
REMOTE_A = 3
SALT = 4
CALIBRATION = 5
class ColorMode(object):
""""""
RELECTIVE = 0
AMBIENT = 1
COLOR = 2
class ColorSensorColor(object):
"""These are the results that the EV3 color sensor can return when operating
in ColorMode.COLOR.
"""
NONE = 0
BLACK = 1
BLUE = 2
GREEN = 3
YELLOW = 4
RED = 5
WHITE = 6
BROWN = 7
class LEDPattern(object):
"""The brick user interface has several status LEDs."""
OFF = 0
GREEN = 1
RED = 2
ORANGE = 3
FLASHING_GREEN = 4
FLASHING_RED = 5
FLASHING_ORANGE = 6
GREEN_HEARTBEAT = 7
RED_HEARTBEAT = 8
ORANGE_HEARTBEAT = 9
class DeviceType(object):
"""These are the known device types.
NOTE: These have only been partially confirmed.
"""
NXT_TOUCH = 0x01
NXT_LIGHT = 0x02
NXT_SOUND = 0x03
NXT_COLOR = 0x04
NXT_ULTRASONIC = 0x05
NXT_TEMPERATURE = 0x06
TACHO = 0x07 # TYPE_TACHO in lms2012.h
MINI_TACHO = 0x08 # TYPE_MINITACHO in lms2012.h
NEW_TACHO = 0x09 # TYPE_NEWTACHO in lms2012.h
EV3_TOUCH = 0x10
EV3_COLOR = 0x1D
EV3_ULTRASONIC = 0x1E
EV3_GYROSCOPE = 0x20
EV3_INFRARED = 0x21
SENSOR_INITIALIZING = 0x7D
PORT_EMPTY = 0x7E
ERROR_PORT = 0x7F
UNKNOWN = 0xFF
class LCDColor(object):
"""The brick's LCD only displays two colors."""
BACKGROUND = 0
FOREGROUND = 1
class ButtonType(object):
"""The brick's user interface contains 6 buttons."""
NO_BUTTON = 0
UP_BUTTON = 1
ENTER_BUTTON = 2
DOWN_BUTTON = 3
RIGHT_BUTTON = 4
LEFT_BUTTON = 5
BACK_BUTTON = 6
ANY_BUTTON = 7
class MathType(object):
""""""
EXP = 1 # e^x r = expf(x)
MOD = 2 # Modulo r = fmod(x,y)
FLOOR = 3 # Floor r = floor(x)
CEIL = 4 # Ceiling r = ceil(x)
ROUND = 5 # Round r = round(x)
ABS = 6 # Absolute r = fabs(x)
NEGATE = 7 # Negate r = 0.0 - x
SQRT = 8 # Squareroot r = sqrt(x)
LOG = 9 # Log r = log10(x)
LN = 10 # Ln r = log(x)
SIN = 11
COS = 12
TAN = 13
ASIN = 14
ACOS = 15
ATAN = 16
MOD8 = 17 # Modulo DATA8 r = x % y
MOD16 = 18 # Modulo DATA16 r = x % y
MOD32 = 19 # Modulo DATA32 r = x % y
POW = 20 # Exponent r = powf(x,y)
TRUNC = 21 # Truncate r = (float)((int)(x * pow(y))) / pow(y)
class BrowserType(object):
""""""
BROWSE_FOLDERS = 0 # Browser for folders
BROWSE_FOLDS_FILES = 1 # Browser for folders and files
BROWSE_CACHE = 2 # Browser for cached / recent files
BROWSE_FILES = 3 # Browser for files
class Icon(object):
"""The icons on the brick are enumerated by value."""
ICON_NONE = -1
ICON_RUN = 0
ICON_FOLDER = 1
ICON_FOLDER2 = 2
ICON_USB = 3
ICON_SD = 4
ICON_SOUND = 5
ICON_IMAGE = 6
ICON_SETTINGS = 7
ICON_ONOFF = 8
ICON_SEARCH = 9
ICON_WIFI = 10
ICON_CONNECTIONS = 11
ICON_ADD_HIDDEN = 12
ICON_TRASHBIN = 13
ICON_VISIBILITY = 14
ICON_KEY = 15
ICON_CONNECT = 16
ICON_DISCONNECT = 17
ICON_UP = 18
ICON_DOWN = 19
ICON_WAIT1 = 20
ICON_WAIT2 = 21
ICON_BLUETOOTH = 22
ICON_INFO = 23
ICON_TEXT = 24
ICON_QUESTIONMARK = 27
ICON_INFO_FILE = 28
ICON_DISC = 29
ICON_CONNECTED = 30
ICON_OBP = 31
ICON_OBD = 32
ICON_OPENFOLDER = 33
ICON_BRICK1 = 34
class FontType(object):
""""""
NORMAL_FONT = 0
SMALL_FONT = 1
LARGE_FONT = 2
TINY_FONT = 3
class DataFormat(object):
"""Data formats that are used by the VM."""
DATA8 = 0x00
DATA16 = 0x01
DATA32 = 0x02
DATA_F = 0x03 # 32bit floating point value (single precision)
DATA_S = 0x04 # Zero terminated string
DATA_A = 0x05 # Array handle
DATA_V = 0x07 # Variable type
DATA_PCT = 0x10 # Percent (used in INPUT_READEXT)
DATA_RAW = 0x12 # Raw (used in INPUT_READEXT)
DATA_SI = 0x13 # SI unit (used in INPUT_READEXT)
# Values used by this Python module only:
HND = 0xFF # For compatibility with ParamTypes.
BOOL = 0xFE # For converting to Python values
class ParamType(object):
"""Parameter types that are used by the VM."""
PRIMPAR_LABEL = 0x20
HND = 0x10 # 8bit handle index (i.e. pointer to a string)
ADR = 0x08 # 3bit address
LCS = 0x84 # Null terminated string
LAB1 = 0xA0
LC0 = 0x00 # 6bit immediate
LC1 = 0x81 # 8bit immediate
LC2 = 0x82 # 16bit immediate
LC4 = 0x83 # 32bit immediate
LCA = 0x81 # 8bit pointer to local array
LV1 = 0xC1 # 8bit pointer to local value
LV2 = 0xC2 # 16bit pointer to local value
LV4 = 0xC3 # 32bit pointer to local value
LVA = 0xC1 # 8bit pointer to local array
GV0 = 0x60 # 5bit pointer to global value
GV1 = 0xE1 # 8bit pointer to global value
GV2 = 0xE2 # 16bit pointer to global value
GV4 = 0xE3 # 32bit pointer to global value
GVA = 0xE1 # 8bit pointer to global array
# Values used by this Python module only:
FLOAT = 0xFF # 32bit floating point value (single precision)
# Defines the number of bytes required to represent each DataFormat.
PARAM_TYPE_LENS = { ParamType.PRIMPAR_LABEL: None,
ParamType.HND: 1,
ParamType.ADR: 1,
ParamType.LCS: None,
ParamType.LAB1: 1,
ParamType.LC0: 0,
ParamType.LC1: 1,
ParamType.LC2: 2,
ParamType.LC4: 4,
ParamType.LCA: 1,
ParamType.LV1: 1,
ParamType.LV2: 2,
ParamType.LV4: 4,
ParamType.LVA: 1,
ParamType.GV0: 0,
ParamType.GV1: 1,
ParamType.GV2: 2,
ParamType.GV4: 4,
ParamType.GVA: 1,
ParamType.FLOAT: 4 }
DATA_FORMAT_LENS = { DataFormat.DATA8: 1,
DataFormat.DATA16: 2,
DataFormat.DATA32: 4,
DataFormat.DATA_F: 4,
DataFormat.DATA_S: None,
DataFormat.DATA_A: None,
DataFormat.DATA_V: None,
DataFormat.DATA_PCT: 1,
DataFormat.DATA_RAW: 4,
DataFormat.DATA_SI: 4,
DataFormat.HND: 1,
DataFormat.BOOL: 1 }
# There are two ways to specify an output in the c_output module. The first is
# as a bit mask and the second is by index.
OUTPUT_CHANNEL_TO_INDEX = { OutputPort.PORT_A: 0,
OutputPort.PORT_B: 1,
OutputPort.PORT_C: 2,
OutputPort.PORT_D: 3 }
class UIReadSubcode(object):
""""""
GET_VBATT = 1
GET_IBATT = 2
GET_OS_VERS = 3
GET_EVENT = 4
GET_TBATT = 5
GET_IINT = 6
GET_IMOTOR = 7
GET_STRING = 8
GET_HW_VERS = 9
GET_FW_VERS = 10
GET_FW_BUILD = 11
GET_OS_BUILD = 12
GET_ADDRESS = 13
GET_CODE = 14
KEY = 15
GET_SHUTDOWN = 16
GET_WARNING = 17
GET_LBATT = 18
TEXTBOX_READ = 21
GET_VERSION = 26
GET_IP = 27
GET_POWER = 29
GET_SDCARD = 30
GET_USBSTICK = 31
class UIWriteSubcode(object):
""""""
WRITE_FLUSH = 1
FLOATVALUE = 2
STAMP = 3
PUT_STRING = 8
VALUE8 = 9
VALUE16 = 10
VALUE32 = 11
VALUEF = 12
ADDRESS = 13
CODE = 14
DOWNLOAD_END = 15
SCREEN_BLOCK = 16
TEXTBOX_APPEND = 21
SET_BUSY = 22
SET_TESTPIN = 24
INIT_RUN = 25
UPDATE_RUN = 26
LED = 27
POWER = 29
GRAPH_SAMPLE = 30
TERMINAL = 31
class UIButtonSubcode(object):
""""""
SHORTPRESS = 1
LONGPRESS = 2
WAIT_FOR_PRESS = 3
FLUSH = 4
PRESS = 5
RELEASE = 6
GET_HORZ = 7
GET_VERT = 8
PRESSED = 9
SET_BACK_BLOCK = 10
GET_BACK_BLOCK = 11
TESTSHORTPRESS = 12
TESTLONGPRESS = 13
GET_BUMBED = 14
GET_CLICK = 15
class COMGetSubcodes(object):
""""""
GET_ON_OFF = 1 # Set, Get
GET_VISIBLE = 2 # Set, Get
GET_RESULT = 4 # Get
GET_PIN = 5 # Set, Get
SEARCH_ITEMS = 8 # Get
SEARCH_ITEM = 9 # Get
FAVOUR_ITEMS = 10 # Get
FAVOUR_ITEM = 11 # Get
GET_ID = 12
GET_BRICKNAME = 13
GET_NETWORK = 14
GET_PRESENT = 15
GET_ENCRYPT = 16
CONNEC_ITEMS = 17
CONNEC_ITEM = 18
GET_INCOMING = 19
GET_MODE2 = 20
class COMSetSubcode(object):
""""""
SET_ON_OFF = 1 # Set, Get
SET_VISIBLE = 2 # Set, Get
SET_SEARCH = 3 # Set
SET_PIN = 5 # Set, Get
SET_PASSKEY = 6 # Set
SET_CONNECTION = 7 # Set
SET_BRICKNAME = 8
SET_MOVEUP = 9
SET_MOVEDOWN = 10
SET_ENCRYPT = 11
SET_SSID = 12
SET_MODE2 = 13
class InputDeviceSubcode(object):
""""""
GET_FORMAT = 2
CAL_MINMAX = 3
CAL_DEFAULT = 4
GET_TYPEMODE = 5
GET_SYMBOL = 6
CAL_MIN = 7
CAL_MAX = 8
SETUP = 9 # Probably only for internal use.
CLR_ALL = 10 # Resets counters, angle, etc.
GET_RAW = 11
GET_CONNECTION = 12
STOP_ALL = 13 # Stops any attached motors?
GET_NAME = 21
GET_MODENAME = 22
SET_RAW = 23
GET_FIGURES = 24
GET_CHANGES = 25
CLR_CHANGES = 26
READY_PCT = 27
READY_RAW = 28
READY_SI = 29
GET_MINMAX = 30
GET_BUMPS = 31
class ProgramInfoSubcode(object):
""""""
OBJ_STOP = 0
OBJ_START = 4
GET_STATUS = 22
GET_SPEED = 23
GET_PRGRESULT = 24
SET_INSTR = 25
class UIDrawSubcode(object):
""""""
UPDATE = 0
CLEAN = 1
PIXEL = 2
LINE = 3
CIRCLE = 4
TEXT = 5
ICON = 6
PICTURE = 7
VALUE = 8
FILLRECT = 9
RECT = 10
NOTIFICATION = 11
QUESTION = 12
KEYBOARD = 13
BROWSE = 14
VERTBAR = 15
INVERSERECT = 16
SELECT_FONT = 17
TOPLINE = 18
FILLWINDOW = 19
SCROLL = 20
DOTLINE = 21
VIEW_VALUE = 22
VIEW_UNIT = 23
FILLCIRCLE = 24
STORE = 25
RESTORE = 26
ICON_QUESTION = 27
BMPFILE = 28
POPUP = 29
GRAPH_SETUP = 30
GRAPH_DRAW = 31
TEXTBOX = 32
class FileSubcode(object):
""""""
OPEN_APPEND = 0
OPEN_READ = 1
OPEN_WRITE = 2
READ_VALUE = 3
WRITE_VALUE = 4
READ_TEXT = 5
WRITE_TEXT = 6
CLOSE = 7
LOAD_IMAGE = 8
GET_HANDLE = 9
MAKE_FOLDER = 10
GET_POOL = 11
SET_LOG_SYNC_TIME = 12
GET_FOLDERS = 13
GET_LOG_SYNC_TIME = 14
GET_SUBFOLDER_NAME = 15
WRITE_LOG = 16
CLOSE_LOG = 17
GET_IMAGE = 18
GET_ITEM = 19
GET_CACHE_FILES = 20
PUT_CACHE_FILE = 21
GET_CACHE_FILE = 22
DEL_CACHE_FILE = 23
DEL_SUBFOLDER = 24
GET_LOG_NAME = 25
OPEN_LOG = 27
READ_BYTES = 28
WRITE_BYTES = 29
REMOVE = 30
MOVE = 31
class ArraySubcode(object):
""""""
DELETE = 0
CREATE8 = 1
CREATE16 = 2
CREATE32 = 3
CREATEF = 4
RESIZE = 5
FILL = 6
COPY = 7
INIT8 = 8
INIT16 = 9
INIT32 = 10
INITF = 11
SIZE = 12
READ_CONTENT = 13
WRITE_CONTENT = 14
READ_SIZE = 15
class FilenameSubcode(object):
""""""
EXIST = 16 # MUST BE GREATER OR EQUAL TO "ARRAY_SUBCODES"
TOTALSIZE = 17
SPLIT = 18
MERGE = 19
CHECK = 20
PACK = 21
UNPACK = 22
GET_FOLDERNAME = 23
class InfoSubcode(object):
""""""
SET_ERROR = 1
GET_ERROR = 2
ERRORTEXT = 3
GET_VOLUME = 4
SET_VOLUME = 5
GET_MINUTES = 6
SET_MINUTES = 7
class SoundSubcode(object):
""""""
BREAK = 0
TONE = 1
PLAY = 2
REPEAT = 3
SERVICE = 4
class StringSubcode(object):
""""""
GET_SIZE = 1 # Get string size
ADD = 2 # Add two strings
COMPARE = 3 # Compare two strings
DUPLICATE = 5 # Duplicate one string to another
VALUE_TO_STRING = 6
STRING_TO_VALUE = 7
STRIP = 8
NUMBER_TO_STRING = 9
SUB = 10
VALUE_FORMATTED = 11
NUMBER_FORMATTED = 12
class TstSubcode(object):
""""""
TST_OPEN = 10 # Must >= "INFO_SUBCODES"
TST_CLOSE = 11
TST_READ_PINS = 12
TST_WRITE_PINS = 13
TST_READ_ADC = 14
TST_WRITE_UART = 15
TST_READ_UART = 16
TST_ENABLE_UART = 17
TST_DISABLE_UART = 18
TST_ACCU_SWITCH = 19
TST_BOOT_MODE2 = 20
TST_POLL_MODE2 = 21
TST_CLOSE_MODE2 = 22
TST_RAM_CHECK = 23
class Opcode(object):
"""All of the opcodes that are used by the VM."""
ERROR = 0x00
NOP = 0x01
PROGRAM_STOP = 0x02
PROGRAM_START = 0x03
OBJECT_STOP = 0x04
OBJECT_START = 0x05
OBJECT_TRIG = 0x06
OBJECT_WAIT = 0x07
RETURN = 0x08
CALL = 0x09
OBJECT_END = 0x0A
SLEEP = 0x0B
PROGRAM_INFO = 0x0C
LABEL = 0x0D
PROBE = 0x0E
DO = 0x0F
# MATH
ADD8 = 0x10
ADD16 = 0x11
ADD32 = 0x12
ADDF = 0x13
SUB8 = 0x14
SUB16 = 0x15
SUB32 = 0x16
SUBF = 0x17
MUL8 = 0x18
MUL16 = 0x19
MUL32 = 0x1A
MULF = 0x1B
DIV8 = 0x1C
DIV16 = 0x1D
DIV32 = 0x1E
DIVF = 0x1F
# LOGIC
OR8 = 0x20
OR16 = 0x21
OR32 = 0x22
AND8 = 0x24
AND16 = 0x25
AND32 = 0x26
XOR8 = 0x28
XOR16 = 0x29
XOR32 = 0x2A
RL8 = 0x2C
RL16 = 0x2D
RL32 = 0x2E
# MOVE
INIT_BYTES = 0x2F
MOVE8_8 = 0x30
MOVE8_16 = 0x31
MOVE8_32 = 0x32
MOVE8_F = 0x33
MOVE16_8 = 0x34
MOVE16_16 = 0x35
MOVE16_32 = 0x36
MOVE16_F = 0x37
MOVE32_8 = 0x38
MOVE32_16 = 0x39
MOVE32_32 = 0x3A
MOVE32_F = 0x3B
MOVEF_8 = 0x3C
MOVEF_16 = 0x3D
MOVEF_32 = 0x3E
MOVEF_F = 0x3F
# BRANCH
JR = 0x40
JR_FALSE = 0x41
JR_TRUE = 0x42
JR_NAN = 0x43
# COMPARE
CP_LT8 = 0x44
CP_LT16 = 0x45
CP_LT32 = 0x46
CP_LTF = 0x47
CP_GT8 = 0x48
CP_GT16 = 0x49
CP_GT32 = 0x4A
CP_GTF = 0x4B
CP_EQ8 = 0x4C
CP_EQ16 = 0x4D
CP_EQ32 = 0x4E
CP_EQF = 0x4F
CP_NEQ8 = 0x50
CP_NEQ16 = 0x51
CP_NEQ32 = 0x52
CP_NEQF = 0x53
CP_LTEQ8 = 0x54
CP_LTEQ16 = 0x55
CP_LTEQ32 = 0x56
CP_LTEQF = 0x57
CP_GTEQ8 = 0x58
CP_GTEQ16 = 0x59
CP_GTEQ32 = 0x5A
CP_GTEQF = 0x5B
# SELECT
SELECT8 = 0x5C
SELECT16 = 0x5D
SELECT32 = 0x5E
SELECTF = 0x5F
SYSTEM = 0x60
PORT_CNV_OUTPUT = 0x61
PORT_CNV_INPUT = 0x62
NOTE_TO_FREQ = 0x63
# BRANCH
JR_LT8 = 0x64
JR_LT16 = 0x65
JR_LT32 = 0x66
JR_LTF = 0x67
JR_GT8 = 0x68
JR_GT16 = 0x69
JR_GT32 = 0x6A
JR_GTF = 0x6B
JR_EQ8 = 0x6C
JR_EQ16 = 0x6D
JR_EQ32 = 0x6E
JR_EQF = 0x6F
JR_NEQ8 = 0x70
JR_NEQ16 = 0x71
JR_NEQ32 = 0x72
JR_NEQF = 0x73
JR_LTEQ8 = 0x74
JR_LTEQ16 = 0x75
JR_LTEQ32 = 0x76
JR_LTEQF = 0x77
JR_GTEQ8 = 0x78
JR_GTEQ16 = 0x79
JR_GTEQ32 = 0x7A
JR_GTEQF = 0x7B
# VM
INFO = 0x7C
STRINGS = 0x7D
MEMORY_WRITE = 0x7E
MEMORY_READ = 0x7F
# UI
UI_FLUSH = 0x80
UI_READ = 0x81
UI_WRITE = 0x82
UI_BUTTON = 0x83
UI_DRAW = 0x84
# TIMER
TIMER_WAIT = 0x85
TIMER_READY = 0x86
TIMER_READ = 0x87
# BREAKPOINT
BP0 = 0x88
BP1 = 0x89
BP2 = 0x8A
BP3 = 0x8B
BP_SET = 0x8C
MATH = 0x8D
RANDOM = 0x8E
# TIMER
TIMER_READ_US = 0x8F
# UI
KEEP_ALIVE = 0x90
# COM
COM_READ = 0x91
COM_WRITE = 0x92
# SOUND
SOUND = 0x94
SOUND_TEST = 0x95
SOUND_READY = 0x96
# INPUT
INPUT_SAMPLE = 0x97
INPUT_DEVICE_LIST = 0x98
INPUT_DEVICE = 0x99
INPUT_READ = 0x9A
INPUT_TEST = 0x9B
INPUT_READY = 0x9C
INPUT_READSI = 0x9D
INPUT_READEXT = 0x9E
INPUT_WRITE = 0x9F
# OUTPUT
OUTPUT_GET_TYPE = 0xA0
OUTPUT_SET_TYPE = 0xA1
OUTPUT_RESET = 0xA2
OUTPUT_STOP = 0xA3
OUTPUT_POWER = 0xA4
OUTPUT_SPEED = 0xA5
OUTPUT_START = 0xA6
OUTPUT_POLARITY = 0xA7
OUTPUT_READ = 0xA8
OUTPUT_TEST = 0xA9
OUTPUT_READY = 0xAA
OUTPUT_POSITION = 0xAB
OUTPUT_STEP_POWER = 0xAC
OUTPUT_TIME_POWER = 0xAD
OUTPUT_STEP_SPEED = 0xAE
OUTPUT_TIME_SPEED = 0xAF
OUTPUT_STEP_SYNC = 0xB0
OUTPUT_TIME_SYNC = 0xB1
OUTPUT_CLR_COUNT = 0xB2
OUTPUT_GET_COUNT = 0xB3
OUTPUT_PRG_STOP = 0xB4
# MEMORY
FILE = 0xC0
ARRAY = 0xC1
ARRAY_WRITE = 0xC2
ARRAY_READ = 0xC3
ARRAY_APPEND = 0xC4
MEMORY_USAGE = 0xC5
FILENAME = 0xC6
# READ
READ8 = 0xC8
READ16 = 0xC9
READ32 = 0xCA
READF = 0xCB
# WRITE
WRITE8 = 0xCC
WRITE16 = 0xCD
WRITE32 = 0xCE
WRITEF = 0xCF
# COM
COM_READY = 0xD0
COM_READDATA = 0xD1
COM_WRITEDATA = 0xD2
COM_GET = 0xD3
COM_SET = 0xD4
COM_TEST = 0xD5
COM_REMOVE = 0xD6
COM_WRITEFILE = 0xD7
MAILBOX_OPEN = 0xD8
MAILBOX_WRITE = 0xD9
MAILBOX_READ = 0xDA
MAILBOX_TEST = 0xDB
MAILBOX_READY = 0xDC
MAILBOX_CLOSE = 0xDD
# SPARE
TST = 0xFF
class DirectCommand(object):
"""Handles variable allocation and parameters for commands that can consist
of arbitrary bytecodes.
TODO: Better param verification?
"""
# These are inserted into the _global_params_types list so that commands
# that return mulitple values can have their values bundled together into
# tuples before they are returned.
_REPLY_TUPLE_OPEN_TOKEN = '_('
_REPLY_TUPLE_CLOSE_TOKEN = ')_'
def __init__(self):
"""Constructs a new, empty object."""
self._global_params_types = []
self._local_params_byte_count = 0
self._global_params_byte_count = 0
# Allocate space for the CommandType.
self._msg = [0x00]
# Allocate space for global and local param lengths.
self._msg.append(0x00)
self._msg.append(0x00)
def send(self, ev3_object):
"""Sends the message and parses the reply."""
if (2 == len(self._msg)):
raise DirectCommandError('Attempt to send an empty DirectCommand.')
self._msg[1] = (self._global_params_byte_count & 0xFF)
self._msg[2] = ((self._local_params_byte_count << 2) |
((self._global_params_byte_count >> 8) & 0x03))
if (self._global_params_byte_count):
self._msg[0] = CommandType.DIRECT_COMMAND_REPLY
reply = ev3_object.send_message_for_reply(self._msg)
return self._parse_reply(reply)
else:
self._msg[0] = CommandType.DIRECT_COMMAND_NO_REPLY
ev3_object.send_message(self._msg)
def safe_add(fn):
"""A wrapper for adding commands in a safe manner."""
def checked_add(*args):
# Wrappers aren't bound methods so they can't reference 'self'
# directly. However, 'self' will be provided as the first parameter
# when the wrapped method is called.
_self = args[0]
msg_len = len(_self._msg)
global_params_types_len = len(_self._global_params_types)
local_params_byte_count = _self._local_params_byte_count
global_params_byte_count = _self._global_params_byte_count
fn(*args)
if ((MAX_CMD_LEN < len(_self._msg)) or
(MAX_CMD_LEN < _self._global_params_byte_count) or
(MAX_LOCAL_VARIABLE_BYTES < _self._local_params_byte_count)):
del (_self._msg[msg_len:])
del (_self._global_params_types[global_params_types_len:])
_self._local_params_byte_count = local_params_byte_count
_self._global_params_byte_count = global_params_byte_count
raise DirectCommandError('Not enough space to add the ' +
'given func.')
return checked_add
@safe_add
def add_timer_wait(self, milliseconds):
"""Causes the thread to sleep for the specified number of milliseconds.
"""
local_var_tuple = self._allocate_local_param(DataFormat.DATA32)
self._msg.append(Opcode.TIMER_WAIT)
self._append_local_constant(milliseconds)
self._append_param(*local_var_tuple)
self._msg.append(Opcode.TIMER_READY)
self._append_param(*local_var_tuple)
@safe_add
def add_ui_draw_update(self):
"""Updates the screen (applies whatever drawing commands have been
issued since the last update).
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.UPDATE)
@safe_add
def add_ui_draw_clean(self):
"""Fills the screen with LCDColor.BACKGROUND."""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.CLEAN)
@safe_add
def add_ui_draw_fillwindow(self, lcd_color, start_y, count):
"""Fills the window with count rows of the given LCDColor starting at
row start_y.
NOTE: Starting at 0 with a size of 0 will clear the window. This seems
to be the way the CLEAN command is implemented.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.FILLWINDOW)
self._append_param(lcd_color)
self._append_param(start_y, ParamType.LC2)
self._append_param(count, ParamType.LC2)
@safe_add
def add_ui_draw_pixel(self, lcd_color, xy):
"""Draws a pixel at the given (x, y)."""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.PIXEL)
self._append_param(lcd_color)
self._append_param(xy[0], ParamType.LC2)
self._append_param(xy[1], ParamType.LC2)
@safe_add
def add_ui_draw_line(self, lcd_color, start_xy, end_xy):
"""Draws a line from the start (x, y) to the end (x, y)."""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.LINE)
self._append_param(lcd_color)
self._append_param(start_xy[0], ParamType.LC2)
self._append_param(start_xy[1], ParamType.LC2)
self._append_param(end_xy[0], ParamType.LC2)
self._append_param(end_xy[1], ParamType.LC2)
@safe_add
def add_ui_draw_dotline(self, lcd_color,
start_xy,
end_xy,
on_pixels,
off_pixels):
"""Draws a line from the start (x, y) to the end (x, y). The line will
be composed of a repeating pattern consisting of on_pixels followed by
off_pixels.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.DOTLINE)
self._append_param(lcd_color)
self._append_param(start_xy[0], ParamType.LC2)
self._append_param(start_xy[1], ParamType.LC2)
self._append_param(end_xy[0], ParamType.LC2)
self._append_param(end_xy[1], ParamType.LC2)
self._append_param(on_pixels, ParamType.LC2)
self._append_param(off_pixels, ParamType.LC2)
@safe_add
def add_ui_draw_rect(self, lcd_color, xy, width, height):
"""Draws a rectangle with (x, y) as the top-left corner and with width
and height dimensions.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.RECT)
self._append_param(lcd_color)
self._append_param(xy[0], ParamType.LC2)
self._append_param(xy[1], ParamType.LC2)
self._append_param(width, ParamType.LC2)
self._append_param(height, ParamType.LC2)
@safe_add
def add_ui_draw_fillrect(self, lcd_color, xy, width, height):
"""Draws a filled rectangle with (x, y) as the top-left corner and
with width and height dimensions.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.FILLRECT)
self._append_param(lcd_color)
self._append_param(xy[0], ParamType.LC2)
self._append_param(xy[1], ParamType.LC2)
self._append_param(width, ParamType.LC2)
self._append_param(height, ParamType.LC2)
@safe_add
def add_ui_draw_inverserect(self, xy, width, height):
"""Draws a rectangle with (x, y) as the top-left corner and with width
and height dimensions. Any pixel that this rectangle overlaps will have
its color flipped.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.INVERSERECT)
self._append_param(xy[0], ParamType.LC2)
self._append_param(xy[1], ParamType.LC2)
self._append_param(width, ParamType.LC2)
self._append_param(height, ParamType.LC2)
@safe_add
def add_ui_draw_circle(self, lcd_color, xy, radius):
"""Draws a circle centered at (x, y) with the specified radius."""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.CIRCLE)
self._append_param(lcd_color)
self._append_param(xy[0], ParamType.LC2)
self._append_param(xy[1], ParamType.LC2)
self._append_param(radius, ParamType.LC2)
@safe_add
def add_ui_draw_fillcircle(self, lcd_color, xy, radius):
"""Draws a filled circle centered at (x, y) with the specified radius.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.FILLCIRCLE)
self._append_param(lcd_color)
self._append_param(xy[0], ParamType.LC2)
self._append_param(xy[1], ParamType.LC2)
self._append_param(radius, ParamType.LC2)
@safe_add
def add_ui_draw_selectfont(self, font_type):
"""Selects the FontType that will be used by following calls to
add_ui_draw_text.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.SELECT_FONT)
self._append_param(font_type)
@safe_add
def add_ui_draw_text(self, lcd_color, xy, text_str):
"""Draws the given text with (x, y) as the top-left corner of the
bounding box. Use add_ui_draw_selectfont to select the font.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.TEXT)
self._append_param(lcd_color)
self._append_param(xy[0], ParamType.LC2)
self._append_param(xy[1], ParamType.LC2)
self._append_param(text_str, ParamType.LCS)
@safe_add
def add_ui_draw_topline(self, topline_enabled):
"""Enables or disables the display of the menu bar at the top of the
screen that normally displays status icons such as the battery
indicator.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.TOPLINE)
self._append_param(int(topline_enabled))
@safe_add
def add_ui_draw_store(self, ui_level_no=0):
"""If ui_level_no is zero then this function saves the current screen
content so that it be restored later using add_ui_draw_restore.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.STORE)
self._append_param(ui_level_no)
@safe_add
def add_ui_draw_restore(self, ui_level_no=0):
"""Restores the screen content that was previously saved using
add_ui_draw_store.
"""
self._msg.append(Opcode.UI_DRAW)
self._msg.append(UIDrawSubcode.RESTORE)
self._append_param(ui_level_no)
@safe_add
def add_ui_button_pressed(self, button_type):
"""Returns True if the specified ButtonType button is being pressed."""
self._msg.append(Opcode.UI_BUTTON)
self._msg.append(UIButtonSubcode.PRESSED)
self._append_param(button_type)
self._append_reply_param(DataFormat.BOOL)
@safe_add
def add_keep_alive(self):
"""Resets the sleep timer and returns the sleep timer's new value in
minutes.
"""
self._msg.append(Opcode.KEEP_ALIVE)
self._append_reply_param(DataFormat.DATA8)
@safe_add
def add_input_device_get_typemode(self, input_port,
layer=USB_CHAIN_LAYER_MASTER):
"""Returns the DeviceType and mode for the given input_port. The mode
value depends on the type of the device.
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.GET_TYPEMODE)
self._append_param(layer)
self._append_param(input_port)
self._global_params_types.append(self._REPLY_TUPLE_OPEN_TOKEN)
self._append_reply_param(DataFormat.DATA8)
self._append_reply_param(DataFormat.DATA8)
self._global_params_types.append(self._REPLY_TUPLE_CLOSE_TOKEN)
@safe_add
def add_input_device_get_name(self, input_port,
layer=USB_CHAIN_LAYER_MASTER):
"""Returns a string describing the device that is located at the
specified port i.e. 'NONE' or 'US-DIST-CM'.
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.GET_NAME)
self._append_param(layer)
self._append_param(input_port)
self._append_param(MAX_NAME_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_input_device_get_modename(self, input_port,
mode,
layer=USB_CHAIN_LAYER_MASTER):
"""Returns a string describing the specified mode of the device that is
located at the given port. For example, for an ultrasonic sensor mode
0 will return 'US-DIST-CM' and mode 1 will return 'US-DIST-IN'.
NOTE: Reading invalid modes can corrupt the reply buffer.
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.GET_MODENAME)
self._append_param(layer)
self._append_param(input_port)
self._append_param(mode)
self._append_param(MAX_NAME_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_input_device_get_minmax(self, input_port,
layer=USB_CHAIN_LAYER_MASTER):
""""""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.GET_MINMAX)
self._append_param(layer)
self._append_param(input_port)
self._global_params_types.append(self._REPLY_TUPLE_OPEN_TOKEN)
self._append_reply_param(DataFormat.DATA_F)
self._append_reply_param(DataFormat.DATA_F)
self._global_params_types.append(self._REPLY_TUPLE_CLOSE_TOKEN)
@safe_add
def add_input_device_get_changes(self, input_port,
layer=USB_CHAIN_LAYER_MASTER):
"""Returns the number of positive changes since the last time
CLR_CHANGES was called (i.e. the number of times that a touch sensor
has been pressed).
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.GET_CHANGES)
self._append_param(layer)
self._append_param(input_port)
self._append_reply_param(DataFormat.DATA_F)
@safe_add
def add_input_device_get_bumps(self, input_port,
layer=USB_CHAIN_LAYER_MASTER):
"""Returns the number of negative changes since the last time
CLR_CHANGES was called (i.e. the number of times that a touch sensor
has been released).
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.GET_BUMPS)
self._append_param(layer)
self._append_param(input_port)
self._append_reply_param(DataFormat.DATA_F)
@safe_add
def add_input_device_clr_changes(self, input_port,
layer=USB_CHAIN_LAYER_MASTER):
"""Returns the number of negative changes since the last time
CLR_CHANGES was called (i.e. the number of times that a touch sensor
has been released).
NOTE: Does not clear the accumulated angle measurement for the EV3
gyro sensor.
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.CLR_CHANGES)
self._append_param(layer)
self._append_param(input_port)
@safe_add
def add_input_device_clr_all(self, layer=USB_CHAIN_LAYER_MASTER):
"""Clears all of the input device values."""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.CLR_ALL)
self._append_param(layer)
@safe_add
def add_input_device_ready_si(self, input_port,
mode=-1,
device_type=0,
layer=USB_CHAIN_LAYER_MASTER):
"""Waits until the device on the specified InputPort is ready and then
returns its value as a standard unit.
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.READY_SI)
self._append_param(layer)
self._append_param(input_port)
self._append_param(device_type)
self._append_param(mode)
self._append_param(1) # Number of values
self._append_reply_param(DataFormat.DATA_F)
@safe_add
def add_input_device_ready_raw(self, input_port,
mode=-1,
device_type=0,
layer=USB_CHAIN_LAYER_MASTER):
"""Waits until the device on the specified InputPort is ready and then
returns its value as a raw value.
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.READY_RAW)
self._append_param(layer)
self._append_param(input_port)
self._append_param(device_type)
self._append_param(mode)
self._append_param(1) # Number of values
self._append_reply_param(DataFormat.DATA32)
@safe_add
def add_input_device_ready_percent(self, input_port,
mode=-1,
device_type=0,
layer=USB_CHAIN_LAYER_MASTER):
"""Waits until the device on the specified InputPort is ready and then
returns its value as a percentage.
"""
self._msg.append(Opcode.INPUT_DEVICE)
self._msg.append(InputDeviceSubcode.READY_PCT)
self._append_param(layer)
self._append_param(input_port)
self._append_param(device_type)
self._append_param(mode)
self._append_param(1) # Number of values
self._append_reply_param(DataFormat.DATA8)
@safe_add
def add_sound_tone(self, volume,
frequency_hz,
duration_ms,
layer=USB_CHAIN_LAYER_MASTER):
"""Plays the tone at the given volume and frequency for the given
duration_ms milliseconds.
"""
self._msg.append(Opcode.SOUND)
self._msg.append(SoundSubcode.TONE)
self._append_param(volume)
self._append_param(frequency_hz, ParamType.LC2)
self._append_param(duration_ms, ParamType.LC2)
@safe_add
def add_sound_play(self, volume, filename):
"""Plays the sound file with the given name at the specified volume.
The default sound files are located in the '/home/root/lms2012/sys/ui/'
directory and include Startup.rsf, PowerDown.rsf, OverpowerAlert.rsf,
GeneralAlarm.rsf, DownloadSucces.rsf, and Click.rsf.
NOTE: Do not include the '.rsf' extension in the filename.
"""
self._msg.append(Opcode.SOUND)
self._msg.append(SoundSubcode.PLAY)
self._append_param(volume)
self._append_param(filename, ParamType.LCS)
@safe_add
def add_ui_read_get_fw_vers(self):
"""Returns the FW version as a string in the form 'VX.XXX'."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_FW_VERS)
self._append_param(MAX_VERSION_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_ui_read_get_hw_vers(self):
"""Returns the HW version as a string in the form 'VX.XXX'."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_HW_VERS)
self._append_param(MAX_VERSION_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_ui_read_get_fw_build(self):
"""Returns the firmware build as a string in the form 'XXXXXXXXXX'."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_FW_BUILD)
self._append_param(MAX_VERSION_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_ui_read_get_os_vers(self):
"""Returns the OS version as a string in the form 'Linux X.X.XX'."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_OS_VERS)
self._append_param(MAX_VERSION_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_ui_read_get_os_build(self):
"""Returns the OS build as a string in the form 'XXXXXXXXXX'."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_OS_BUILD)
self._append_param(MAX_VERSION_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_ui_read_get_version(self):
"""Returns the Lego Mindstorms version as a string in the form
'LMS2012 VX.XXX(<TIMESTAMP>)'.
"""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_VERSION)
self._append_param(MAX_VERSION_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_ui_read_get_ip(self):
"""Returns the IP address as a string."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_IP)
self._append_param(MAX_VERSION_STR_LEN, ParamType.LC2)
self._append_reply_param((DataFormat.DATA_S, MAX_NAME_STR_LEN))
@safe_add
def add_ui_read_get_vbatt(self):
"""Gets the current battery voltage. According to the constants that are
defined in 'lms2012.h', the rechargeable battery should be in the range
of [6.0, 7.1] and normal batteries should be in the range of [4.5, 6.2].
"""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_VBATT)
self._append_reply_param(DataFormat.DATA_F)
@safe_add
def add_ui_read_get_lbatt(self):
"""Gets the current battery level as a percentage."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_LBATT)
self._append_reply_param(DataFormat.DATA_PCT)
@safe_add
def add_ui_read_get_ibatt(self):
"""Gets the current battery discharge amperage."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_IBATT)
self._append_reply_param(DataFormat.DATA_F)
@safe_add
def add_ui_read_get_tbatt(self):
"""Gets the current battery temperature rise."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_TBATT)
self._append_reply_param(DataFormat.DATA_F)
@safe_add
def add_ui_read_get_imotor(self):
"""Gets the amount of current that the motors are using."""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_IMOTOR)
self._append_reply_param(DataFormat.DATA_F)
@safe_add
def add_ui_read_get_sdcard(self):
"""Returns the following information about the SD card:
(<SD_CARD_OK>, <TOTAL_KBYTES>, <FREE_KBYTES>). The SD_CARD_OK value is
a boolean.
"""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_SDCARD)
self._global_params_types.append(self._REPLY_TUPLE_OPEN_TOKEN)
self._append_reply_param(DataFormat.BOOL)
self._append_reply_param(DataFormat.DATA32)
self._append_reply_param(DataFormat.DATA32)
self._global_params_types.append(self._REPLY_TUPLE_CLOSE_TOKEN)
@safe_add
def add_ui_read_get_usbstick(self):
"""Returns the following information about the USB stick:
(<USB_STICK_OK>, <TOTAL_KBYTES>, <FREE_KBYTES>). The USB_STICK_OK value
is a boolean.
"""
self._msg.append(Opcode.UI_READ)
self._msg.append(UIReadSubcode.GET_SDCARD)
self._global_params_types.append(self._REPLY_TUPLE_OPEN_TOKEN)
self._append_reply_param(DataFormat.BOOL)
self._append_reply_param(DataFormat.DATA32)
self._append_reply_param(DataFormat.DATA32)
self._global_params_types.append(self._REPLY_TUPLE_CLOSE_TOKEN)
@safe_add
def add_output_get_type(self, output_port, layer=USB_CHAIN_LAYER_MASTER):
"""Returns the DeviceType of the device that is connected to the
specified OutputPort.
"""
self._msg.append(Opcode.OUTPUT_GET_TYPE)
self._append_param(layer)
self._append_param(OUTPUT_CHANNEL_TO_INDEX[output_port])
self._append_reply_param(DataFormat.DATA8)
@safe_add
def add_output_set_type(self, output_port,
output_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Sets the DeviceType of the device that is connected to the
specified OutputPort.
TODO: d_pwm.c says this only works with type TACHO or MINI_TACHO.
TODO: Debug this. Not sure how to verify that this works.
It seems to be implemented in d_pwm.c
"""
self._msg.append(Opcode.OUTPUT_SET_TYPE)
self._append_param(layer)
self._append_param(output_type)
@safe_add
def add_output_reset(self, output_port_mask,
layer=USB_CHAIN_LAYER_MASTER):
"""Resets the tacho count and timer of the motor(s) described by the
output_port_mask parameter. Should be called when initializing a
motor?
"""
self._msg.append(Opcode.OUTPUT_RESET)
self._append_param(layer)
self._append_param(output_port_mask)
@safe_add
def add_output_stop(self, output_port_mask,
stop_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Stops the motor(s) described by the output_port_mask parameter.
The stop_byte parameter defines whether the motor will BRAKE or COAST.
"""
self._msg.append(Opcode.OUTPUT_STOP)
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(stop_type)
@safe_add
def add_output_power(self, output_port_mask,
power,
layer=USB_CHAIN_LAYER_MASTER):
"""Sets the power for the motor(s) described by the output_port_mask
parameter. Power values should be in the range [-100, 100]. Note that
add_output_start needs to be called before the motor will start moving.
"""
self._msg.append(Opcode.OUTPUT_POWER)
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(power)
@safe_add
def add_output_speed(self, output_port_mask,
speed,
layer=USB_CHAIN_LAYER_MASTER):
"""Sets the speed for the motor(s) described by the output_port_mask
parameter. Speed values should be in the range [-100, 100]. Note that
add_output_start needs to be called before the motor will start moving.
"""
self._msg.append(Opcode.OUTPUT_SPEED);
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(speed)
@safe_add
def add_output_start(self, output_port_mask,
layer=USB_CHAIN_LAYER_MASTER):
"""Starts the motor(s) described by the output_port_mask
parameter.
"""
self._msg.append(Opcode.OUTPUT_START)
self._append_param(layer)
self._append_param(output_port_mask)
@safe_add
def add_output_polarity(self, output_port_mask,
polarity_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Sets the polarity for the motor(s) described by the
output_port_mask parameter.
"""
self._msg.append(Opcode.OUTPUT_POLARITY)
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(polarity_type)
@safe_add
def add_output_read(self, output_port, layer=USB_CHAIN_LAYER_MASTER):
"""Reads the speed and tacho pulses for the given output_port and
returns them as a tuple in the form (SPEED, TACHO_PULSES).
"""
self._msg.append(Opcode.OUTPUT_READ)
self._append_param(layer)
self._append_param(OUTPUT_CHANNEL_TO_INDEX[output_port])
self._global_params_types.append(self._REPLY_TUPLE_OPEN_TOKEN)
self._append_reply_param(DataFormat.DATA8)
self._append_reply_param(DataFormat.DATA32)
self._global_params_types.append(self._REPLY_TUPLE_CLOSE_TOKEN)
@safe_add
def add_output_ready(self, output_port_mask,
layer=USB_CHAIN_LAYER_MASTER):
"""Waits for the outputs in the output_port_mask to report that
they are ready before executing the next opcode. For example, if two
consecutive motor commands are used with the same OutputPort putting
this opcode between them ensures that the first command finishes
before the second one is started.
"""
self._msg.append(Opcode.OUTPUT_READY)
self._append_param(layer)
self._append_param(output_port_mask)
@safe_add
def add_output_position(self, output_port_mask,
position,
layer=USB_CHAIN_LAYER_MASTER):
"""Sets the position of the specied OutputPort(s)."""
self._msg.append(Opcode.OUTPUT_POSITION)
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(position, ParamType.LC4)
@safe_add
def add_output_step_power(self, output_port_mask,
power,
ramp_up_steps,
steps,
ramp_down_steps,
stop_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Ramps up the power for the motor(s) described by the
output_port_mask, holds for steps, and then ramps down. It is not
necessary to call add_output_start in addition to this opcode.
NOTE: The EV3 will NOT wait for this operation to complete before
executing the next opcode unless add_output_ready is used.
"""
self._msg.append(Opcode.OUTPUT_STEP_POWER);
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(power)
self._append_param(ramp_up_steps, ParamType.LC4)
self._append_param(steps, ParamType.LC4)
self._append_param(ramp_down_steps, ParamType.LC4)
self._append_param(stop_type)
@safe_add
def add_output_time_power(self, output_port_mask,
power,
ramp_up_ms,
time_ms,
ramp_down_ms,
stop_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Ramps up the power for the motor(s) described by the
output_port_mask, holds for time_ms, and then ramps down. It is not
necessary to call add_output_start in addition to this opcode.
NOTE: The EV3 will NOT wait for this operation to complete before
executing the next opcode unless add_output_ready is used.
"""
self._msg.append(Opcode.OUTPUT_TIME_POWER);
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(power)
self._append_param(ramp_up_ms, ParamType.LC4)
self._append_param(time_ms, ParamType.LC4)
self._append_param(ramp_down_ms, ParamType.LC4)
self._append_param(stop_type)
@safe_add
def add_output_step_speed(self, output_port_mask,
speed,
ramp_up_steps,
steps,
ramp_down_steps,
stop_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Ramps up the power for the motor(s) described by the
output_port_mask, holds for steps, and then ramps down. It is not
necessary to call add_output_start in addition to this opcode.
NOTE: The EV3 will NOT wait for this operation to complete before
executing the next opcode unless add_output_ready is used.
"""
self._msg.append(Opcode.OUTPUT_STEP_SPEED);
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(speed)
self._append_param(ramp_up_steps, ParamType.LC4)
self._append_param(steps, ParamType.LC4)
self._append_param(ramp_down_steps, ParamType.LC4)
self._append_param(stop_type)
@safe_add
def add_output_time_speed(self, output_port_mask,
speed,
ramp_up_ms,
time_ms,
ramp_down_ms,
stop_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Ramps up the power for the motor(s) described by the
output_port_mask, holds for time_ms, and then ramps down. It is not
necessary to call add_output_start in addition to this opcode.
NOTE: The EV3 will NOT wait for this operation to complete before
executing the next opcode unless add_output_ready is used.
"""
self._msg.append(Opcode.OUTPUT_TIME_SPEED);
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(speed)
self._append_param(ramp_up_ms, ParamType.LC4)
self._append_param(time_ms, ParamType.LC4)
self._append_param(ramp_down_ms, ParamType.LC4)
self._append_param(stop_type)
@safe_add
def add_output_step_sync(self, output_port_mask,
speed,
turn_ratio,
step,
stop_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Sets the speed for the two given motors in the following fashion:
[-200, -101]: Turn right with right motor running in reverse
[-100, -1]: Turn right with right motor slowed
0: Both motors in sync in the same direction
[1, 100]: Turn left with left motor slowed
[101, 200]: Turn left with left motor running in reverse
It is not necessary to call add_output_start in addition to this opcode.
NOTE: The EV3 will NOT wait for this operation to complete before
executing the next opcode unless add_output_ready is used.
"""
self._msg.append(Opcode.OUTPUT_STEP_SYNC);
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(speed)
self._append_param(turn_ratio, ParamType.LC2)
self._append_param(step, ParamType.LC4)
self._append_param(stop_type)
@safe_add
def add_output_time_sync(self, output_port_mask,
speed,
turn_ratio,
time,
stop_type,
layer=USB_CHAIN_LAYER_MASTER):
"""Sets the speed for the two given motors in the following fashion:
[-200, -101]: Turn right with right motor running in reverse
[-100, -1]: Turn right with right motor slowed
0: Both motors in sync in the same direction
[1, 100]: Turn left with left motor slowed
[101, 200]: Turn left with left motor running in reverse
It is not necessary to call add_output_start in addition to this opcode.
NOTE: The EV3 will NOT wait for this operation to complete before
executing the next opcode unless add_output_ready is used.
"""
self._msg.append(Opcode.OUTPUT_TIME_SYNC);
self._append_param(layer)
self._append_param(output_port_mask)
self._append_param(speed)
self._append_param(turn_ratio, ParamType.LC2)
self._append_param(time, ParamType.LC4)
self._append_param(stop_type)
@safe_add
def add_output_clr_count(self, output_port_mask,
layer=USB_CHAIN_LAYER_MASTER):
"""Clears the tacho count for the given OutputPort(s) when in sensor
mode.
"""
self._msg.append(Opcode.OUTPUT_CLR_COUNT);
self._append_param(layer)
self._append_param(output_port_mask)
@safe_add
def add_output_get_count(self, output_port,
layer=USB_CHAIN_LAYER_MASTER):
"""Returns the tacho count for the given OutputPort when in sensor
mode.
"""
self._msg.append(Opcode.OUTPUT_GET_COUNT);
self._append_param(layer)
self._append_param(OUTPUT_CHANNEL_TO_INDEX[output_port])
self._append_reply_param(DataFormat.DATA32)
@safe_add
def add_set_leds(self, led_pattern):
"""Sets the LEDs on the front of the brick to the specified item in
the LEDPattern enumeration.
"""
self._msg.append(Opcode.UI_WRITE)
self._msg.append(UIWriteSubcode.LED)
self._append_param(led_pattern)
def _parse_reply(self, buf):
result = []
index = 0
if (ReplyType.DIRECT_REPLY_ERROR == buf[0]):
raise DirectCommandError('The DirectCommand failed.')
if (self._global_params_byte_count != (len(buf) - 1)):
raise DirectCommandError('The data returned by the ' +
'command was smaller than expected.')
# The items in the reply are grouped into tuples. Each tuple represents
# the reply to a command that returns multiple values.
sub_tuple = None
for item in self._global_params_types:
value = None
length = 0
if (self._REPLY_TUPLE_OPEN_TOKEN == item):
sub_tuple = []
continue
elif (self._REPLY_TUPLE_CLOSE_TOKEN == item):
result.append(tuple(sub_tuple))
sub_tuple = None
continue
if (isinstance(item, tuple)):
value, length = self._parse_param(buf, (index + 1), *item)
else:
# Ensure that the alignment is correct.
data_len = DATA_FORMAT_LENS[item]
pad = (index % data_len)
if (0 != pad):
pad = (data_len - pad)
index += pad
value, length = self._parse_param(buf, (index + 1), item)
if (sub_tuple is not None):
sub_tuple.append(value)
else:
result.append(value)
index += length
return tuple(result)
def _parse_param(self, buf, index, data_format, data_len=None):
value = None
length = 1
if (DataFormat.DATA_S == data_format):
value = message.parse_null_terminated_str(buf, index, data_len)
length = data_len
elif (DataFormat.HND == data_format):
value = (buf[index] & ~ParamType.HND)
elif (DataFormat.DATA_F == data_format):
value = message.parse_float(buf, index)
length = DATA_FORMAT_LENS[DataFormat.DATA_F]
elif (DataFormat.BOOL == data_format):
value = bool(buf[index])
length = 1
else:
length = DATA_FORMAT_LENS[data_format]
if (1 == length):
value = buf[index]
elif (2 == length):
value = message.parse_u16(buf, index)
elif (4 == length):
value = message.parse_u32(buf, index)
else:
raise DirectCommandError('Unexpected ParamType: %d' %
param_type)
return (value, length)
def _append_reply_param(self, reply_format):
"""Global parameters are stored in the tx buffer on the brick so
their values are returned in the message reply.
"""
data_len = None
if (not isinstance(reply_format, tuple)):
# Ensure that the alignment is correct.
data_len = DATA_FORMAT_LENS[reply_format]
pad = (self._global_params_byte_count % data_len)
if (pad):
pad = (data_len - pad)
self._global_params_byte_count += pad
else:
data_len = reply_format[1]
# Use as few bits as possible to save space in message buffer.
param_type = ParamType.GV1
if (0xFFFF < self._global_params_byte_count):
param_type = ParamType.GV4
elif (0xFF < self._global_params_byte_count):
param_type = ParamType.GV2
self._append_param(self._global_params_byte_count, param_type)
self._global_params_types.append(reply_format)
self._global_params_byte_count += data_len
def _allocate_local_param(self, data_format):
"""Local parameters are essentially stack variables so they are NOT
included in the reply from the brick. This function returns an index
that can be used to access a new local variable of the given DataFormat.
"""
# Ensure that the alignment is correct.
data_len = DATA_FORMAT_LENS[data_format]
pad = (self._local_params_byte_count % data_len)
if (pad):
pad = (data_len - pad)
self._local_params_byte_count += pad
# Use as few bits as possible to save space in message buffer.
param_type = ParamType.LV1
if (0xFFFF < self._local_params_byte_count):
param_type = ParamType.LV4
elif (0xFF < self._local_params_byte_count):
param_type = ParamType.LV2
index = self._local_params_byte_count
self._local_params_byte_count += data_len
return (index, param_type)
def _append_local_constant(self, val):
""""Appends an immediate value as a local constant."""
param_type = None
if (isinstance(val, int)):
num_bits = int.bit_length(val)
if (num_bits > 16):
param_type = ParamType.LC4
elif (num_bits > 8):
param_type = ParamType.LC2
elif (num_bits > 6):
param_type = ParamType.LC1
else:
param_type = ParamType.LC0
elif (isinstance(val, float)):
param_type = ParamType.FLOAT
elif (isinstance(val, str)):
param_type = ParamType.LCS
else:
raise NotImplementedError('Unknown local constant type.')
self._append_param(val, param_type)
def _append_param(self, val, param_type=ParamType.LC1):
"""All parameters need to be prefixed with their type so the VM knows
how to interpret the following data. The reply_format parameter is
used when a reply is expected.
"""
if (ParamType.PRIMPAR_LABEL == param_type):
raise NotImplementedError('ParamType.PRIMPAR_LABEL')
elif (ParamType.LCS == param_type):
self._msg.append(param_type)
message.append_str(self._msg, val)
elif (ParamType.LC0 == param_type):
self._msg.append(ParamType.LC0 | (0x3F & val))
elif (ParamType.HND == param_type):
self._msg.append(ParamType.HND | val)
elif (ParamType.ADR == param_type):
self._msg.append(ParamType.ADR | val)
elif (ParamType.GV0 == param_type):
self._msg.append(ParamType.GV0 | (0x1F & val))
elif (ParamType.FLOAT == param_type):
self._msg.append(ParamType.LC4)
message.append_float(self._msg, val)
else:
length = PARAM_TYPE_LENS[param_type]
self._msg.append(param_type)
if (1 == length):
message.append_u8(self._msg, val)
elif (2 == length):
message.append_u16(self._msg, val)
elif (4 == length):
message.append_u32(self._msg, val)
else:
raise DirectCommandError('Unexpected ParamType:' +
' %d' % param_type)