Espruino:Примеры/Коммуникация с ПК

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Коммуникация с ПК[1]

Вы можете использовать Espruino напрямую с ПК, Mac или Raspberry Pi и с их помощью включать/выключать устройства, а также измерять различные показатели.

Espruino-устройства показываются в ПК-системах как последовательные порты, и коммуникация с ними осуществляется при помощи REPL (консоли). Обычно подключение выполняется при помощи VT100-совместимого терминала, через который также можно писать код, но вы также можете напрямую отправлять команды, как если бы вы печатали их напрямую в REPL.

Примечание

Поскольку Espruino-устройства считают, что выводят данные в VT100-терминал, они также возвращают дополнительные символы – всё, что было написано (это называется «эхо-отображением»). Если вы знаете, что будете отправлять много команд, то можете вообще отключить эхо-отображение, отправив команду "echo(0)\n", или же вы можете отключать его построчно – для этого первым символом в строчке нужно поставить "\x10" (например, "\x10LED.toggle()\n").

USB / Serial

Web Serial (для веб-страниц)

Для доступа к устройствам с веб-страниц можно воспользоваться Web Serial API.

Чтобы упростить этот процесс, мы создали библиотеку UART.js – она служит цельным API для доступа к последовательным и Bluetooth-устройствам через сеть:

<html>
 <head>
 </head>
 <body>
  <script src="https://www.espruino.com/js/uart.js"></script>
  <button onclick="UART.write('LED1.set();\n');">LED On!</button>
  <button onclick="UART.write('LED1.reset();\n');">LED Off!</button>
 </body>
</html>

Windows

На Windows с Espruino-устройством можно легко общаться при помощи командной строки. Например, командой для включения светодиода будет следующее:

echo LED1.set() > \\.\COM10

Здесь COM10 – это COM-порт вашего устройства. Если вы хотите обернуть эту команду в исполняемый ПК-файл, просто введите следующее:

cmd.exe /c "echo LED1.set() > \\.\COM10"

Если вы не подключены к USB, вам, возможно, сначала надо будет задать скорость передачи данных. Это делается следующим образом:

MODE COM10:9600,N,8,1

Mac, Linux и Raspberry Pi

Здесь всё очень похоже на Windows – если вы знаете название своего Espruino-устройства.

Примечание

На Linux устройства имеют названия вроде /dev/ttyACM0ttyAMA0 и так далее, а на MacOS это что-то вроде /dev/cu.usmodem1234.

echo "LED1.set()" > /dev/ttyACM0

Если вы не подключены к USB, то вам, возможно, сначала надо будет задать скорость передачи данных. Это делается следующим образом:

stty -F /dev/ttyACM0 9600

Python (мультиплатформенный вариант)

Хороший тред об этом можно почитать тут.

Но на самом деле всё очень просто:

  • При помощи своего любимого языка откройте последовательный порт на 9600 бит/с.
  • Отправьте echo(0) (и символ новой строки) – это отключит эхо-отображение. Это значит, что теперь Espruino будет отправлять только текст, сгенерированный функцией print()
  • Отправьте какие-нибудь JavaScript-команды вроде digitalWrite(LED1,1)
  • Или прочтите какие-нибудь значения при помощи команд вроде print(analogRead(A0)). Результат должен прийти менее чем через секунду.
  • Перед выходом отправьте echo(1) (и символ новой строки) – это включит эхо-отображение обратно. В результате, когда вы в следующий раз подключитесь к Espruino с помощью терминала, она ответит на введённые вами символы привычным образом.

Вот пример кода на Python:

#!/usr/bin/python
import time
import serial
import sys
import json

def espruino_cmd(command):
 ser = serial.Serial(
  port='/dev/ttyACM0', # или /dev/ttyAMA0 для последовательной
                       # коммуникации с Raspberry Pi
  baudrate=9600,
  parity=serial.PARITY_NONE,
  stopbits=serial.STOPBITS_ONE,
  bytesize=serial.EIGHTBITS,
  xonxoff=0, rtscts=0, dsrdtr=0,
 )
 ser.isOpen()
 ser.write(command+"\n")
 endtime = time.time()+0.2 # подождите 0.2 секунд
 result = ""
 while time.time() < endtime:
  while ser.inWaiting() > 0:
   result=result+ser.read(1)
 ser.close()
 return result

