Глава 6. Разговоры с облаками
В предыдущих главах вы познакомились с основами Arduino и основами строительных блоков. Позвольте мне напомнить вам, из чего состоит "алфавит Arduino":
6.1 Цифровой вывод
6.1.1 Цифровой вывод
Мы использовали его для управления светодиодом, но, с правильной схемой он может использоваться для управления моторами, создания звуков и многого другого.
6.1.2 Аналоговый вывод
Даёт возможность управлять яркостью светодиода, а не просто включать или выключать его. Мы даже можем контролировать с его помощью скорость электродвигателя.
6.1.3 Цифровой ввод
Позволяет нам читать состояние простых сенсоров, таких как кнопки или датчики наклона.
6.1.4 Аналоговый ввод
Мы можем читать сигналы с датчиков, которые постоянно посылают данные, а не просто "вкл/выкл", таких как потенциометр или датчик света.
6.1.5 Последовательная связь
Позволяет связываться с компьютером и обмениваться данными, или просто следить за тем, что происходит в скетче, выполняющемся Arduino.
В этой главе мы посмотрим как обьединить работающие приложения используя всё что мы выучили в предыдущих. Эта глава покажет вам как каждый отдельный пример может быть использован как кирпичик при построении сложного проекта.
Вот где во мне проявляется желание быть дизайнером. Мы создадим версию двадцать первого века классической лампы моего любимого итальянского дизайнера, Джо Коломбо. Обьект, который мы будем создавать, называется "Атон" с 1964 года.
Рис. 6-1. Законченная лампа
Лампа, которую вы можете видеть на рис. 6-1 - это простой шар, установленный на подставке с большим отверстием для того чтобы шар не скатился с вашего стола. Такая конструкция позволяет направлять лампу куда угодно.
В терминах функциональности, мы хотим создать устройство, которое должно подключаться к интернету, получать текущий список статей в блоге Make blog (blog.makezine.com) и подсчитывать использованное количество слов "peace", "love" и "Arduino". С этими значениями мы будет создавать цвет и показыавть его при помощи лампы. Лампа имеет кнопку для включения и выключения, а также датчик света для автоматического включения.
6.2 Планирование
Давайте посмотрим чего мы хотим добиться и что нам для этого надо. Во-первых, нам требуется чтобы Arduino могла подключаться к интернету. Поскольку у платы Arduino есть только USB-порт, мы не можем подключить её к интернету напрямую, так-что нам надо подумать как соединить их. Обычно люди запускают приложение на компьютерах, которые подключаются к интернету, обрабатывают данные и посылают Arduino пакеты обработанной информации.
Arduino - это простой компьютер с небольшой памятью, она не может легко обрабатывать большие файлы, и мы подключимся к RSS-ленте для получения понятного XML-файла, который требует больше оперативной памяти. Мы создадим промежуточный элемент (прокси) для упрощения XML с применением языка Processing.
Processing
Processing - это то, откуда появился Arduino. Мы любим этот язык и используем его как для обучения программирования начинающих, так и для создания прекрасного кода. Другое преимущество Processing - это то, что у него открытый код и он работает на всех основных платформах (Mac, Linux и Windows). Он также может создавать отдельные приложения, которые будут выполняться на этих платформах. Более того, сообщество Processing довольно оживлено и полезно, к тому-же вы можете найти тысячи уже написанных примеров программ.
Прокси сделает для нас следующая работу: скачает ленту RSS с сайта makezine.com и извлечёт все слова из полученного XML-файла.Затем, пройдя по файлу, он подсчитает количество слов "peace", "love" и "Arduino" в тексте. Исходя из полученных трёх чисел мы можем вычислить значение цвета, которое пошлём Arduino. Плата отправит обратно величину света, измеренную датчиком и покажет её на экране компьютера.
Со стороны аппаратного обеспечения, мы обьединим пример с кнопкой, управлением светодиодом при помощи ШИМ (умноженное на три) и последовательную связь.
Поскольку Arduino - простое устройство, нам требуется закодировать цвет простым способом. Мы будем использовать стандартный способ, представленный в языке HTML: знак решётки # с шестью шестнадцатеричными цифрами после него.
Шестнадцатеричные цифры удобны потому, что каждое 8-битовое число сохраняется в двух битах; для десятичных чисел количество знаков меняется от одного до трёх. Предсказуемость делает код проще: ждём, пока не увидим решётку "#", затем считываем шесть знаков после неё в буфер (переменную для временного хранения данных). И, наконец, превращаем каждую группу из двух цифр в один байт, который будет соответствовать яркости одного из трёх светодиодов.
6.3 Программирование
Вы будете запускать два скетча: один скетч на языке Processing и один а языке Arduino. Вот код для Processing. Вы можете скачать его сwww.makezine.com/getstartedarduino.
Пример 6-1. Честь кода для лампы с подключением к сети, вдохновлённые записью блоге Tod E. Kurt (todbot.com)
import processing.serial.*; String feed = "http://blog.makezine.com/index.xml"; int interval = 10; // retrieve feed every 60 seconds; int lastTime; // the last time we fetched the content int love = 0; int peace = 0; int arduino = 0; int light = 0; // light level measured by the lamp Serial port; color c; String cs; String buffer = ""; // Accumulates characters coming from Arduino PFont font; void setup() { size(640,480); frameRate(10); // we don't need fast updates font = loadFont("HelveticaNeue-Bold-32.vlw"); fill(255); textFont(font, 32); // IMPORTANT NOTE: // The first serial port retrieved by Serial.list() // should be your Arduino. If not, uncomment the next // line by deleting the // before it, and re-run the // sketch to see a list of serial ports. Then, change // the 0 in between [ and ] to the number of the port // that your Arduino is connected to. //println(Serial.list()); String arduinoPort = Serial.list()[0]; port = new Serial(this, arduinoPort, 9600); // connect to Arduino lastTime = 0; fetchData(); } void draw() { background( c ); int n = (interval - ((millis()-lastTime)/1000)); // Build a colour based on the 3 values c = color(peace, love, arduino); cs = "#" + hex(c,6); // Prepare a string to be sent to Arduino text("Arduino Networked Lamp", 10,40); text("Reading feed:", 10, 100); text(feed, 10, 140); text("Next update in "+ n + " seconds",10,450); text("peace" ,10,200); text(" " + peace, 130, 200); rect(200,172, peace, 28); text("love ",10,240); text(" " + love, 130, 240); rect(200,212, love, 28); text("arduino ",10,280); text(" " + arduino, 130, 280); rect(200,252, arduino, 28); // write the colour string to the screen text("sending", 10, 340); text(cs, 200,340); text("light level", 10, 380); rect(200, 352,light/10.23,28); // this turns 1023 into 100 if (n <= 0) { fetchData(); lastTime = millis(); } port.write(cs); // send data to Arduino if (port.available() > 0) { // check if there is data waiting int inByte = port.read(); // read one byte if (inByte != 10) { // if byte is not newline buffer = buffer + char(inByte); // just add it to the buffer } else { // newline reached, let's process the data if (buffer.length() > 1) { // make sure there is enough data // chop off the last character, it's a carriage return // (a carriage return is the character at the end of a // line of text) buffer = buffer.substring(0,buffer.length() -1); // turn the buffer from string into an integer number light = int(buffer); // clean the buffer for the next read cycle buffer = ""; // We're likely falling behind in taking readings // from Arduino. So let's clear the backlog of // incoming sensor readings so the next reading is // up-to-date. port.clear(); } } } } void fetchData() { // we use these strings to parse the feed String data; String chunk; // zero the counters love = 0; peace = 0; arduino = 0; try { URL url = new URL(feed); // An object to represent the URL // prepare a connection URLConnection conn = url.openConnection(); conn.connect(); // now connect to the Website // this is a bit of virtual plumbing as we connect // the data coming from the connection to a buffered // reader that reads the data one line at a time. BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); // read each line from the feed while ((data = in.readLine()) != null) { StringTokenizer st = new StringTokenizer(data,"\"<>,.()[] ");// break it down while (st.hasMoreTokens()) { // each chunk of data is made lowercase chunk= st.nextToken().toLowerCase() ; if (chunk.indexOf("love") >= 0 ) // found "love"? love++; // increment love by 1 if (chunk.indexOf("peace") >= 0) // found "peace"? peace++; // increment peace by 1 if (chunk.indexOf("arduino") >= 0) // found "arduino"? arduino++; // increment arduino by 1 } } // Set 64 to be the maximum number of references we care about. if (peace > 64) peace = 64; if (love > 64) love = 64; if (arduino > 64) arduino = 64; peace = peace * 4; // multiply by 4 so that the max is 255, love = love * 4; // which comes in handy when building a arduino = arduino * 4; // colour that is made of 4 bytes (ARGB) } catch (Exception ex) { // If there was an error, stop the sketch ex.printStackTrace(); System.out.println("ERROR: "+ex.getMessage()); } }
Есть две вещи, которые вам потребуются для правильного запуска скетча Processing. Во-первых, вы должны указать Processing создать шрифт, который мы будем использовать для скетча. Чтобы сделать это, создайте и сохраните этот скетч. Затем, при открытом скетче, щёлкните меню "Tools" и выберите "Create Font". Выберите шрифт с именем "HelveticaNeue-Bold", выберите 32 для размера шрифта, а затем щёлкните "ОК".
Во-вторых, вам требуется удостовериться в том, что скетч использует правильный последовательный порт для связи с Arduino. Вам надо подождать, пока мы не соберём схему с Arduino и загрузим в неё скетч. На большинстве систем скетч Processing будет работать нормально. Однако если вы видите что на плате Arduino ничего не происходит и на экран не выводится никаких данных от датчика света, найдите комментарий "IMPORTANT NOTE (ВАЖНОЕ ЗАМЕЧАНИЕ)" в скетче Processing и следуйте следующим инструкциям.
// IMPORTANT NOTE: // The first serial port retrieved by Serial.list() // should be your Arduino. If not, uncomment the next // line by deleting the // before it, and re-run the // sketch to see a list of serial ports. Then, change // the 0 in between [ and ] to the number of the port // that your Arduino is connected to. // ВАЖНОЕ ЗАМЕЧАНИЕ // Первый последовательный порт, полученный функцией Serial.list(), // должен быть подключён к Arduino. Если это не так, раскомментируйте следующую // строку удалением // перед ней, и перезапустите // скетч чтобы увидеть список последовательных портов. Затем измените // 0 в между скобками [ и ] на номер порта, к которому // подключена ваша Arduino. //println(Serial.list());
Вот скетч для Arduino (также доступен на www.makezine.com/getstartedarduino):
Пример 6-2. Сетевая лампа с Arduino
#define SENSOR 0 #define R_LED 9 #define G_LED 10 #define B_LED 11 #define BUTTON 12 int val = 0; // variable to store the value coming from the sensor int btn = LOW; int old_btn = LOW; int state = 0; char buffer[7] ; int pointer = 0; byte inByte = 0; byte r = 0; byte g = 0; byte b = 0; void setup() { Serial.begin(9600); // open the serial port pinMode(BUTTON, INPUT); } void loop() { val = analogRead(SENSOR); // read the value from the sensor Serial.println(val); // print the value to // the serial port if (Serial.available() >0) { // read the incoming byte: inByte = Serial.read(); // If the marker's found, next 6 characters are the colour if (inByte == '#') { while (pointer < 6) { // accumulate 6 chars buffer[pointer] = Serial.read(); // store in the buffer pointer++; // move the pointer forward by 1 } // now we have the 3 numbers stored as hex numbers // we need to decode them into 3 bytes r, g and b r = hex2dec(buffer[1]) + hex2dec(buffer[0]) * 16; g = hex2dec(buffer[3]) + hex2dec(buffer[2]) * 16; b = hex2dec(buffer[5]) + hex2dec(buffer[4]) * 16; pointer = 0; // reset the pointer so we can reuse the buffer } } btn = digitalRead(BUTTON); // read input value and store it // Check if there was a transition if ((btn == HIGH) && (old_btn == LOW)){ state = 1 - state; } old_btn = btn; // val is now old, let's store it if (state == 1) { // if the lamp is on analogWrite(R_LED, r); // turn the leds on analogWrite(G_LED, g); // at the colour analogWrite(B_LED, b); // sent by the computer } else { analogWrite(R_LED, 0); // otherwise turn off analogWrite(G_LED, 0); analogWrite(B_LED, 0); } delay(100); // wait 100ms between each send } int hex2dec(byte c) { // converts one HEX character into a number if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } }
6.4 Сборка схемы
Рис. 6-2 показывает как собрать схему. Вам понадобится резисторы на 10 кОм (все резисторы, показанные на схеме, хотя для резисторов, подключённых к светодиодам, можно использовать меньшие значения).
Вспомните пример с ШИМ из главы 5: светодиоды имеют полярность: в этой схеме длинный вывод (анод) подключается справа, а короткий (катод) - слева. (большинство светодиодов имеют фаску со стороны катода, как показано на рисунке).
Рис. 6-2. Схема сетевой лампы с Arduino
Соберите схему как показано на рисунке, используя один красный, один зелёный и один синий светодиоды. Затем загрузите скетчи в Arduino и Processing и запустите их. Если случились какие-то проблемы, см. главу 7.
Теперь давайте завершим устройство, поместив макетную плату в стеклянный шар. Самый простейший и дешёвый выход - купить настольную лампу "FADO" в магазине IKEA. Сейчас она продаётся за US$14.99/€14.99/£8.99 (ага, прекрасно быть европейцем).
Вместо использования трёх отдельных светодиодов вы можете использовать один RGB-светодиод с четырьмя выводами. Подключите его так-же как и светодиоды на рис. 6-2, с единственным отличием - вместо трёх отдельных выводов, подключённых на вывод GND платы Arduino (называемый общий катод, или земля), у вас будет только один провод на землю.
6.5 Как собрать лампу:
Распакуйте лампу и удалите кабель от лампы к подставке. Вы больше не будете включать её в розетку.
Укрепите Arduino на макетной плате и приклейте плату к лампе.
Припаяйте длинные провода к светодиодам и приклейте их в нужном месте внутри лампы. Подключите провода от светодиода к плате (откуда вы их вытащили). Помните, что для RGB-светодиода требуется только один земляной провод.
Или найдите подходящий кусок дерева с отверстием, который может быть использован как основание для лампы, или просто отрежьте у картонной коробки для лампы верх (примерно 5 см) и сделайте отверстие для неё. Проклейте внутренности картонной коробки клеевым пистолетом по всем граням чтобы придать жёсткость коробке.
Поместите шар на основание и подключите USB-кабель к компьютеру.
Запустите код Processing, нажмите кнопку "вкл/выкл" и следите как оживает лампа.
Как упражнение попробуйте добавить код, который включит лампу в тёмной комнате. Другие возможные улучшения:
- Добавить датчик наклона для включения или выключения лампы поворотом
- Добавить маленький датчик движения для включения лампы если кто-то есть поблизости и для выключения если никого рядом нет
- Создать различные режимы для ручного управления цветом или заставить лампу плавно менять все цвета.
Подумайте о разных вещах, поэкспериментируйте и получите удовольствие!