
Вы приобрели себе Arduino, попробовали несколько примеров, поигрались со скетчами. Но вам этого мало, вы хотите управлять, управлять всем этим добром через интернет. Самый простой способ — это приобрести шилдик с Ethernet-портом и подключить его к Arduino (или приобрести платку с уже встроенным Ethernet ). Но она и стоит дороже и в управлении надо поднатаскаться.
Для работы нам понадобятся:
— HTTP сервер
— интерпретатор python
— Arduino
Тут я опишу где взять первое и второе, и как их подружить
Теперь по порядку. Как HTTP сервер я использую Apache. Установить его не составит труда. Если вы совсем новичок и используете windows, то можете взять пакет Denwer с официального сайта, в его составе есть Apache.
Python (я использовал версию 3.3) можете взять так же с официального сайта и установить. Теперь нам надо подружить наш Apache и python. Самый простой способ — это запускать python как cgi. Для этого открываем файл httpd.conf в папке conf в том месте где вы поставили свой apache (если вы поставили denwer то путь будет примерно следующим: [буква виртуального диска]:\usr\local\bin\apache)
Ищем строчку
AddHandler cgi-script .cgi
Добавляем в конце через пробел .py и смотрим, чтоб в начале строки не было знака #. Сохраняем, перезапускам сервер.
Теперь для проверки тесной дружбы pythone и apache можно создать тестовый файлик и положить его в домашнюю папку.
Обратите внимание что первой строкой мы показываем где у нас лежит интерпретатор языка. У меня, например, он лежит по адресу C:/Python33/python.exe. Думаю, разберетесь. Назовите его как хотите и зайдите на него через браузер, например, так: localhost/my_first_test_phyton_file.py. Если увидите «hello world», то все хорошо.
Python (я использовал версию 3.3) можете взять так же с официального сайта и установить. Теперь нам надо подружить наш Apache и python. Самый простой способ — это запускать python как cgi. Для этого открываем файл httpd.conf в папке conf в том месте где вы поставили свой apache (если вы поставили denwer то путь будет примерно следующим: [буква виртуального диска]:\usr\local\bin\apache)
Ищем строчку
AddHandler cgi-script .cgi
Добавляем в конце через пробел .py и смотрим, чтоб в начале строки не было знака #. Сохраняем, перезапускам сервер.
Теперь для проверки тесной дружбы pythone и apache можно создать тестовый файлик и положить его в домашнюю папку.
#!/Python33/python.exe
print ("STATUS: 200 OK\n\n")
print ("<b>hello world</b>")
Обратите внимание что первой строкой мы показываем где у нас лежит интерпретатор языка. У меня, например, он лежит по адресу C:/Python33/python.exe. Думаю, разберетесь. Назовите его как хотите и зайдите на него через браузер, например, так: localhost/my_first_test_phyton_file.py. Если увидите «hello world», то все хорошо.
Код основного управляющего скрипта на JavaScript предельно прост:
//Порт к которому подключен Arduino
var serialPort = 'COM5';
//непосредственно управляющая функция
var Arduino = function(command, callback){
$.get('c.py',{
c:command,
p:serialPort
}, callback);
}
Единственное что тут надо менять, как вы догадались, это порт, на котором у вас подключен arduino. Его всегда можно посмотреть в windows используя Диспетчер устройств. Мы его будем передавать в наш python скрипт чтоб тот знал на какой serial port отправлять полученные данные.
Теперь, если мы сделаем вызов нашей функции, например: Arduino(123), то скрипт создаст ajax запрос вида с.py?c=123&p=COM5 и пошлет его на наш python скрипт c.py. Рассмотрим, что он из себя представляет:
#!/Python33/python.exe
import serial
import cgi
print ("STATUS: 200 OK\n")
req = cgi.FieldStorage();
ser = serial.Serial(req['p'].value, 9600, timeout=1)
ser.write(bytes(req['c'].value,'latin'))
ser.close()
print ("ok")
Фактически он просто принимает значение параметра «с», передает его в serial port «p» и пишет «ok». Дешево и сердито.
Для тех, кто хочет не только отдавать, но и принимать, напишем больше кода
Немного усовершенствуем нашу клиентскую часть.
Теперь, поскольку мы превратили Arduino в класс, то простейший вызов будет примерно таким:
Ну и, конечно, надо немного изменить серверную часть:
Тут почти ничего не поменялось, кроме того, что когда сервер в запросе получает параметр r=1 то он ожидает от Arduino ответ.
И мы добавили проверку на то, смог ли наш скрипт открыть serial port. Если нет, то вернет ключевое слово «error»
//непосредственно управляющая функция
var Arduino = function(sp, errorCallback) {
this.serialPort = sp;
this.errorCallback = errorCallback || function(){
console.log('Error');
}
this.send = function(data, callback){
var callback = callback;
var self = this;
data['p'] = this.serialPort;
data['s'] = Math.round(Math.random()*1000); //на всякий случай, чтобы браузер не кешировал
$.ajax({
url:'c.py',
data:data,
success:function(data){
if($.trim(data) == 'error'){
self.errorCallback();
} else {
if(typeof callback == "function") callback(data);
}
}
});
}
//передаем
this.set = function(command, callback){
this.send({
c:command,
r:0
}, callback);
}
//передаем и ожидаем ответ
this.get = function(command, callback){
this.send({
c:command,
r:1 //флаг отвечающий за режим "ожидаем ответа"
}, callback);
}
}
Теперь, поскольку мы превратили Arduino в класс, то простейший вызов будет примерно таким:
var myArduino = new Arduino('COM5');
myArduino.set(113); //зажигаем светодиод на пине 13
myArduino.get(36,function(data){console.log(data)}); //смотрим состояние пина 6. и выводим его в консоль
Ну и, конечно, надо немного изменить серверную часть:
#!/Python33/python.exe
import serial
import cgi
print ("STATUS: 200 OK\n")
req = cgi.FieldStorage();
try:
ser = serial.Serial(req['p'].value, 9600, timeout=1)
except:
print("error")
exit()
ser.write(bytes(req['c'].value,'latin'))
if int(req['r'].value) == 1:
res = '';
while not res:
res = ser.readline()
print(res.decode('UTF-8'))
else:
print ("ok")
ser.close()
Тут почти ничего не поменялось, кроме того, что когда сервер в запросе получает параметр r=1 то он ожидает от Arduino ответ.
И мы добавили проверку на то, смог ли наш скрипт открыть serial port. Если нет, то вернет ключевое слово «error»
Теперь давайте рассмотрим скетч для arduino, который все это принимает и обрабатывает:
#include <Servo.h>
Servo myservo;
void setup() {
Serial.begin(9600);
}
String getParam(){
String re;
while (Serial.available()) {
re.concat(Serial.read()-48);
}
return re;
}
int getPin(String p){
return p.substring(0,2).toInt();
}
int getVal(String p){
return p.substring(2,6).toInt();
}
// Главный цикл
void loop() {
while (Serial.available()) {
char command = (char)Serial.read();
String param = getParam();
int pin = getPin(param);
int p;
switch (command) {
case '0': //Digital write
pinMode(pin,OUTPUT);
digitalWrite(pin, LOW);
break;
case '1': //Digital write
pinMode(pin,OUTPUT);
digitalWrite(pin, HIGH);
break;
case '2': //Servo
myservo.attach(pin);
p = getVal(param);
myservo.write(p);
break;
case '3': //Digital read
pinMode(pin,INPUT);
Serial.print(digitalRead(pin));
break;
case '4': { //Analog read
int aPin = A0;
switch (pin) {
case 1: aPin = A1; break;
case 2: aPin = A2; break;
case 3: aPin = A3; break;
case 4: aPin = A4; break;
case 5: aPin = A5; break;
}
Serial.print(analogRead(aPin));
}
break;
case '5': //Analog write
pinMode(pin,OUTPUT);
p = getVal(param);
analogWrite(pin, p);
break;
}
}
}
По serial port мы будем передавать команды вида: 1234567 где:
[1] — номер команды
[23] — номер пина
[4567] — данные для пина, если надо.
Например:
113 — установит пин 13 на вывод и передаст по нему состояние HIGH (то-есть включит).
013 — установит пин 13 на вывод и передаст по нему состояние LOW (то-есть выключит).
209100 — установит пин 9 как управляющий сервоприводом и передаст ему значение 100 через ШИМ модуляцию.
310 — установит пин 10 на ввод и считает с него данные HIGH / LOW и вернет как 1 или 0 соответственно.
Вы запросто можете дописывать и свои команды в switch case блок.
Теперь добавим немного красоты в нашу frontend часть и получим, например, такое
Далее я добавил немного
Для web-части использовал Bootstrap (исключительно из-за удобства и его «резиновости») и jQuery (для ajax).
Теперь посмотрим как это работает.
Сначала надо указать на каком порту у вас устройство и сколько пинов имеет. Потом выбрать на каком пине у вас что находится, и вперед к управлению.
Из недостатков такого подхода можно отметить относительно медленную скорость обмена данных. Чтоб узнать состояние, например, кнопки надо посылать запросы, но слишком часто это делать нельзя, так как можем упереться в занятый serial port. На веб-сокетах работало бы быстрее, но это уже чуть более продвинутая тема, которую я, если захотите, освещу позже.
Проверялось все под Windows 8 х64. Наверно, есть какие-то особенности реализации всего этого под другие системы, буду рад услышать об этом в комментариях.
Теперь о том, где все это может пригодится: например можно сделать демонстрационный стенд; управлять положением камеры; подключить датчик температуры и прочие приборы и удаленно наблюдать за каким нибудь процессом и т.д.
Архив с проектом
Для запуска на iPad в полный экран я использовал бесплатную программу oneUrl
В тематические хабы не вставил только лишь из за отсутствия кармы.
Это первая моя статья. Буду рад ответить на вопросы.
UPD: По просьбам трудящихся я потестил так же этот метод на MacOS. Особых проблем не возникло. На маке обычно уже стоит по умолчинию python, единственное что надо сделать, это подружить его с apache. Первая строка в c.py будет
#!/usr/bin/python
Так же, возможно у вас не будет установленно расширение для питона pyserial, оно устанавливается простой командой в консоли:
easy_install -U pyserial
Далее следует обратить внимание, что обычно предустановленная версия python достаточно старая и может не работать строка
ser.write(bytes(req['c'].value,'latin'))
Я её заменил на
ser.write(bytes(req['c'].value.decode('latin')))
Все заработало.
Не забудьте посмотреть на каком порту у вас подключится девайс. Это удобно смотреть например через саму программу Arduino. Меню Сервис->Последовательный порт. У меня например он имел такой вот вид: /dev/cu.usbmodemfd141
Желаю всем удачных опытов.