# Считываем значение одного аналогового контакта:
#print espruino_cmd("print(analogRead(A1))").strip()
# Считываем значение трёх аналоговых контактов в массив: 
#print espruino_cmd("print([analogRead(A1),analogRead(A2),analogRead(A3)])").strip().split(',')

if len(sys.argv)!=2:
 print "USAGE: espruino_command.py "+'"'+"print('hello')"+'"'
 exit(1)

print espruino_cmd(sys.argv[1]).strip()

Кроме того, команды можно запускать прямо в оболочке:

$ ./espruino_command.py "echo(0)"
echo(0)
$ ./espruino_command.py "print('hello')"
hello
$ ./espruino_command.py "print(analogRead(A0))"
0.6546
$ ./espruino_command.py "digitalWrite(LED1,1)"
[ответа не будет, но это включит светодиод]

Чтобы коммуницировать с последовательным портом при помощи Python, вам понадобится pySerial. Если код жалуется, что не может найти serial, вам сначала нужно установить pySerial (делается это довольно просто). Пример ниже подразумевает, что вы загрузили версию 2.7 – при необходимости обновите команды:

tar xfvz pyserial-2.7.tar.gz
cd pyserial-2.7
sudo python setup.py install

Bluetooth

Об использовании BLE на Puck.js/Pixl.js/MDBT42Q читайте в следующем разделе.

Если у вас Espruino Original, вы можете подключить к её нижней части Bluetooth-модуль HC-05 или HC-06. Или же вы можете подключить этот модуль при помощи проводов к UART-контактам платы (это вариант не только для Original, но и для любой другой платы Espruino). Оба этих варианта позволят коммуницировать с платой беспроводным путём.

В этом случае Bluetooth-модуль будет работать как беспроводной последовательный порт. Когда вы выполните сопряжение со своей ОС, в ней появится новый последовательный порт. Далее можно подключаться так же, как в случае с последовательным/USB-портом (см. выше).

Bluetooth LE

Если вам надо лишь получать неприватные данные от BLE-устройства Espruino (например, температуру или данные о том, была ли нажата кнопка или нет), рекомендуем использовать рассылку BLE-объявлений. Этот метод не требует подключения и расходует мало энергии, что делает его гибким и надёжным. Больше примеров смотрите, опять же, по ссылке выше.

Если вам нужна двусторонняя коммуникация, то у BLE-устройств Espruino есть последовательный порт под названием Nordic UART Service. Он в данный момент используется во многих устройствах, но не является частью BLE-стандарта Bluetooth SIG, поэтому операционные системы не добавляют этот коммуникационный порт так же, как и другие USB-устройства.

В результате доступ к сервису Nordic UART необходимо получать напрямую через BLE.

Web Bluetooth (для веб-страниц)

Более подробно об этом читайте в этой статье.

<html>
 <head>
 </head>
 <body>
  <script src="https://www.puck-js.com/puck.js"></script>
  <button onclick="Puck.write('LED1.set();\n');">LED On!</button>
  <button onclick="Puck.write('LED1.reset();\n');">LED Off!</button>
 </body>
</html>

Кроме того, разработчики Espruino создали библиотеку UART.js, содержащую цельный API для доступа к последовательным/Bluetooth-устройствам через сеть.

Node.js / JavaScript

Запустите npm install @abandonware/noble, а затем:

/* На Linux для доступа к BLE обычно нужны права администратора 
 *
 * sudo setcap cap_net_raw+eip $(eval readlink -f `which node`)
 */

var noble = require('@abandonware/noble');

var ADDRESS = "ff:a0:c7:07:8c:29";
var COMMAND = "\x03\x10clearInterval()\n\x10setInterval(function() {LED.toggle()}, 500);\n\x10print('Hello World')\n";

var btDevice;
var txCharacteristic;
var rxCharacteristic;

noble.on('stateChange', function(state) {
 console.log("Noble: stateChange -> "+state);
  if (state=="poweredOn")
    noble.startScanning([], true);
});

