Прямые команды EV3 — урок 1. Искусство бездействия

Java Mac Linux блютус
Прямые команды EV3 — урок 1. Искусство бездействия

LEGO EV3 — отличный игровой инструмент. Стандартным способом программирования является графический инструмент программирования LEGO. Вы можете писать программы, передавать их в модуль EV3 и запускать. Но есть и другой способ взаимодействия с вашим EV3. Думайте об этом как о сервере и отправляйте ему команды, которые будут отвечать данными и/или поведением. В этом случае машина, на которой работает ваша программа, является клиентом. Это открывает новые интересные перспективы. Если программа работает на вашем смартфоне, вы получите большую интерактивность и удобство. Если ваш клиент ПК или ноутбук, вы получите удобную клавиатуру и монитор. Еще одна новая возможность — объединить несколько роботов в один. EV3. Один клиент взаимодействует с несколькими серверами, что позволяет роботу иметь бесконечное количество двигателей и датчиков. Или относитесь к своему EV3 как к машине, производящей данные. Клиенты могут постоянно получать данные от датчиков EV3, что также открывает новые возможности. Если вы хотите войти в этот новый мир, вы должны использовать прямые команды EV3, что требует некоторой работы с вашей стороны. Если вы готовы инвестировать в него, читайте дальше, в противном случае получайте удовольствие, играя со своим EV3, и ждите, пока другие сделают новые крутые приложения.

Прежде чем мы перейдем к формальностям, давайте взглянем на протокол связи EV3 и Особенности прямых команд LEGO. Ваш EV3 предлагает три типа связи: Bluetooth, Wi-Fi и USB. Bluetooth и USB можно использовать без дополнительного оборудования, для связи по WiFi требуется адаптер. Все три позволяют вам разрабатывать приложения, которые запускаются на любом компьютере для связи с вашим блоком EV3. Возможно, вы знаете о предшественнике EV3, NXT и его коммуникационном протоколе. Он предоставляет около 20 системных команд, похожих на вызовы функций. LEGO изменил свою философию, EV3 предоставляет синтаксис команд в машинном коде. С одной стороны, это означает свободу реализации реальных алгоритмов, но с другой стороны, становится сложнее начать работу.

Как я выяснил, официальная документация носит чисто технический характер. В них отсутствуют мотивационные и воспитательные аспекты. Я надеюсь, что этот документ станет для вас правильным входом в ваш EV3. Если вы уже являетесь программистом или пытаетесь им стать, вы погрузитесь в мир битов и байтов. Если нет, это может быть сложно, но единственный вариант — держать руки врозь. Попробуйте, и благодаря эффективному общению с LEGO EV3 вы многое узнаете о машинном коде, который лежит в основе всех компьютеров.

Документация, которую нужно знать

Компания LEGO выпустила очень хорошую и подробную документацию, которую вы можете скачать здесь:woooooooo.Lego.com/ru-basics/minds…. Для наших целей вам обязательно нужно посмотреть документацию, которую вы можете найти в заголовкеПродвинутые пользователи — комплекты разработчика (ПК/MAC)нашел под заголовком.EV3 Firmware Developer KitСправочник по прямым командам LEGO EV3. Я надеюсь, что вы можете прочитать его в глубину.

Существует коммуникационная библиотека для C#, которая взаимодействует с LEGO EV3 с помощью прямых команд. Если вам нравится использовать готовое программное обеспечение и вам нравится C#, это может быть выходом:www.monobrick.dk.

Моей первоначальной мыслью было не выпускать исходный код. Программирование приносит больше удовольствия, когда функциональность расширяется постепенно. Ошибки — это часть игры, и найти их сложно, но это часть жизни каждого программиста. Короткий вывод: по мере роста кода до определенного размера и сложности первоначальная идея становится нереалистичной. Я опубликовал код на Github, вы можете скачать его здесь:ev3-python3.

Урок 1 Искусство бездействия

Вы сделали это, вы действительно хотели вмешаться, это здорово! Этот урок посвящен очень простому общению. Мы реализуем первый цикл отправки-ответа. Отправьте сообщение на EV3 через Wi-Fi, Bluetooth или USB, и вы получите четкий ответ. Не задерживайте дыхание, мы не начнем с потрясающего приложения. Вместо этого он ничего не делает. Звучит меньше, чем есть на самом деле, но если вам удастся это сделать, откиньтесь на спинку кресла и почувствуйте себя счастливым, значит, вы на правильном пути.

Краткое введение в наименования битов и байтов

Возможно, вы уже знаете, как записывать двоичные и шестнадцатеричные числа, что такое порядок следования байтов и т. д. Если вы действительно можете записать значение 156 в формате представления 4-байтового целого числа с прямым порядком байтов, вы можете пропустить этот раздел. Если вы не можете, вам нужно прочитать это, потому что вам действительно нужно это знать.

Начнем с основ! Почти все современные компьютеры группируют 8 бит в 1 байт и адресуют их побайтно в памяти (ваш EV3 — современный компьютер, и таковым он и является). Далее мы используем следующие обозначения для представления двоичных чисел:0b 1001 1100.

ведущий0bСкажите нам, что далее следует двоичное число, 8 цифр каждого байта делятся на две части 4 и 4. Это двоичное представление числа 156, вы можете представить его как: 156 = 1.128 + 064 + 032 + 116 + 18 + 14 + 02 + 01. Могут быть сделаны дополнительные интерпретации одних и тех же байтов. Его можно рассматривать как последовательность из 8 токенов или это может быть код ASCII для символа £. Интерпретация зависит от контекста. В настоящее время мы сосредоточены на цифрах.

Двоичная запись очень длинная, поэтому принято записывать полубайты в виде шестнадцатеричных чисел, где буквы от A до F представляют числа от 10 до 15. Шестнадцатеричная запись более компактна, и ее легко преобразовать в двоичную запись. Это связано с тем, что шестнадцатеричное число представляет собой полубайт. В шестнадцатеричном виде (здесь значение равно 156) это означает: 0x 9C. Вы можете думать об этом как: 156 = 916 + 121. Ведущий 0x говорит нам, что следующее за ним шестнадцатеричное число. Из-за его компактности мы можем записывать и читать большие числа. В виде 4-байтового целого числа записывается значение 156: 0x 00 00 00 9C.

Мы будем разделять байты двоеточием «:» или вертикальной чертой «|». Мы используем вертикальные черты для разделения высокого уровня и двоеточия для разделения низкого уровня. Мы запишем 2-байтовое целое число со значением 156 как: 0x|00:9C|. Теперь мы можем удерживать список значений в одной строке. Последовательность 255 (беззнаковое 1-байтовое целое), 156 (2-байтовое целое) и 65536 (4-байтовое целое) может быть записана как: 0x|FF|00:9C|00:01:00:00| .

А отрицательные числа? Большинство компьютерных языков различают знаковые и беззнаковые целые числа. Если целые числа имеют знак, их первым битом является знак минус, а целые числа относятся к другому диапазону. Диапазон для 1-байтовых целых чисел со знаком составляет от -128 до 127, диапазон для 2-байтовых целых чисел со знаком составляет от -32 768 до 32 767 и так далее. Отрицательные значения рассчитываются путем прибавления минимального значения (-128, -32 768 и т. д.) к значению оставшихся беззнаковых флагов. Минимальное значение 1-байтового целого числа со знаком, -128 записывается как 0b 1000 0000 или 0x|80|, 2-байтовое целое число со знаком -1 (-32 768 + 32 767): 0b 1111 1111 1111 1111 или 0x|FF:FF|

