Pull to refresh

Простое управление вашим Arduino через web

Reading time6 min
Views106K
Эта статья предназначена для новичков. Здесь будет описано как из web приложения при помощи ajax запросов посылать команды phyton скрипту, который будет передавать их через serial port непосредственно на наш arduino.
Вы приобрели себе 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 можно создать тестовый файлик и положить его в домашнюю папку.
#!/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». Дешево и сердито.
Для тех, кто хочет не только отдавать, но и принимать, напишем больше кода
Немного усовершенствуем нашу клиентскую часть.

//непосредственно управляющая функция
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
Желаю всем удачных опытов.
Tags:
Hubs:
+6
Comments4

Articles

Change theme settings