Журнал LinuxFormat - перейти на главную

LXF161:Arduino

Материал из Linuxformat
Перейти к: навигация, поиск


Ап­па­рат­ные про­ек­ты с от­кры­тым ко­дом, рас­ши­ряющие ваш кру­го­зор

Содержание

Arduino: Свой чат-бот для бесед

Ко­му нуж­ны лю­ди, ко­гда мож­но по­о­бщаться со сво­им пре­дан­ным дру­гом Arduino? Ник Вейч всту­па­ет в разговор.

(thumbnail)
Наш эксперт. Ко­гда LXF толь­ко поя­вил­ся, его дер­жа­ли на пла­ву исключительно скрип­ты Bash от Ни­ка Вей­ча. По­том их за­ме­ни­ли «лю­ди», и это, по мне­нию Ни­ка, ста­ло ша­гом на­зад...

Ес­ли что-то не под­клю­че­но к Ин­тернету, су­ще­ст­ву­ет ли оно на са­мом де­ле? Про­ек­тов с 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

  1. 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 “по­лу­чен по­сле­до­ва­тель­ный сиг­нал”

  1. serial input from device

txt=ser.readline()

#print txt

client.send(xmpp.protocol.Message(controller, txt))

Про­грам­ма на са­мом де­ле очень про­ста. Она уста­нав­ли­ва­ет по­сле­до­ва­тель­ное со­единение с Arduino, за­тем под­клю­ча­ет­ся к сер­ве­ру Jabber. По­сле под­клю­чения учет­ная запись ав­то­ри­зу­ет­ся пу­тем ука­зания па­ро­ля, и бу­дет мож­но от­прав­лять и принимать со­об­щения ча­та. За­тем про­грам­ма за­цик­ли­ва­ет­ся, ожи­дая со­бы­тий. При по­лу­чении со­об­щения с по­сле­до­ва­тель­но­го пор­та Ardu­ino она от­пра­вит его на сер­вер 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. Ку­пив та­кую пла­ту, мож­но со­брать схе­му са­мо­му. Ее схе­ма по­ка­за­на ниже, но го­раз­до про­ще ку­пить сбо­роч­ную пла­ту.

(thumbnail)
> Соб­ст­вен­ную схе­му соз­дать мож­но, но из-за стои­мо­сти и при­ро­ды ком­по­нен­тов (па­ять эле­мен­ты в кор­пу­се ИС рис­ко­ван­но) про­ще сбе­речь вре­мя и уси­лия и ку­пить го­то­вую.

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.|

Персональные инструменты
купить
подписаться
Яндекс.Метрика