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 с помощью методов класса EV3
send_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_cnt
EV3 - это частный объект переменной, каждый вызов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.
- Заголовок правильно содержит размер глобальной памяти, хорошо.
Если вы сделали свою домашнюю работу, вы хорошо подготовлены к новому приключению. Надеюсь увидеть вас снова на следующем уроке.