это чтомаленький конецШерстяная ткань? Ладно, я больше не буду держать это в секрете. Формат с прямым порядком байтов меняет положение байтов на противоположное (тот, который вы обычно используете, называемыйбольшой конец). 2-байтовое целочисленное значение 156 записывается в формате с прямым порядком байтов: 0x|9C:00|.

Может быть, это звучит как плохая шутка, но мне очень жаль, EV3 дает прямые команды читать и записывать все числа в прямом порядке, это не моя вина. Но я могу тебя утешить. Во-первых, в этом курсе используются числа. Во-вторых, существуют хорошие инструменты для управления числами с прямым порядком байтов. В Python вы можете использоватьstructмодули, на Java,ByteBufferМожет быть ваш выбор.

прямая команда, которая ничего не делает

В первом примере показана самая простая из всех возможных прямых команд. Вы отправите сообщение на свой EV3 и ожидаете, что он ответит. Давайте посмотрим на сообщение, которое нужно отправить, оно состоит из 8 байтов следующим образом:

-------------------------       \ len \ cnt \ty\ hd  \op\       -------------------------    0x|06:00|2A:00|00|00:00|01|      -------------------------       \ 6   \ 42  \Re\ 0,0 \N \       \     \     \  \     \o \       \     \     \  \     \p \       -------------------------

Само сообщение представляет собой строку, начинающуюся с 0x. В верхней части сообщения вы увидите примечания о типе соответствующей части сообщения. Примечание о его значении отображается внизу. 8 байт сообщения состоят из следующих частей:

  • длина сообщения(байты 0, 1): первые два байта не являются частью самой прямой команды. Они являются частью протокола связи, которым в случае EV3 может быть Wi-Fi, Bluetooth или USB. Длина кодируется как 2-байтовое целое число без знака в формате с прямым порядком байтов, поэтому0x|06:00|Представляет значение 6.

  • счетчик сообщений(байты 2, 3): Следующие два байта являются отпечатком этой прямой команды. Счетчик сообщений будет включен в ответ и может использоваться для сопоставления прямой команды и ее ответа. Это также 2-байтовое целое число без знака в формате с прямым порядком байтов. В нашем случае установите счетчик сообщений на0x|2A:00|, который имеет значение 42.

  • тип сообщения(байт 4): Это может быть одно из следующих двух значений:

    • DIRECT_COMMAND_REPLY = 0x|00|
    • DIRECT_COMMAND_NO_REPLY = 0x|80|
      В нашем случае мы хотим, чтобы EV3 ответил на сообщение.
  • голова(байты 5, 6): Следующие два байта, последняя часть перед первой операцией, являются заголовком. Он содержит два числа, которые определяют размер памяти прямой команды (да, это множественное число, у нас есть две памяти, одна локальная и одна глобальная). Вскоре мы вернемся к особенностям этого объема памяти. На данный момент нам повезло, что нашей команде не нужна память, поэтому мы устанавливаем заголовок0x|00:00|.

  • действовать(начиная с 7 байта): в нашем случае один байт, что означает:opNOP = 0x|01|, ничего не делать, EV3 работает вхолостую.

Отправить сообщение на EV3

Наша задача — отправить сообщение, описанное выше, на EV3. Как это сделать? Вы можете выбрать один из трех протоколов связи: Bluetooth, Wi-Fi и USB, а также любой язык программирования, поддерживающий хотя бы один протокол связи. Ниже я покажу примеры для Python и Java. Если ваш любимый язык недоступен, было бы здорово перевести программу на ваш любимый компьютерный язык и отправить мне. Они будут размещены здесь.

блютус

Вам нужен доступ к компьютеру с включенным Bluetooth, и вам нужно включить Bluetooth на вашем EV3. Далее вам нужно соединить два устройства. Это можно инициировать с EV3 или вашего компьютера. Руководство пользователя EV3 подробно описывает этот процесс. Для справки вы можете найти учебные пособия в Интернете, вот ссылки на страницы LEGO:woooooooo.Lego.com/ru-basics/minds…. В процессе сопряжения вы увидите MAC-адрес вашего EV3. адрес. Вы должны обратить на это внимание. Кроме того, вы также можете на дисплее вашего EV3, вBrick Info / IDПрочтите приведенный ниже MAC-адрес, здесь представлена ​​шестнадцатеричная форма MAC-адреса EV3 без двоеточий, например 001653602591, MAC-адрес которого равен 00:16:53:60:25:91.

python

Вам необходимо выполнить следующие шаги:

  • скопируйте код вEV3_do_nothing_bluetooth.pyв файле.
  • изменить MAC-адрес с00:16:53:42:2B:99становится ценностью вашего EV3.
  • Откройте терминал и перейдите в каталог вашей программы.
  • набравpython3 EV3_do_nothing_bluetooth.pyзапустить его.
#!/usr/bin/env python3import socketimport structclass EV3():    def __init__(self, host: str):        self._socket = socket.socket(            socket.AF_BLUETOOTH,            socket.SOCK_STREAM,            socket.BTPROTO_RFCOMM        )        self._socket.connect((host, 1))    def __del__(self):        if isinstance(self._socket, socket.socket):            self._socket.close()    def send_direct_cmd(self, ops: bytes, local_mem: int=0, global_mem: int=0) -> bytes:        cmd = b''.join([            struct.pack('<h', len(ops) + 5),            struct.pack('<h', 42),            DIRECT_COMMAND_REPLY,            struct.pack('<h', local_mem*1024 + global_mem),            ops        ])        self._socket.send(cmd)        print_hex('Sent', cmd)        reply = self._socket.recv(5 + global_mem)        print_hex('Recv', reply)        return replydef print_hex(desc: str, data: bytes) -> None:    print(desc + ' 0x|' + ':'.join('{:02X}'.format(byte) for byte in data) + '|')DIRECT_COMMAND_REPLY = b'\x00'opNop = b'\x01'my_ev3 = EV3('00:16:53:42:2B:99')ops_nothing = opNopmy_ev3.send_direct_cmd(ops_nothing)

socketРеализация зависит от операционной системы вашего компьютера. если не поддерживаетсяAF_BLUETOOTH(вы увидите сообщение об ошибке, напримерAttributeError: модуль «сокет» не имеет атрибута «AF_BLUETOOTH»),ты можешь использоватьpybluez, это означает, что вам нужно импортироватьbluetoothвместоsocket. В моем случае было сказано:

  • Установить с помощью pip3pybluez:sudo pip3 install pybluez
    Есть два предварительных условия для установки pybluez: во-первых, текущий Python по умолчанию, настроенный в системе, — это Python 3, который можно настроить через$ sudo update-alternatives --config pythonЗавершено; во-вторых, был установлен комплект для разработки bluetoothlibbluetooth-dev, это можно сделать, выполнив команду$ sudo apt-get install libbluetooth-devготово, иначе установитьpybluezсообщит о следующей ошибке:

        creating build/lib.linux-x86_64-3.5    creating build/lib.linux-x86_64-3.5/bluetooth    copying bluetooth/osx.py -> build/lib.linux-x86_64-3.5/bluetooth    copying bluetooth/__init__.py -> build/lib.linux-x86_64-3.5/bluetooth    copying bluetooth/btcommon.py -> build/lib.linux-x86_64-3.5/bluetooth    copying bluetooth/msbt.py -> build/lib.linux-x86_64-3.5/bluetooth    copying bluetooth/widcomm.py -> build/lib.linux-x86_64-3.5/bluetooth    copying bluetooth/ble.py -> build/lib.linux-x86_64-3.5/bluetooth    copying bluetooth/bluez.py -> build/lib.linux-x86_64-3.5/bluetooth    running build_ext    building 'bluetooth._bluetooth' extension    creating build/temp.linux-x86_64-3.5    creating build/temp.linux-x86_64-3.5/bluez    x86_64-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I./port3 -I/usr/include/python3.5m -c bluez/btmodule.c -o build/temp.linux-x86_64-3.5/bluez/btmodule.o    In file included from bluez/btmodule.c:20:0:    bluez/btmodule.h:5:33: fatal error: bluetooth/bluetooth.h: 没有那个文件或目录    compilation terminated.    error: command 'x86_64-linux-gnu-gcc' failed with exit status 1        ----------------------------------------Command "/usr/bin/python -u -c "import setuptools, tokenize;__file__='/tmp/pip-install-mk3f5p_f/pybluez/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-record-gpk9_xhc/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /tmp/pip-install-mk3f5p_f/pybluez/
  • Измените программу:

    #!/usr/bin/env python3import bluetoothimport structclass EV3():    def __init__(self, host: str):        self._socket = bluetooth.BluetoothSocket(bluetooth.RFCOMM)        self._socket.connect((host, 1))    def __del__(self):        if isinstance(self._socket, bluetooth.BluetoothSocket):            self._socket.close()...
  • запустить программу

  • оstruct.packинструкция
    structмодульныйpack()Прототип функции выглядит следующим образом:

struct.pack(format, v1, v2, ...)

То есть первый параметр — это строка формата, за которой следует упаковываемое значение.

где строка форматаformatОн состоит из двух частей: символов, представляющих порядок/размер/выравнивание байтов, и символов формата. Значение каждого символа, представляющего порядок/размер/выравнивание байтов, следующее:

персонаж порядок байтов размер Выравнивание
@ местный местный местный
= местный стандартный none
< маленький конец стандартный none
> большой конец стандартный none
! сеть (= обратный порядок байтов) стандартный none

Значения символов формата следующие:

Формат Тип С Тип Python стандартный размер Уведомление
x pad byte no value
c char bytes of length 1 1
b signed char integer 1 (1),(3)
B unsigned char integer 1 (3)
? _Bool bool 1 (1)
h short integer 2 (3)
H unsigned short integer 2 (3)
i int integer 4 (3)
I unsigned int integer 4 (3)
l long integer 4 (3)
L unsigned long integer 4 (3)
q long long integer 8 (2), (3)
Q unsigned long long integer 8 (2), (3)
n ssize_t integer (4)
N size_t integer (4)
e (7) float 2 (5)
f float float 4 (5)
d double float 8 (5)
s char[] bytes
p char[] bytes
P void * integer (6)

оstructБолее подробное описание модуля см.официальная документация.

Java

Для связи с Bluetooth-устройством мои вариантыbluecove. Скачать Java-пакетbluecove-2.1.0.jar(В Unix это также может бытьbluecove-gpl-2.1.0.jar), вы можете добавить их в свой путь к классам. На моей машине Unix это делается с помощью:

export CLASSPATH=$CLASSPATH:./bluecove-2.1.0.jar:./bluecove-gpl-2.1.0.jar

bluecove-2.1.0.jarАдрес загрузкиА как насчет репозитория.com/artifact/ внутри MV…Адрес загрузкиА как насчет репозитория.com/artifact/ внутри MV…

Затем выполните следующие действия:

  • Скопируйте следующий код в файл с именемEV3_do_nothing_bluetooth.javaв файле.
  • укажите MAC-адрес001653422B99Измените значение вашего EV3.
  • Откройте терминал и перейдите в каталог вашей программы.
  • типjavac EV3_do_nothing_bluetooth.javaскомпилировать его.
  • типjava EV3_do_nothing_bluetoothзапустить его.
