LXF161:Arduino
|
|
|
Аппаратные проекты с открытым кодом, расширяющие ваш кругозор
Содержание |
Arduino: Свой чат-бот для бесед
Кому нужны люди, когда можно пообщаться со своим преданным другом Arduino? Ник Вейч вступает в разговор.
Если что-то не подключено к Интернету, существует ли оно на самом деле? Проектов с Arduino, не чувствующих себя одиноко, в общем, хватает, но многие из них можно было бы улучшить, если бы у них было постоянное подключение к сети и вы могли бы взаимодействовать с ними отовсюду. На нашем уроке мы рассмотрим два способа подключения Arduino, чтобы с ним можно было общаться по телефону, с ноутбука или другого устройства, подключенного к Интернету.
Первый вариант — имитация
Если Arduino нужно подключить к сети, самый простой вариант – сымитировать ее. Часто плата Arduino подключена к USB-кабелю, другой конец которого подключен к компьютеру, а тот, в свою очередь, скорее всего подключен к сети. С помощью последовательного интерфейса очень просто взаимодействовать с Arduino из приложения на этом компьютере и воспользоваться тем же приложением для дальнейшей передачи полученной информации в любое место.
Существует огромное количество проектов, которые с помощью данной технологии заставляют Arduino обновлять web-страницы, и хотя это, безусловно, может быть полезно, но выглядит слегка банально. А не превратить ли Arduino в чат-бота, заставив его передавать информацию по протоколу мгновенного обмена сообщениями вроде Jabber (XMPP) – как в Google Chat?
Все, что нам нужно в этом случае – программа, которая будет работать на подключенном к Arduino компьютере и передавать сообщения. Есть несколько вариантов ее реализации, и, пожалуй, самый простой из них – воспользоваться Python (если вы не знакомы с ним, не огорчайтесь – с минимальными изменениями в коде вы сможете запустить клиентскую программу с нашего DVD).
Понадобится указать данные своей учетной записи Jabber (на сайте Jabber.org за пару секунд можно завести новую – это проще, чем завести учетную запись в Gmail) и данные (т. е. адрес) учетной записи XMPP, к которой нужно подключиться. Также потребуется установить начальное соединение между этими учетными записями – проще всего запустить какую-нибудь программу для обмена сообщениями, например, Pidgin, и инициировать соединение. Когда соединение будет принято обеими сторонами, вы сможете переписываться с помощью новой учетной записи Jabber из Google Chat или Facebook (там тоже используется XMPP).
import serial
import xmpp
- you will need to set these
ser = serial.Serial(‘/dev/ttyACM0’, 9600)
jidname=”evilbot1@jabber.org”
controller=”someone@gmail.com”
pwd=”xxxxxxxyxyxxyxyxy”
def on_message(connection, message):
global ser
print “получено сообщение”
txt= message.getBody()
if (txt != None):
ser.write(message.getBody())
print “Соединение с jabber”””
jid=xmpp.protocol.JID(jidname)
client=xmpp.Client(jid.getDomain(), debug=[])
client.connect()
client.auth(jid.getNode(), pwd)
client.sendInitPresence()
presence = xmpp.Presence(status = ‘Running!’, show =’chat’, priority = ‘1’)
client.send(presence)
client.send(xmpp.protocol.Message(controller, “Жду ваших команд”))
client.RegisterHandler(“message”, on_message)
while True:
client.Process()
if ser.inWaiting():
print “получен последовательный сигнал”
- serial input from device
txt=ser.readline()
#print txt
client.send(xmpp.protocol.Message(controller, txt))
Программа на самом деле очень проста. Она устанавливает последовательное соединение с Arduino, затем подключается к серверу Jabber. После подключения учетная запись авторизуется путем указания пароля, и будет можно отправлять и принимать сообщения чата. Затем программа зацикливается, ожидая событий. При получении сообщения с последовательного порта Arduino она отправит его на сервер Jabber. Аналогично, при получении сообщения от подключенного пользователя (нет никаких причин ограничиваться одной учетной записью чата) оно будет перенаправлено Arduino по последовательной связи.
Однако методология немного отличается. Модуль XMPP для Python использует вызовы ответчика для обработки сообщений (ответчик – это функция, вызываемая автоматически при возникновении определенного события), что немного упрощает жизнь – но следует помнить, что на клиентской стороне стоит вызывать метод Process(), иначе сообщения будут просто накапливаться. Лучше всего делать это в цикле, в котором мы также будем проверять наличие последовательной связи с устройством. При получении сообщения Jabber при вызове client.Process() новое сообщение будет обнаружено и отправлено функции-ответчику, которую мы зарегистрировали для него (on_message). Эта функция извлечет из пакета сообщения текст и отправит его через последовательный порт Arduino. Программу можно развить, добавив фильтры для приема только тех команд, которые понимает Arduino, или заменив команды байтовыми кодами, которые будет проще обрабатывать. Но все это остается на ваше усмотрение – ведь программа, кроме всего прочего, должна оставаться как можно более прозрачной.
На Arduino нам нужен код, который откроет последовательное соединение (и отправит сигнал сообщения коду на Python, что все запущено). Для этой проверки представьте себе, что на аналоговом порте 1 есть температурный датчик, который нам нужно проверять. Прежде всего объявим несколько переменных и инициализируем последовательный порт:
float temp;
char buffer[32];
uint32_t count;
void setup() {
Serial.begin(9600);
Serial.println(“Готов”);
}
Пока все очевидно. Для главного цикла мы настроим механизм вывода некоторых данных через равные интервалы времени. Но мы также будем слушать последовательный порт на наличие команд, подлежащих отработке.
void loop() {
count = millis();
while ((millis()-count)<10000){
if (Serial.available()){
readBuffer();
parseBuffer();
}
}
sendTemp();
delay(200);
}
Пока цикл будет продолжаться меньше заданного количества времени, он будет проверять функцию Serial.available(), которая вернет true при появлении данных во входном буфере (т. е. кто-то отправляет данные). В этом случае вызовутся таинственные функции для чтения этих данных и их преобразования в команды. Когда счетчик дойдет до конца, он вызовет еще одну таинственную функцию, затем loop() завершится и начнется снова. Итак, все что нам осталось – реализовать таинственные функции.
Самая сложная из них – чтение буфера, но даже она сложна не чрезмерно. Мы будем считывать в буфер по одной строке. Это значит, что нам нужно получать данные из последовательного устройства по одному байту, сохранять их в массиве buffer, для которого мы создали глобальную переменную в первой части кода (32 символов вам должно хватить!), пока не встретится символ конца строки и она не завершится.
void readBuffer(){
int pos;
int inbyte;
const int EOL = 13; //Строки заканчиваются CR
inbyte = Serial.read();
delay(100);
pos=0;
while (Serial.available() > 0){
buffer[pos] = inbyte;
pos++;
if (pos>(sizeof(buffer)/sizeof(char))) break;
inbyte = Serial.read();
if (inbyte == EOL) buffer[pos]=0;
}
buffer[pos+1] = 0;
}
Код может показаться слегка непростым, но это не так. Переменная pos содержит количество символов во входных данных. При получении каждого байта она увеличивается на единицу. Поэтому цикл while просто продолжит считывать байты, пока не встретит символ конца строки (код ASCII – 13). Единственное, что мы добавили – завершение считывания, если количество символов во входных данных превышает размер буфера. Память в Arduino не защищена, и если вы начнете записывать в пространство за пределами того, для которого выделили место, то моментально сотрете что-нибудь важное.
Считав команду, ее нужно обработать. Так как мы закончили нулем строку в переменной buffer, то она представляет собой обычную строку на C. Это удобно, потому что мы сможем воспользоваться стандартной функцией сравнения строк, чтобы понять, является ли эта строка командой:
void parseBuffer(){
Serial.print(“you said:”);
Serial.println(buffer);
if (strcmp(buffer, “temp”) == 0) sendTemp();
if (strcmp(buffer, “time”) == 0) Serial.println(millis());
}
В данном случае мы реализовали две команды: одну, которая будет считывать температуру, и другую, которая вернет время работы Arduino. Конечно, при желании можно реализовать и более сложные вещи – разбирать параметры или использовать команды для управления выводами вместо чтения значений датчика. Оставляю вашему воображению представить то, что могут сотворить армии сетевых роботов-разрушителей. Последний кусочек мозаики – функции, которые считывают данные аналогового датчика и отправляют их на последовательное устройство:
void getTemp(){
temp=analogRead(1)*0.004882812*100;
temp = temp -273;
}
void sendTemp(){
getTemp();
Serial.print(“temp =”);
Serial.println(temp);
}
Здесь нет никаких хитростей. Помните, что для последовательного вывода нужно использовать функции print() и println(), так как для обозначения конца строки используется символ конца строки. Загрузив этот код в Arduino, вы сможете последовательно проверить его утилитой Serial Monitor, прежде чем отправлять информацию по сети.
Второй вариант — реальность
XMPP – прекрасный протокол обмена сообщениями. Он используется не только в клиент-серверных программах мгновенного обмена сообщениями вроде Google Chat, но и для передачи пакетов данных между серверами в Интернете.
К сожалению, XMPP сложноват для создания подключенного к сети Ethernet коммуникатора «все в одном» на базе Arduino. В ограниченной по объему памяти Arduino можно развернуть клиент XMPP, но особенности протокола (такие как XML для всего) означают, что большую часть времени Arduino будет тратить на работу системы обмена сообщениями, не выполняя никакой полезной работы. Накладные расходы на XMPP слишком велики. Но не душите мечту – есть другие варианты!
Подключить Arduino к сети не так сложно, как вы могли бы подумать: существуют микросхемы, предназначенные для подключения микроконтроллера к сети Ethernet и реализующие аппаратную часть протокола. Сообщество Arduino использует для этих целей микросхему Wiznet 5100, в основном потому, что она использовалась в версиях Arduino с поддержкой Ethernet и в некоторых официальных версиях Ethernet-интерфейсах Arduino. Она также хорошо подкреплена прошивками, поскольку библиотека драйверов для нее включена в основную поставку Arduino. Альтернативный вариант – Microchip ENC28J60. Существует много сборочных плат от сторонних производителей, и они могут быть гораздо дешевле (что-то вроде £ 5 против 20) по сравнению с платами от Arduino/Wiznet. Купив такую плату, можно собрать схему самому. Ее схема показана ниже, но гораздо проще купить сборочную плату.
IRC: чат для настоящих роботов
IRC существует почти столько же, сколько Интернет. Это простой и эффективный способ оскорблять людей онлайн или заигрывать с ними – в зависимости от ваших предпочтений. Он также очень прост в реализации, потому что появился во времена, когда последовательное соединение и мигающий зеленый курсор были нормой.
Об этом даже не придется много думать – все очень просто и делалось уже не один раз. Одна из самых лучших и самых простых реализаций принадлежит Кейрану «Аффиксу» Смиту [Keiran ‘Affix’ Smith], ею мы и воспользуемся.
Первым делом подключим необходимые библиотеки. Если вы пользуетесь enc26J60, замените имя соответствующей библиотеки:
#include <SPI.h>
#include <Ethernet.h>
Несколько глобальных переменных позаботятся о настройках. MAC-адрес, как вы, наверное, знаете, должен просто представлять собой уникальное число. IP-адрес нужно задавать по отношению к своей локальной сети. Адрес сервера представляет собой адрес IRC-сервера, которым мы хотим воспользоваться. Все эти адреса хранятся в массивах – так к ним проще обращаться. Также нам нужны несколько строк для хранения команд и текста, которым мы будем обмениваться с сервером:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192,168,0,28 };
byte server[] = { 38,229,70,20 }; // Freenode
int port = 6667;
String chan = “#arduino”;
String nick = “evilbot”;
String join = “JOIN “;
String nickcmd = “NICK “;
String user = “USER “;
String out = “TEMP=”;
EthernetClient client;
Клиент представляет собой класс из библиотеки Ethernet, и с ним проще подключиться к определенному серверу/порту и обмениваться данными. У него есть методы, которые имитируют последовательное подключение, так что для отправки информации будет можно использовать методы print() и println(). Установить соединение тоже просто:
void setup() {
Ethernet.begin(mac, ip);
Serial.begin(9600);
delay(200);
Serial.print(“connecting to IRC... “);
if (client.connect(server,port)) {
Serial.println(“connected!”);
client.println(nickcmd.concat(nick));
client.println(user.concat(nick));
client.println();
}
else {
Serial.println(“connection failed”);
}
}
Основное здесь – инициализация устройства Ethernet (номера контактов указывать не нужно, так как оно всегда остается в одном и том же месте – на контактах 10,11,12,13). Затем клиент подключается к указанному порту заданного сервера. Этот код использует класс EthernetClient, который в новых библиотеках для релиза 1.0 называется просто ‘client’, поэтому следите за этим, если попытаетесь адаптировать старый код.
Затем основной цикл просто считывает все входящие данные в ожидании ключевого слова, а когда находит его, отвечает запрашиваемыми данными.
void loop()
{
if (client.available()) {
client.println(join.concat(chan));
while(true)
{
String data;
data = client.read();
if(data.startsWith(“TEMP”))
{
temp=analogRead(1)*0.004882812*100 -273;
client.println(out.concat(String(temp)));
}
}
}
}
Скорее всего, вам захочется сделать что-то еще – как видите, подключение реализуется очень просто, а с помощью строк относительно легко реализовать систему команд и ответов.
Идем дальше
На следующем уроке мы посмотрим, как реализовать другие удобные сетевые возможности. Мы вернемся ко входам и исследуем различные способы реализации клавиатуры!
Arduino 1.0
Если вы пропустили предыдущий урок, напомню, что теперь в этих руководствах используется версия 1.0 кода Arduino. Между версиями есть несколько мелких различий, не самое незначительное из которых – отличие в расширении файлов-«набросков [sketches]»; не обновив свою версию, вы не сможете загрузить примеры – специально для вас мы записали новую версию на DVD.|