var foundDevice = false;
noble.on('discover', function(dev) {
  if (foundDevice) return; 
  console.log("Найденное устройство: ",dev.address);
  if (dev.address != ADDRESS) return;
  noble.stopScanning();
  // noble не перестанет работать сразу после вызова stopScanning,
  // так что нам надо воспользоваться foundDevice,
  // чтобы гарантированно подключиться только один раз.
  foundDevice = true;
  // Теперь подключаемся!
  connect(dev, function() {
    // Подключились!
    write(COMMAND, function() {
      btDevice.disconnect();
    });
  });
});

function connect(dev, callback) {
  btDevice = dev;
  console.log("BT> Подключение");
  btDevice.on('disconnect', function() {
    console.log("Отключились");
  });
  btDevice.connect(function (error) {
    if (error) {
      console.log("BT> ОШИБКА при подключении",error);
      btDevice = undefined;
      return;
    }
    console.log("BT> Подключились");
    btDevice.discoverAllServicesAndCharacteristics(function(error, services, characteristics) {
      function findByUUID(list, uuid) {
        for (var i=0;i<list.length;i++)
          if (list[i].uuid==uuid) return list[i];
        return undefined;
      }

      var btUARTService = findByUUID(services, "6e400001b5a3f393e0a9e50e24dcca9e");
      txCharacteristic = findByUUID(characteristics, "6e400002b5a3f393e0a9e50e24dcca9e");
      rxCharacteristic = findByUUID(characteristics, "6e400003b5a3f393e0a9e50e24dcca9e");
      if (error || !btUARTService || !txCharacteristic || !rxCharacteristic) {
        console.log("BT> ОШИБКА при получении сервисов/характеристик");
        console.log("Service "+btUARTService);
        console.log("TX "+txCharacteristic);
        console.log("RX "+rxCharacteristic);
        btDevice.disconnect();
        txCharacteristic = undefined;
        rxCharacteristic = undefined;
        btDevice = undefined;
        return openCallback();
      }

      rxCharacteristic.on('data', function (data) {
        var s = "";
        for (var i=0;i<data.length;i++) s+=String.fromCharCode(data[i]);
        console.log("Получено", JSON.stringify(s));
      });
      rxCharacteristic.subscribe(function() {
        callback();
      });
    });
  });
};

function write(data, callback) {  
  function writeAgain() {
    if (!data.length) return callback();
    var d = data.substr(0,20);
    data = data.substr(20);
    var buf = Buffer.alloc(d.length);
    for (var i = 0; i < buf.length; i++)
      buf.writeUInt8(d.charCodeAt(i), i);
    txCharacteristic.write(buf, false, writeAgain);
  }
  writeAgain();
}

function disconnect() {
  btDevice.disconnect();
}
Примечание

Python

Для Python есть несколько разных BLE-пакетов.

  • bluepy – это более старый пакет, который поддерживает только Linux.
  • bleak – это более новая мультиплатформенная Python-библиотека, поддерживающая Windows, Mac OS и Linux.

Bleak

Вам нужно запустить pip install bleak, а затем можно сделать следующее:

import asyncio
import array
from bleak import discover
from bleak import BleakClient

address = "dd:0c:e4:29:32:ab"
UUID_NORDIC_TX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
UUID_NORDIC_RX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
command = b"\x03\x10clearInterval()\n\x10setInterval(function() {LED.toggle()}, 500);\n\x10print('Hello World')\n"

def uart_data_received(sender, data):
    print("RX> {0}".format(data))

# Сканировать устройства можно при помощи:
#async def run():
#    devices = await discover()
#    for d in devices:
#        print(d)

print("Подключение...")
async def run(address, loop):
    async with BleakClient(address, loop=loop) as client:
        print("Подключились")
        await client.start_notify(UUID_NORDIC_RX, uart_data_received)
        print("Команда записи")
        c=command
        while len(c)>0:
          await client.write_gatt_char(UUID_NORDIC_TX, bytearray(c[0:20]), True)
          c = c[20:]
        print("Ждём данных")
        await asyncio.sleep(1.0, loop=loop) # ждём ответа
        print("Готово!")


loop = asyncio.get_event_loop()
loop.run_until_complete(run(address, loop))

Bluepy

Пакет bluepy поддерживает только Linux.

Вам надо запустить pip install bluepy и затем вы сможете сделать следующее:

# ИСПОЛЬЗОВАНИЕ:
# python bluepy_uart.py ff:a0:c7:07:8c:29