import java.io.*;import javax.microedition.io.*;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.io.*;public class EV3_do_nothing_bluetooth {    static final String mac_addr = "001653422B99";    static final byte  opNop                        = (byte)  0x01;    static final byte  DIRECT_COMMAND_REPLY         = (byte)  0x00;    static InputStream in;    static OutputStream out;    public static void connectBluetooth () throws IOException {     String s = "btspp://" + mac_addr + ":1";     StreamConnection c = (StreamConnection) Connector.open(s);     in = c.openInputStream();     out = c.openOutputStream();    }    public static ByteBuffer sendDirectCmd (ByteBuffer operations,         int local_mem, int global_mem) throws IOException { ByteBuffer buffer = ByteBuffer.allocateDirect(operations.position() + 7); buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putShort((short) (operations.position() + 5));   // length buffer.putShort((short) 42);                            // counter buffer.put(DIRECT_COMMAND_REPLY);                       // type buffer.putShort((short) (local_mem*1024 + global_mem)); // header for (int i=0; i < operations.position(); i++) {         // operations     buffer.put(operations.get(i)); } byte[] cmd = new byte [buffer.position()]; for (int i=0; i<buffer.position(); i++) cmd[i] = buffer.get(i); out.write(cmd); printHex("Sent", buffer); byte[] reply = new byte[global_mem + 5]; in.read(reply); buffer = ByteBuffer.wrap(reply); buffer.position(reply.length); printHex("Recv", buffer); return buffer;    }    public static void printHex(String desc, ByteBuffer buffer) { System.out.print(desc + " 0x|"); for (int i= 0; i < buffer.position() - 1; i++) {     System.out.printf("%02X:", buffer.get(i)); } System.out.printf("%02X|", buffer.get(buffer.position() - 1)); System.out.println();    }    public static void main (String args[] ) { try {     connectBluetooth();     ByteBuffer operations = ByteBuffer.allocateDirect(1);     operations.put(opNop);     ByteBuffer reply = sendDirectCmd(operations, 0, 0); } catch (Exception e) {     e.printStackTrace(System.err); }    }}

Это обычное Java-приложение, и вы можете использовать свои собственные инструменты сборки и IDE. вводитьbluecoveиbluecove-gplЗависимости Maven следующие:

<dependencies>    <dependency>        <groupId>net.sf.bluecove</groupId>        <artifactId>bluecove</artifactId>        <version>2.1.0</version>    </dependency>    <dependency>        <groupId>net.sf.bluecove</groupId>        <artifactId>bluecove-gpl</artifactId>        <version>2.1.0</version>    </dependency></dependencies>

Wifi

Вам понадобится адаптер Wi-Fi для подключения EV3 к локальной сети. Первая часть документа ниже описывает этот процесс:woohoo.mono кирпич.Открыть /guides/how-…. Теперь ваш EV3 является частью локальной сети и имеет сетевой адрес. Вы можете общаться с ним со всех машин в сети. Как описано в упомянутом выше документе, для установления TCP/IP-соединения с EV3 необходимо выполнить следующие шаги:

  • Слушайте UDP-трансляции от EV3 через порт 3015.
  • Отправьте сообщение UDP обратно в EV3, чтобы он принял соединение TCP/IP.
  • Установите соединение TCP/IP на порт 5555.
  • Отправьте сообщение о разблокировке на EV3 по TCP/IP.

Python

Вам необходимо выполнить следующие шаги:

  • скопируйте код вEV3_do_nothing_wifi.pyв файле.
  • Откройте терминал и перейдите в каталог вашей программы.
  • типpython3 EV3_do_nothing_wifi.pyзапустить его.
#!/usr/bin/env python3import socketimport structimport reclass EV3():    def __init__(self, host: str):        # listen on port 3015 for a UDP broadcast from the EV3        UDPSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)        UDPSock.bind(('', 3015))        data, addr = UDPSock.recvfrom(67)        # pick serial number, port, name and protocol        # from the broadcast message        matcher = re.search('Serial-Number: (\w*)\s\n' +                            'Port: (\d{4,4})\s\n' +                            'Name: (\w+)\s\n' +                            'Protocol: (\w+)\s\n',                            data.decode('utf-8'))        serial_number = matcher.group(1)        port = matcher.group(2)        name = matcher.group(3)        protocol = matcher.group(4)        if serial_number.upper() != host.replace(':', '').upper():            self._socket = None            raise ValueError('found ev3 but not ' + host)        # Send an UDP message back to the EV3        # to make it accept a TCP/IP connection        UDPSock.sendto(' '.encode('utf-8'), (addr[0], int(port)))        UDPSock.close()        # Establish a TCP/IP connection with EV3s address and port        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        self._socket.connect((addr[0], int(port)))        # Send an unlock message to the EV3 over TCP/IP        msg = ''.join([            'GET /target?sn=' + serial_number + 'VMTP1.0\n',            'Protocol: ' + protocol        ])        self._socket.send(msg.encode('utf-8'))        reply = self._socket.recv(16).decode('utf-8')        if not reply.startswith('Accept:EV340'):            raise IOError('No wifi connection to ' + name + ' established')    def __del__(self):        if isinstance(self._socket, socket.socket):            self._socket.close()    def send_direct_cmd(self, ops: bytes, local_mem: int=0, global_mem: int=0) -> bytes:        cmd = b''.join([            struct.pack('<h', len(ops) + 5),            struct.pack('<h', 42),            DIRECT_COMMAND_REPLY,            struct.pack('<h', local_mem*1024 + global_mem),            ops        ])        self._socket.send(cmd)        print_hex('Sent', cmd)        reply = self._socket.recv(5 + global_mem)        print_hex('Recv', reply)        return replydef print_hex(desc: str, data: bytes) -> None:    print(desc + ' 0x|' + ':'.join('{:02X}'.format(byte) for byte in data) + '|')DIRECT_COMMAND_REPLY = b'\x00'opNop = b'\x01'my_ev3 = EV3('00:16:53:42:2B:99')ops_nothing = opNopmy_ev3.send_direct_cmd(ops_nothing)

Java

Вам необходимо выполнить следующие шаги:

  • скопируйте код вEV3_do_nothing_wifi.javaв файле.
  • Откройте терминал и перейдите в каталог вашей программы.
  • типjavac EV3_do_nothing_wifi.javaскомпилировать его.
  • типjava EV3_do_nothing_wifiзапустить его.
import java.net.Socket;import java.net.SocketException;import java.net.ServerSocket;import java.net.DatagramSocket;import java.net.DatagramPacket;import java.net.InetAddress;import java.nio.ByteBuffer;import java.nio.IntBuffer;import java.nio.ByteOrder;import java.io.*;import java.util.regex.*;public class EV3_do_nothing_wifi {    static final byte  opNop                        = (byte)  0x01;    static final byte  DIRECT_COMMAND_REPLY         = (byte)  0x00;    static InputStream in;    static OutputStream out;    public static void connectWifi () throws IOException, SocketException {     // listen for a UDP broadcast from the EV3 on port 3015     DatagramSocket listener = new DatagramSocket(3015);     DatagramPacket packet_r = new DatagramPacket(new byte[67], 67);     listener.receive(packet_r); // receive the broadcast message     String broadcast_message = new String(packet_r.getData());     /* pick serial number, port, name and protocol         from the broadcast message */     Pattern broadcast_pattern =      Pattern.compile("Serial-Number: (\\w*)\\s\\n" +                            "Port:\\s(\\d{4,4})\\s\\n" +                            "Name:\\s(\\w+)\\s\\n" +                            "Protocol:\\s(\\w+)\\s\\n");     Matcher matcher = broadcast_pattern.matcher(broadcast_message);     String serial_number, name, protocol;     int port;     if(matcher.matches()) {  serial_number = matcher.group(1);  port = Integer.valueOf(matcher.group(2));  name = matcher.group(3);  protocol = matcher.group(4);     }     else {  throw new IOException("Unexpected Broadcast message: " + broadcast_message);     }     InetAddress adr = packet_r.getAddress();     // connect the EV3 with its address and port     listener.connect(adr, port);     /* Send an UDP message back to the EV3         to make it accept a TCP/IP connection */     listener.send(new DatagramPacket(new byte[1], 1));     // close the UDP connection     listener.close();     // Establish a TCP/IP connection with EV3s address and port     Socket socket = new Socket(adr, port);     in     = socket.getInputStream();     out    = socket.getOutputStream();     // Send an unlock message to the EV3 over TCP/IP     String unlock_message = "GET /target?sn=" + serial_number +                                     "VMTP1.0\n" +                                     "Protocol: " + protocol;     out.write(unlock_message.getBytes());     byte[] reply = new byte[16];           // read reply     in.read(reply);     if (! (new String(reply)).startsWith("Accept:EV340")) {  throw new IOException("No wifi connection established " + name);     }     }    public static ByteBuffer sendDirectCmd (ByteBuffer operations,         int local_mem, int global_mem) throws IOException { ByteBuffer buffer = ByteBuffer.allocateDirect(operations.position() + 7); buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putShort((short) (operations.position() + 5));   // length buffer.putShort((short) 42);                            // counter buffer.put(DIRECT_COMMAND_REPLY);                       // type buffer.putShort((short) (local_mem*1024 + global_mem)); // header for (int i=0; i<operations.position(); i++) {           // operations     buffer.put(operations.get(i)); } byte[] cmd = new byte [buffer.position()]; for (int i=0; i<buffer.position(); i++) cmd[i] = buffer.get(i); out.write(cmd); printHex("Sent", buffer); byte[] reply = new byte[global_mem + 5]; in.read(reply); buffer = ByteBuffer.wrap(reply); buffer.position(reply.length); printHex("Recv", buffer); return buffer;    }    public static void printHex(String desc, ByteBuffer buffer) { System.out.print(desc + " 0x|"); for (int i= 0; i<buffer.position() - 1; i++) {     System.out.printf("%02X:", buffer.get(i)); } System.out.printf("%02X|", buffer.get(buffer.position() - 1)); System.out.println();    }    public static void main (String args[] ) { try {     connectWifi();     ByteBuffer operations = ByteBuffer.allocateDirect(1);     operations.put(opNop);     ByteBuffer reply = sendDirectCmd(operations, 0, 0); } catch (Exception e) {     e.printStackTrace(System.err); }    }}

USB

Универсальная последовательная шина — это отраслевой стандарт для подключения электронных устройств. Ваш EV3 оснащен разъемом 2.0 Mini-B (обозначен как ПК). Это наиболее эффективный протокол связи, но для него требуется провод. На компьютере, на котором вы запускаете свою программу, вам необходимо разрешение на связь с LEGO EV3. В Ubuntu Linux черезlsusbПроверьте подключенные в данный момент USB-устройства, чтобы убедиться, что EV3 успешно распознан, например:

$ lsusbBus 002 Device 002: ID 8087:8000 Intel Corp. Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hubBus 001 Device 002: ID 8087:8008 Intel Corp. Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hubBus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hubBus 003 Device 006: ID 5986:055c Acer, Inc Bus 003 Device 005: ID 8087:07dc Intel Corp. Bus 003 Device 004: ID 2717:ff48  Bus 003 Device 003: ID 046d:c52b Logitech, Inc. Unifying ReceiverBus 003 Device 007: ID 0694:0005 Lego Group Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Зависит отBus 003 Device 007: ID 0694:0005 Lego GroupЭта строка показывает, что EV3 был успешно распознан системой, иBus 003 Device 007раздел описывает, куда подключено устройство,ID 0694:0005Описывает идентификатор производителя и идентификатор продукта. По умолчанию вновь подключенные новые типы USB-устройств имеют доступ только к root-пользователю, который можно определить в соответствии сBus 003 Device 007Для получения этой информации см./dev/bus/usbСоответствующий файл устройства можно увидеть ниже:

$ ls -al  /dev/bus/usb/003/总用量 0drwxr-xr-x  2 root root       160 11月  1 09:51 .drwxr-xr-x  6 root root       120 11月  1 09:49 ..crw-rw-r--  1 root root  189, 256 11月  1 09:49 001crw-rw-r--  1 root root  189, 258 11月  1 09:49 003crw-rw----+ 1 root audio 189, 259 11月  1 09:50 004crw-rw-r--  1 root root  189, 260 11月  1 09:49 005crw-rw-r--  1 root root  189, 261 11月  1 09:49 006crw-rw-r--  1 root root  189, 262 11月  1 09:51 007

Файл USB-устройства, соответствующий LEGO EV3, можно увидеть/dev/bus/usb/003/007, чьим владельцем и группой владельцев являются root, а другие пользователи имеют разрешение только на чтение. Когда у нас нет разрешения, черезpyusbПри доступе к EV3 будет сообщено о следующей ошибке:

Traceback (most recent call last):  File "/home/hanpfei0306/data/MyProjects/wolfcs-tools/EV3_do_nothing_usb.py", line 44, in <module>    my_ev3 = EV3('00:16:53:42:2B:99')  File "/home/hanpfei0306/data/MyProjects/wolfcs-tools/EV3_do_nothing_usb.py", line 11, in __init__    serial_number = usb.util.get_string(self._device, self._device.iSerialNumber)  File "/usr/local/lib/python3.5/dist-packages/usb/util.py", line 314, in get_string    raise ValueError("The device has no langid")ValueError: The device has no langid

Чтобы разрешить обычным пользователям без полномочий root доступ к EV3 через USB, в него необходимо добавить правила udev. На моем Ubuntu Linux 16.04 да, создайте файл/etc/udev/rules.d/51-legoev3-usb.rulesи добавьте следующее:

SUBSYSTEM=="usb", ATTR{idVendor}=="0694", ATTR{idProduct}=="0005", MODE="0666", GROUP="<group>"

Пучок<group>Измените его на тот, к которому вы принадлежите. После добавления правила udev отключите USB-порт и снова подключите его, чтобы правило вступило в силу. На данный момент даже под обычными пользователями можно просмотреть подробную информацию о EV3 через lsusb:

$ lsusb -v -d 0694:0005Bus 003 Device 013: ID 0694:0005 Lego Group Device Descriptor:  bLength                18  bDescriptorType         1  bcdUSB               2.00  bDeviceClass            0 (Defined at Interface level)  bDeviceSubClass         0   bDeviceProtocol         0   bMaxPacketSize0        64  idVendor           0x0694 Lego Group  idProduct          0x0005   bcdDevice            2.16  iManufacturer           1 LEGO Group  iProduct                2 EV3  iSerial                 3 001653602591  bNumConfigurations      1  Configuration Descriptor:    bLength                 9    bDescriptorType         2    wTotalLength           41    bNumInterfaces          1    bConfigurationValue     1    iConfiguration          1 LEGO Group    bmAttributes         0xc0      Self Powered    MaxPower                2mA    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        0      bAlternateSetting       0      bNumEndpoints           2      bInterfaceClass         3 Human Interface Device      bInterfaceSubClass      0 No Subclass      bInterfaceProtocol      0 None      iInterface              4 Xfer data to and from EV3 brick        HID Device Descriptor:          bLength                 9          bDescriptorType        33          bcdHID               1.10          bCountryCode            0 Not supported          bNumDescriptors         1          bDescriptorType        34 Report          wDescriptorLength      29          Report Descriptor: (length is 29)            Item(Global): Usage Page, data= [ 0x00 0xff ] 65280                            (null)            Item(Local ): Usage, data= [ 0x01 ] 1                            (null)            Item(Main  ): Collection, data= [ 0x01 ] 1                            Application            Item(Global): Logical Minimum, data= [ 0x00 ] 0            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255            Item(Global): Report Size, data= [ 0x08 ] 8            Item(Global): Report Count, data= [ 0x00 0x04 ] 1024            Item(Local ): Usage, data= [ 0x01 ] 1                            (null)            Item(Main  ): Input, data= [ 0x02 ] 2                            Data Variable Absolute No_Wrap Linear                            Preferred_State No_Null_Position Non_Volatile Bitfield            Item(Global): Report Count, data= [ 0x00 0x04 ] 1024            Item(Local ): Usage, data= [ 0x01 ] 1                            (null)            Item(Main  ): Output, data= [ 0x02 ] 2                            Data Variable Absolute No_Wrap Linear                            Preferred_State No_Null_Position Non_Volatile Bitfield            Item(Main  ): End Collection, data=none      Endpoint Descriptor:        bLength                 7        bDescriptorType         5        bEndpointAddress     0x81  EP 1 IN        bmAttributes            3          Transfer Type            Interrupt          Synch Type               None          Usage Type               Data        wMaxPacketSize     0x0400  1x 1024 bytes        bInterval               4      Endpoint Descriptor:        bLength                 7        bDescriptorType         5        bEndpointAddress     0x01  EP 1 OUT        bmAttributes            3          Transfer Type            Interrupt          Synch Type               None          Usage Type               Data        wMaxPacketSize     0x0400  1x 1024 bytes        bInterval               4Device Qualifier (for other device speed):  bLength                10  bDescriptorType         6  bcdUSB               2.00  bDeviceClass            0 (Defined at Interface level)  bDeviceSubClass         0   bDeviceProtocol         0   bMaxPacketSize0        64  bNumConfigurations      1Device Status:     0x0001  Self Powered

Для получения дополнительной информации см.Как получить доступ к обычному USB-устройству в качестве пользователя без полномочий root.

Идентифицируйте устройства EV3 по их идентификатору производителя 0x0694 и идентификатору продукта 0x0005. Режим 0666 разрешает принадлежность к<group>Все пользователи имеют права на чтение и запись. Дескриптор устройства EV3-USB показывает конфигурацию с одним интерфейсом и двумя конечными точками: 0x01 для отправки данных на EV3 и 0x81 для получения данных от EV3. Данные отправляются и принимаются пакетами по 1024 байта.

Python

возможно вам нужно установитьpyusb. Для моей системы это делается командой вроде:

sudo pip3 install --pre pyusb

если уже установленpyusb, необходимо выполнить следующие шаги:

  • скопируйте код вEV3_do_nothing_usb.pyв файле.
  • Откройте терминал и перейдите в каталог вашей программы.
  • типpython3 EV3_do_nothing_usb.pyзапустить его.
#!/usr/bin/env python3import usb.coreimport structclass EV3():    def __init__(self, host: str):        self._device = usb.core.find(idVendor=ID_VENDOR_LEGO, idProduct=ID_PRODUCT_EV3)        if self._device is None:            raise RuntimeError("No Lego EV3 found")        serial_number = usb.util.get_string(self._device, self._device.iSerialNumber)        if serial_number.upper() != host.replace(':', '').upper():            raise ValueError('found ev3 but not ' + host)        if self._device.is_kernel_driver_active(0) is True:            self._device.detach_kernel_driver(0)        self._device.set_configuration()        self._device.read(EP_IN, 1024, 100)    def __del__(self): pass    def send_direct_cmd(self, ops: bytes, local_mem: int=0, global_mem: int=0) -> bytes:        cmd = b''.join([            struct.pack('<h', len(ops) + 5),            struct.pack('<h', 42),            DIRECT_COMMAND_REPLY,            struct.pack('<h', local_mem*1024 + global_mem),            ops        ])        self._device.write(EP_OUT, cmd, 100)        print_hex('Sent', cmd)        reply = self._device.read(EP_IN, 1024, 100)[0:5+global_mem]        print_hex('Recv', reply)        return replydef print_hex(desc: str, data: bytes) -> None:    print(desc + ' 0x|' + ':'.join('{:02X}'.format(byte) for byte in data) + '|')ID_VENDOR_LEGO = 0x0694ID_PRODUCT_EV3 = 0x0005EP_IN  = 0x81EP_OUT = 0x01DIRECT_COMMAND_REPLY = b'\x00'opNop = b'\x01'my_ev3 = EV3('00:16:53:42:2B:99')ops_nothing = opNopmy_ev3.send_direct_cmd(ops_nothing)

Примечание переводчика:

  • прошел сверхуlsusbИз информации об устройстве EV3 видно, что серийный номер EV3iSerial 3 001653602591,Сейчас00:16:53:60:25:91, вместо00:16:53:42:2B:99', поэтому строки оценки серийного номера в приведенном выше коде:
    serial_number = usb.util.get_string(self._device, self._device.iSerialNumber)if serial_number.upper() != host.replace(':', '').upper():    raise ValueError('found ev3 but not ' + host)

можно удалить.

  • После первого подключения устройства LEGO EV3 выполните приведенный выше код, чтобы отправлять сообщения на EV3 и получать ответы в обычном режиме. Но при повторном запуске программа выдала следующую ошибку:
    Traceback (most recent call last):  File "/home/hanpfei0306/data/MyProjects/wolfcs-tools/EV3_do_nothing_usb.py", line 44, in <module>    my_ev3 = EV3('00:16:53:42:2B:99')  File "/home/hanpfei0306/data/MyProjects/wolfcs-tools/EV3_do_nothing_usb.py", line 17, in __init__    self._device.read(EP_IN, 1024, 100)  File "/usr/local/lib/python3.5/dist-packages/usb/core.py", line 988, in read    self.__get_timeout(timeout))  File "/usr/local/lib/python3.5/dist-packages/usb/backend/libusb1.py", line 851, in intr_read    timeout)  File "/usr/local/lib/python3.5/dist-packages/usb/backend/libusb1.py", line 936, in __read    _check(retval)  File "/usr/local/lib/python3.5/dist-packages/usb/backend/libusb1.py", line 595, in _check    raise USBError(_strerror(ret), ret, _libusb_errno[ret])usb.core.USBError: [Errno 110] Operation timed out

Этот вопрос обсуждается на StackOverflow:stackoverflow.com/questions/3… find()После вызова метода добавьтеself._device.reset()решить, например:

def __init__(self, host: str):    self._device = usb.core.find(idVendor=ID_VENDOR_LEGO, idProduct=ID_PRODUCT_EV3)    if self._device is None:        raise RuntimeError("No Lego EV3 found")    self._device.reset()    serial_number = usb.util.get_string(self._device, self._device.iSerialNumber)    # if serial_number.upper() != host.replace(':', '').upper():    #     raise ValueError('found ev3 but not ' + host)    if self._device.is_kernel_driver_active(0) is True:        self._device.detach_kernel_driver(0)    self._device.set_configuration()    self._device.read(EP_IN, 1024, 100)

Java

Мой выбор для связи с USB-устройствами — usb4java. После загрузки пакетов Java вы можете добавить их в свой путь к классам. На моем Unix-компьютере это делается с помощью следующей команды:

export CLASSPATH=$CLASSPATH:./usb4java-1.3.0.jar:./libusb4java-1.3.0-linux-x86_64.jar

Примечание переводчика:
Если ваша система сборки использует Maven, вы также можетеpom.xmlДобавьте следующие зависимости в файл для использованияusb4java:

<dependency>    <groupId>org.usb4java</groupId>    <artifactId>usb4java</artifactId>    <version>1.3.0</version></dependency>

Затем выполните следующие действия:

  • Скопируйте следующий код в файл с именемEV3_do_nothing_usb.javaв файле.
  • Откройте терминал и перейдите в каталог вашей программы.
  • типjavac EV3_do_nothing_usb.javaскомпилировать его.
  • типjava EV3_do_nothing_usbзапустить его.
import org.usb4java.Device;import org.usb4java.DeviceDescriptor;import org.usb4java.DeviceHandle;import org.usb4java.DeviceList;import org.usb4java.LibUsb;import org.usb4java.LibUsbException;import java.nio.ByteBuffer;import java.nio.IntBuffer;import java.nio.ByteOrder;public class EV3_do_nothing_usb {    static final short ID_VENDOR_LEGO = (short) 0x0694;    static final short ID_PRODUCT_EV3 = (short) 0x0005;    static final byte  EP_IN          = (byte)  0x81;    static final byte  EP_OUT         = (byte)  0x01;    static final byte  opNop                        = (byte)  0x01;    static final byte  DIRECT_COMMAND_REPLY         = (byte)  0x00;    static DeviceHandle handle;    public static void connectUsb () { int result = LibUsb.init(null); Device device = null; DeviceList list = new DeviceList(); result = LibUsb.getDeviceList(null, list); if (result < 0){     throw new RuntimeException("Unable to get device list. Result=" + result); } boolean found = false; for (Device dev: list) {     DeviceDescriptor descriptor = new DeviceDescriptor();     result = LibUsb.getDeviceDescriptor(dev, descriptor);     if (result != LibUsb.SUCCESS) {  throw new LibUsbException("Unable to read device descriptor", result);     }     if (  descriptor.idVendor()  == ID_VENDOR_LEGO    || descriptor.idProduct() == ID_PRODUCT_EV3) {  device = dev;  found = true;  break;     } } LibUsb.freeDeviceList(list, true); if (! found) throw new RuntimeException("Lego EV3 device not found."); handle = new DeviceHandle(); result = LibUsb.open(device, handle); if (result != LibUsb.SUCCESS) {     throw new LibUsbException("Unable to open USB device", result); } boolean detach = LibUsb.kernelDriverActive(handle, 0) != 0; if (detach) result = LibUsb.detachKernelDriver(handle, 0); if (result != LibUsb.SUCCESS) {     throw new LibUsbException("Unable to detach kernel driver", result); } result = LibUsb.claimInterface(handle, 0); if (result != LibUsb.SUCCESS) {     throw new LibUsbException("Unable to claim interface", result); }    }    public static ByteBuffer sendDirectCmd (ByteBuffer operations,         int local_mem, int global_mem) { ByteBuffer buffer = ByteBuffer.allocateDirect(operations.position() + 7); buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putShort((short) (operations.position() + 5));   // length buffer.putShort((short) 42);                            // counter buffer.put(DIRECT_COMMAND_REPLY);                       // type buffer.putShort((short) (local_mem*1024 + global_mem)); // header for (int i=0; i < operations.position(); i++) {         // operations     buffer.put(operations.get(i)); } IntBuffer transferred = IntBuffer.allocate(1); int result = LibUsb.bulkTransfer(handle, EP_OUT, buffer, transferred, 100);  if (result != LibUsb.SUCCESS) {     throw new LibUsbException("Unable to write data", transferred.get(0)); } printHex("Sent", buffer); buffer = ByteBuffer.allocateDirect(1024); transferred = IntBuffer.allocate(1); result = LibUsb.bulkTransfer(handle, EP_IN, buffer, transferred, 100); if (result != LibUsb.SUCCESS) {     throw new LibUsbException("Unable to read data", result); } buffer.position(global_mem + 5); printHex("Recv", buffer); return buffer;    }    public static void printHex(String desc, ByteBuffer buffer) { System.out.print(desc + " 0x|"); for (int i= 0; i < buffer.position() - 1; i++) {     System.out.printf("%02X:", buffer.get(i)); } System.out.printf("%02X|", buffer.get(buffer.position() - 1)); System.out.println();    }    public static void main (String args[] ) { try {     connectUsb();     ByteBuffer operations = ByteBuffer.allocateDirect(1);     operations.put(opNop);     ByteBuffer reply = sendDirectCmd(operations, 0, 0);     LibUsb.releaseInterface(handle, 0);     LibUsb.close(handle); } catch (Exception e) {     e.printStackTrace(System.err); }    }}

ответное сообщение

Если вы успешно установили связь с EV3, используя один из описанных выше сценариев, вы получите следующий вывод — ответное сообщение для прямой команды:

----------------     \ len \ cnt \rs\     –---------------  0x|03:00|2A:00|02|    –---------------     \ 3   \ 42  \ok\     ----------------

Первые два байта хорошо известны и являются прямым представлением длины ответного сообщения. В нашем случае ответное сообщение имеет длину 3 байта. Следующие два байта — это счетчик сообщений, также широко известный как отпечаток отправленного вами сообщения, который равен 42.

Последний байт — это статус возврата, который имеет 2 возможных значения:

  • DIRECT_REPLY = 0x|02|: Прямая командная операция выполнена успешно
  • DIRECT_REPLY_ERROR = 0x|04|: прямая команда заканчивается неудачей

Если вы получили это ответное сообщение, вы уже в пути. Поздравляем!

деталь головы

Выше мы пропустили описание деталей заголовка. Упомянул об этом, в заголовке есть два числа, определяющие размер памяти.

Первое число — это размер локальной памяти, то есть адресного пространства, в котором вы можете хранить промежуточную информацию. Второе число описывает размер глобальной памяти, которая является адресным пространством вывода. существуетDIRECT_COMMAND_REPLYВ случае , глобальная память будет отправлена ​​обратно как часть ответного сообщения.

Локальная память имеет максимум 63 байта, а глобальная память имеет максимум 1019 байт. Это означает, что для размера локальной памяти требуется 6 бит, а для размера глобальной памяти — 10 бит. Все вместе может содержаться в двух байтах, если один байт является общим. Так и было. Если вы запишете байты заголовка в обратном порядке, который является знакомым обратным порядком байтов, и в двоичной записи как группы полубайтов, вы получите:0b LLLL LLGG GGGG GGGG. Первые 6 бит — это размер локальной памяти, который находится в диапазоне от 0 до 63. Задние 10 бит — это глобальный размер памяти, который находится в диапазоне от 0 до 1020. Под маленьким концом находится:0b GGGG GGGG LLLL LLGG. Например, если ваша глобальная память имеет 6 байт, вашей локальной памяти требуется 16 байт, тогда ваш заголовок0b 0000 0110 0100 0000или в шестнадцатеричном виде0x 06 40.

Это описательный вариант, теперь второй подход к декларативному способу. еслиlocal_memобъем локальной памяти,global_mem- размер глобальной памяти, затем рассчитайте:header = local_mem * 1024 + global_mem. Запишите заголовок как 2-байтовое целое число с прямым порядком байтов, и вы получите два байта заголовка. Если у вас остались вопросы, ждите следующих уроков, вы увидите много голов и узнаете на примерах, которые, надеюсь, развеют ваши сомнения.

Вариант, который ничего не делает

Прежде чем покинуть наш первый пример и закрыть первую главу, мы протестируем два варианта головы. Первая попытка состояла в том, чтобы напрямую управлять пространством глобальной памяти с помощью 6 байтов:

 \ len \ cnt \ty\ hd  \op\       -------------------------    0x|06:00|2A:00|00|06:00|01|      -------------------------       \ 6   \ 42  \Re\ 0,6 \N \       \     \     \  \     \o \       \     \     \  \     \p \       -------------------------

Мы ожидаем ответ с выходным 6-байтовым низким значением. Следовательно, вы должны увеличить длину ответа с 5 до 11. Если вы сделаете это, вы получите:

----------------------------------     \ len \ cnt \rs\ Output          \     –---------------------------------  0x|09:00|2A:00|02|00:00:00:00:00:00|    –---------------------------------     \ 9   \ 42  \ok\                 \     ----------------------------------

Добавляем 16 байт пространства локальной памяти и меняем прямую команду на следующую:

-------------------------       \ len \ cnt \ty\ hd  \op\       -------------------------    0x|06:00|2A:00|00|06:40|01|      -------------------------       \ 6   \ 42  \Re\16,6 \N \       \     \     \  \     \o \       \     \     \  \     \p \       -------------------------

Мы ожидаем такой же ответ, как и выше, а именно:

----------------------------------     \ len \ cnt \rs\ Output          \     –---------------------------------  0x|09:00|2A:00|02|00:00:00:00:00:00|    –---------------------------------     \ 9   \ 42  \ok\                 \     ----------------------------------

твоя домашняя работа

Прежде чем перейти к Уроку 2, вы должны выполнить следующее домашнее задание:

  • Преобразование небольшой программы на ваш любимый язык программирования и ее интеграция в вашу любимую среду разработки.
  • Имейте под рукой несколько инструментов, потому что начинать с нуля снова и снова — дело не из приятных. Я придумал следующие конструкции:
    • EV3 — это класс.
    • BLUETOOTH, USB, WIFI, STD, ASYNC, SYNC и opNop являются общедоступными константами.
    • Подключение к EV3 является частью инициализации объекта EV3, т. е. выбор протокола осуществляется путем вызова конструктора объекта EV3 с определенными параметрами. Объект EV3 должен помнить свой тип протокола.socketиdeviceявляется частной или защищенной переменной объекта EV3.
    • Отправка данных в EV3 с помощью методов класса EV3send_direct_cmdЗаканчивать. Вы можете думать о примерах функций как о схемах, но внутри вы должны различать протоколы.
    • Для получения данных от EV3 используем методwait_for_reply. вы должны поставить функциюsend_direct_cmdКод разделен на два новых методаsend_direct_cmdиwait_for_reply.
    • добавить свойствоverbosity, который определяет, печатаются ли отправленные прямые команды и полученные ответы.
    • добавить свойствоsync_mode, который управляет поведением связи со следующими значениями:
      • SYNC: всегда используйте типDIRECT_COMMAND_REPLYи ждите ответа.
      • ASYNC: никогда не ждать ответа, устанавливается, если глобальная память не используетсяDIRECT_COMMAND_NO_REPLY, другие настройкиDIRECT_COMMAND_REPLY.
      • STD:рисунокASYNCустановить такDIRECT_COMMAND_NO_REPLYилиDIRECT_COMMAND_REPLY, но вDIRECT_COMMAND_REPLYдождитесь ответа.
    • msg_cntEV3 - это частный объект переменной, каждый вызовsend_direct_cmdЭто значение будет увеличиваться. Используйте его для установки счетчика сообщений.
    • Как и в примере, длина сообщения, счетчик сообщений, тип сообщения и заголовок находятся вsend_direct_cmdАвтоматически добавляется внутри. следовательноsend_direct_cmdпараметры методаopsРеально сохраняет информацию об операции и больше ничего.
  • Проведите несколько тестов производительности и сравните три протокола связи (вы увидите, что USB — самый быстрый, Bluetooth — самый медленный, а Wi-Fi — посередине, но вы можете сделать ставку на абсолютные значения и коэффициенты между тремя протоколами).
    • отDIRECT_COMMAND_REPLYПовторить отправкуopNopИ вычислить среднее время цикла отправки-получения.
    • Отделите время соединения от времени отправки и получения. Вы будете подключаться только один раз, но производительность цикла отправки и получения ограничит ваше приложение.

Примечание переводчика:
Имея под рукой устройство, удобнее тестировать работоспособность USB и Bluetooth. Что я тестировал, так это установление соединения и производительность отправки сообщений операции noop:

Способ подключения Время соединения (ед./сек.) Время отправки и получения сообщения (ед/сек)
блютус 3.849 0.0872
USB 0.3204 0.004

в заключении

вы начинаете писать классEV3, для связи с LEGO EV3 с помощью прямых команд. Этот класс позволяет свободно выбирать протоколы связи и обеспечивает Bluetooth, USB и Wi-Fi. Мой любимый язык программирования — Python3. Я использую pydoc3, чтобы показать фактическое состояние нашего проекта. Я надеюсь, вы можете просто преобразовать его на предпочитаемый вами язык. На данный момент наш классEV3Имеет следующий API:

Help on module ev3:NAME    ev3 - LEGO EV3 direct commandsCLASSES    builtins.object        EV3        class EV3(builtins.object)     |  object to communicate with a LEGO EV3 using direct commands     |       |  Methods defined here:     |       |  __del__(self)     |      closes the connection to the LEGO EV3     |       |  __init__(self, protocol:str, host:str)     |      Establish a connection to a LEGO EV3 device     |           |      Arguments:     |      protocol: 'Bluetooth', 'Usb' or 'Wifi'     |      host: mac-address of the LEGO EV3 (f.i. '00:16:53:42:2B:99')     |       |  send_direct_cmd(self, ops:bytes, local_mem:int=0, global_mem:int=0) -> bytes     |      Send a direct command to the LEGO EV3     |           |      Arguments:     |      ops: holds netto data only (operations), the following fields are added:     |        length: 2 bytes, little endian     |        counter: 2 bytes, little endian     |        type: 1 byte, DIRECT_COMMAND_REPLY or DIRECT_COMMAND_NO_REPLY     |        header: 2 bytes, holds sizes of local and global memory     |           |      Keyword Arguments:     |      local_mem: size of the local memory     |      global_mem: size of the global memory     |           |      Returns:      |        sync_mode is STD: reply (if global_mem > 0) or message counter     |        sync_mode is ASYNC: message counter     |        sync_mode is SYNC: reply of the LEGO EV3     |       |  wait_for_reply(self, counter:bytes) -> bytes     |      Ask the LEGO EV3 for a reply and wait until it is received     |           |      Arguments:     |      counter: is the message counter of the corresponding send_direct_cmd     |           |      Returns:     |      reply to the direct command     |       |  ----------------------------------------------------------------------     |  Data descriptors defined here:     |       |  __dict__     |      dictionary for instance variables (if defined)     |       |  __weakref__     |      list of weak references to the object (if defined)     |       |  sync_mode     |      sync mode (standard, asynchronous, synchronous)     |           |      STD:   Use DIRECT_COMMAND_REPLY if global_mem > 0,     |             wait for reply if there is one.     |      ASYNC: Use DIRECT_COMMAND_REPLY if global_mem > 0,     |             never wait for reply (it's the task of the calling program).     |      SYNC:  Always use DIRECT_COMMAND_REPLY and wait for reply.     |           |      The general idea is:     |      ASYNC: Interruption or EV3 device queues direct commands,     |             control directly comes back.     |      SYNC:  EV3 device is blocked until direct command is finished,     |             control comes back, when direct command is finished.                    |      STD:   NO_REPLY like ASYNC with interruption or EV3 queuing,     |             REPLY like SYNC, synchronicity of program and EV3 device.     |       |  verbosity     |      level of verbosity (prints on stdout).DATA    BLUETOOTH = 'Bluetooth'    USB = 'Usb'    WIFI = 'Wifi'    STD = 'STD'    ASYNC = 'ASYSNC'    SYNC = 'SYNC'    opNop = b'\x01'

мойEV3классы являются модулямиev3часть имени файлаev3.py. Я использую следующую программу для проверки моегоEV3своего рода:

#!/usr/bin/env python3import ev3my_ev3 = ev3.EV3(protocol=ev3.BLUETOOTH, host='00:16:53:42:2B:99')my_ev3.verbosity = 1ops = ev3.opNopprint("*** SYNC ***")my_ev3.sync_mode = ev3.SYNCmy_ev3.send_direct_cmd(ops)print("*** ASYNC (no reply) ***")my_ev3.sync_mode = ev3.ASYNCmy_ev3.send_direct_cmd(ops)print("*** ASYNC (reply) ***")counter_1 = my_ev3.send_direct_cmd(ops, global_mem=1)counter_2 = my_ev3.send_direct_cmd(ops, global_mem=1)my_ev3.wait_for_reply(counter_1)my_ev3.wait_for_reply(counter_2)print("*** STD (no reply) ***")my_ev3.sync_mode = ev3.STDmy_ev3.send_direct_cmd(ops)print("*** STD (reply) ***")my_ev3.send_direct_cmd(ops, global_mem=5)my_ev3.send_direct_cmd(ops, global_mem=5)

чтобы получить вывод:

*** SYNC ***15:15:05.084275 Sent 0x|06:00|2A:00|00|00:00|01|15:15:05.168023 Recv 0x|03:00|2A:00|02|*** ASYNC (no reply) ***15:15:05.168548 Sent 0x|06:00|2B:00|80|00:00|01|*** ASYNC (reply) ***15:15:05.168976 Sent 0x|06:00|2C:00|00|01:00|01|15:15:05.169315 Sent 0x|06:00|2D:00|00|01:00|01|15:15:05.212077 Recv 0x|04:00|2C:00|02|00|15:15:05.212708 Recv 0x|04:00|2D:00|02|00|*** STD (no reply) ***15:15:05.213034 Sent 0x|06:00|2E:00|80|00:00|01|*** STD (reply) ***15:15:05.213411 Sent 0x|06:00|2F:00|00|05:00|01|15:15:05.254032 Recv 0x|08:00|2F:00|02|00:00:00:00:00|15:15:05.254633 Sent 0x|06:00|30:00|00|05:00|01|15:15:05.313027 Recv 0x|08:00|30:00|02|00:00:00:00:00|

Некоторые замечания:

  • sync_mode = SYNCнастраиватьtype = DIRECT_COMMAND_REPLYИ автоматически ждать ответа, ок.
  • sync_mode = ASYNCнастраиватьtype = DIRECT_COMMAND_NO_REPLY, не ждите, ок.
  • глобальные настройки памятиtype = DIRECT_COMMAND_REPLYВремяsync_mode = ASYNC, не жди. вызывать явноwait_for_replyметод получения ответа.
  • Пожалуйста, уважайте этот вариант в особенности. Мы отправляем две прямые команды, они обе выполняются, и устройство EV3 сохраняет ответ.
  • Когда мы запрашиваем ответ позже, мы сначала читаем ответ для первой команды. Похоже, что EV3 — это FIFO (First In First Out).
  • Но он также может выполняться параллельно, а также может выполняться параллельно и многократно в том порядке, в котором выполняется выполнение. Мы вернемся к этому.
  • модельASYNCТребуется некоторая дисциплина. Если вы забудете прочитать ответ, он придет, пока вы ждете другого. Мы используем счетчик сообщений, чтобы показать это!
  • Будьте осторожны с протоколом USB! Протокол USB Будьте осторожны! Это может быть слишком быстро, если отправлять асинхронные команды напрямую, как я.
  • sync_mode = STD, нет настройки глобальной памятиtype = DIRECT_COMMAND_NO_REPLY, и не ждите ответа, ок.
  • sync_mode = STD, глобальные настройки памятиtype = DIRECT_COMMAND_REPLY, каждая прямая команда ожидает ответа, ок.
  • Счетчик сообщений увеличивается прямой командой ok.
  • Заголовок правильно содержит размер глобальной памяти, хорошо.

Если вы сделали свою домашнюю работу, вы хорошо подготовлены к новому приключению. Надеюсь увидеть вас снова на следующем уроке.