import sys
from bluepy import btle
from time import sleep

if len(sys.argv) != 2:
  print "Fatal, must pass device address:", sys.argv[0], "<device address="">"
  quit()

# \x03 -> Ctrl-C очищает строчку
# \x10 -> Отключаем эхо-отображение на этой строчке,
          чтобы введённый текст не отправлялся обратно.
#command = "\x03\x10reset()\nLED.toggle()\n"
command = "\x03\x10clearInterval()\n\x10setInterval(function() {LED.toggle()}, 500);\n\x10print('Hello World')\n"

# Обрабатываем присланные данные:
class NUSRXDelegate(btle.DefaultDelegate):
    def __init__(self):
        btle.DefaultDelegate.__init__(self)
        # ... инициализируем тут:
    def handleNotification(self, cHandle, data):
        print('RX: ', data)
# Подключаемся, настраиваем уведомления:
p = btle.Peripheral(sys.argv[1], "random")
p.setDelegate( NUSRXDelegate() )
nus = p.getServiceByUUID(btle.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"))
nustx = nus.getCharacteristics(btle.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"))[0]
nusrx = nus.getCharacteristics(btle.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"))[0]
nusrxnotifyhandle = nusrx.getHandle() + 1
p.writeCharacteristic(nusrxnotifyhandle, b"\x01\x00", withResponse=True)
# Отправляем данные (фрагментами по 20 байт):
while len(command)>0:
  nustx.write(command[0:20]);
  command = command[20:];
# Ждём получения данных:
while p.waitForNotifications(1.0): pass
# В течение одной секунды не было данных, отключаемся:
p.disconnect()

gatttol на Linux / оболочка Raspberry Pi

Вы также можете использовать предустановленные Bluetooth-инструменты Linux, чтобы писать прямо из оболочки. Кроме того, вы можете отправлять «сырой» JavaScript-код на запрограммированное каким-то другим образом BLE-устройство Espruino.

Поиск устройства

Просто запустите sudo hcitool lescan и ждите, пока не увидите устройство с нужным названием (в данном случае нам интересно MDBT42Q c774), а затем выйдите при помощи  Ctrl + C .

$ sudo hcitool lescan
DA:34:7C:4C:5A:47 Puck.js 5a47
DA:34:7C:4C:5A:47 (unknown)
C4:7C:8D:6A:AC:79 (unknown)
C4:7C:8D:6A:AC:79 Flower care
F5:3E:A0:62:C7:74 MDBT42Q c774
F5:3E:A0:62:C7:74 (unknown)

Скопируйте адрес устройства из терминала и используйте его в последующих командах.

Далее нам надо преобразовать нашу JavaScript-команду в форму (шестнадцатеричные байты), которую можно было бы использовать в gatttool. Нам нужно проделать это с командой "LED.toggle()\n" – используйте для этого JavaScript-код ниже:

"LED.toggle()\n".split("").map(x=>(256+x.charCodeAt()).toString(16).substr(-2)).join("")
// ="4c45442e746f67676c6528290a"

Или напишите в командную оболочку следующее:

echo "LED.toggle()\n" | od -A n -t x1 | tr -d " "

Теперь можно запускать gatttool:

$ gatttool --addr-type=random -I --device=F5:3E:A0:62:C7:74
# Теперь подключаемся к устройству:
[F5:3E:A0:62:C7:74][LE]> connect
Attempting to connect to F5:3E:A0:62:C7:74
Connection successful
# Список характеристик,
# и нам нужна 6e400002-b5a3-f393-e0a9-e50e24dcca9e.
# Это характеристика Nordic TX
[F5:3E:A0:62:C7:74][LE]> char-desc
handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0002, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0004, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0006, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0007, uuid: 00002a04-0000-1000-8000-00805f9b34fb
handle: 0x0008, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0009, uuid: 00002aa6-0000-1000-8000-00805f9b34fb
handle: 0x000a, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x000b, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x000c, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x000d, uuid: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
handle: 0x000e, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x000f, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0010, uuid: 6e400002-b5a3-f393-e0a9-e50e24dcca9e
# Теперь пишем нашу команду в указатель этой характеристики (0x0010):
# Это включит LED1.
[F5:3E:A0:62:C7:74][LE]> char-write-req 0x0010 4c45442e746f67676c6528290a
Characteristic value was written successfully
# Ещё раз пишем команду и выключаем LED1:
[F5:3E:A0:62:C7:74][LE]> char-write-req 0x0010 4c45442e746f67676c6528290a
Characteristic value was written successfully
[F5:3E:A0:62:C7:74][LE]> quit

Всё это можно обернуть в одну команду оболочки:

gatttool --addr-type=random --device=F5:3E:A0:62:C7:74 --char-write-req --handle=0x0010 \
  --value=4c45442e746f67676c6528290a

Или чтобы преобразовать всю команду:

gatttool --addr-type=random --device=F5:3E:A0:62:C7:74 --char-write-req --handle=0x0010 \
 --value=`echo "LED.toggle()\n" | od -A n -t x1 | tr -d " "`

Это подразумевает, что указатель характеристики UART TX – это всегда «0x0010». Так гарантированно будет в случае, если прошивка Espruino обновлена до последней версии (если только вы не включили другие сервисы вроде HID).

Кастомная характеристика

Оставлять свою Espruino полностью открытой, чтобы кто-то мог выполнить на ней JavaScript-код – это очень небрежно и небезопасно. Поэтому вместо этого вы можете создать собственную характеристику, которая делала бы именно то, что вам нужно, и записывать данные уже в неё.

  • Подключитесь к Espruino при помощи Web IDE.
  • Загрузите на Espruino вот этот код:
NRF.setServices({
  "35ac0001-18b0-e8b7-3feb-62cec301da00" : {
    "35ac0002-18b0-e8b7-3feb-62cec301da00" : {
      value : [0],
      maxLen : 1,
      writable : true,
      onWrite : function(evt) {
        digitalWrite([LED2,LED1], evt.data[0]);
        // Здесь можно написать что-нибудь ещё,
        // чтобы выполнить какие-нибудь другие действия...
      }
    }
  }
});

Это создаст характеристику с UUID «35ac0002-18b0-e8b7-3feb-62cec301da00» (который был сгенерирован случайно – рекомендуем генерировать UUID при помощи date|md5sum). Запись в эту характеристику обновит состояние двух светодиодов с помощью двух младших битов данных.

  • Отключите IDE и воспользуйтесь gatttol:
$ gatttool --device=F5:3E:A0:62:C7:74 --addr-type=random -I
[F5:3E:A0:62:C7:74][LE]> connect
Attempting to connect to F5:3E:A0:62:C7:74
Connection successful
[F5:3E:A0:62:C7:74][LE]> char-desc
handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0002, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0004, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0006, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0007, uuid: 00002a04-0000-1000-8000-00805f9b34fb
handle: 0x0008, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0009, uuid: 00002aa6-0000-1000-8000-00805f9b34fb
handle: 0x000a, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x000b, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x000c, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x000d, uuid: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
handle: 0x000e, uuid: 00002902-0000-1000-8000-00805f9b34fb
handle: 0x000f, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0010, uuid: 6e400002-b5a3-f393-e0a9-e50e24dcca9e
handle: 0x0011, uuid: 00002800-0000-1000-8000-00805f9b34fb
handle: 0x0012, uuid: 00002803-0000-1000-8000-00805f9b34fb
handle: 0x0013, uuid: 35ac0002-18b0-e8b7-3feb-62cec301da00
[F5:3E:A0:62:C7:74][LE]> char-write-req 0x0013 01  
# <----------- включение LED1
Characteristic value was written successfully
[F5:3E:A0:62:C7:74][LE]> char-write-req 0x0013 02  
# <----------- включение LED2
Characteristic value was written successfully
[F5:3E:A0:62:C7:74][LE]> char-write-req 0x0013 02  
# <----------- включение обоих светодиодов
Characteristic value was written successfully
[F5:3E:A0:62:C7:74][LE]> char-write-req 0x0013 00
# <----------- выключение обоих светодиодов
Characteristic value was written successfully
[F5:3E:A0:62:C7:74][LE]> quit

Опять же, всё это можно уместить в одну команду (с условием, что указатель будет тем же):

gatttool --addr-type=random --device=F5:3E:A0:62:C7:74 --char-write-req --handle=0x0013 --value=01

См.также

Внешние ссылки