На хабре предостаточно статей для начинающих о том, какой волшебный и замечательный этот MSP430 LaunchPad от Texas Instruments. Однако дальше стандартной мигалки светодиодом обычно никто не заходит. Пора исправлять эту ситуацию.
Работая в команде, мы пользуемся старым добрым SVN для контроля версий. Казалось бы, причём тут микроконтроллеры?
Как раз для сигнализации очередного коммита в репозиторий я и приспособил эту дивную штуковину.

Хотелось бы такое устройство, которое будет сигнализировать мелодией об очередном коммите в любой из наших репозиториев. Было бы неплохо, чтобы у каждого разработчика была своя уникальная мелодия, и всем было бы слышно, кто только что сделал очередной коммит.
В качестве веб фронтенда к SVN прикручен WebSVN, закрытый Basic-авторизацией. Таким образом мы можем получить доступ к списку проектов и их ревизиям. В свою очередь, нам необходим процесс, который по расписанию, например раз в минуту будет проверять репозитории проектов на предмет нового коммита. Так как ближе всего к рукам был PHP, решено было писать на нём. Рабочая система — Windows 7 с Apache 2.1 + PHP 5.3.6.
При обнаружении нового коммита, скрипт посылает на COM-порт управляющую команду устройству, о том что надо проиграть мелодию автора коммита.
Итак, у нас есть WebSVN:

Средствами PHP мы парсим список проектов и заглядываем в каждый проект:
Для того, чтобы авторизоваться через Basic Auth, воспользуемся функциями библиотеки CURL.
Ещё для разбора HTML воспользуемся DOMDocument и DOM.
Т.к. с DOM из PHP работать довольно муторно, а нам достаточно взять все ссылки, воспользуемся функцией dom2array():
У каждого проекта есть своя страничка, в которой указана последняя ревизия и её автор.

Используя всё тот же CURL, выковыриваем номер ревизии и её автора. Парсинг через explode() и strpos() ужасен, но на скорую руку потянет.
Реализация банальная и местами топорная, но прекрасно работает. Читаем из текстового файлика старую ревизию и сравниваем с текущей. Если различаются, записываем и бибикаем.
Я был на 146% уверен, что работа с COM-портами не представляет сложности. Как оказалось, старый способ fopen(«COM6:», «w+») на Windows 7 уже не работает. Не хватает каких-то прав доступа. Кроме того даже если в консоли перенаправить вывод в порт, то опять возникнет ошибка доступа. Так что из cmd (bat) — файлов у нас тоже ничего не получится, и через exec() тоже не прокатит.
Крепкое гугление вывело меня на виндовое расширение — PHP Serial.
Подк��ючается как и все расширения, довольно просто и работает как два пальца:
Настала очередь программировать LaunchPad. Возьмём Energia в качестве среды разработки.
В отличие от Arduino, для MSP430 не так очевидно, как работать с Serial port. Оказывается, сужествует библиотечка TimerSerial, которая является модификацией Arduino-вской базовой библиотеки Serial.
Немного усилий, и можно писать и читать в терминал.
Эта простенькая программка читает символ и тут же пишет его обратно. Как бы эхо.
Так, с приёмом-передачей данных разобрались. Теперь бибикало.
Стандартная библиотека Tone, также как-то видоизменена в Energia. И самое неприятное, они никак не дружат с TimerSerial. Вероятно используются общие таймеры или ещё что-то в этом духе, но вместе их использовать не получилось.
Пришлось писать свой велосипед.
Все заморочки решены, осталось написать код. Вообщем-то остальное довольно просто.
Объединяем и получаем наш скетч:
Подключаем, заливаем, открываем окошко терминала, пишем туда 1, и слушаем дефолтную мелодию.

Как ни странно, но мы будем использовать именно планировщик задач для фонового выполнения нашего PHP скрипта.
Напишем CMD-файлик, который вызывает PHP и сам скрипт:
А чтобы это хозяйство не мозолило глаза чёрным окном консоли, воспользуемся нехитрой утилитой hidcon от Андрея Гречкина.
Само по себе всё работает и усердно пищит при каждом коммите. Но на столе выглядит не очень красиво. Осталось обернуть нашу новую железку в приятный пластиковый корпус, поработав чуток канцелярским ножом и изолентой:


Один день на разработку от идеи до готового устройства. Интересная и увлекательная борьба с подводными камнями и знакомство с Energia и MSP430.
Бюджет проекта ~200-300 руб.
Исходники на github.
Update: Добавил поддержку RTTTL — мелодий в хардовом режиме UART.
Для того, чтобы включить хардовый режим, надо переткнуть перемычки RT/TX (Спасибо BoxaShu).
Исходники обновил.
Работая в команде, мы пользуемся старым добрым SVN для контроля версий. Казалось бы, причём тут микроконтроллеры?
Как раз для сигнализации очередного коммита в репозиторий я и приспособил эту дивную штуковину.

Идея
Хотелось бы такое устройство, которое будет сигнализировать мелодией об очередном коммите в любой из наших репозиториев. Было бы неплохо, чтобы у каждого разработчика была своя уникальная мелодия, и всем было бы слышно, кто только что сделал очередной коммит.
Проект
В качестве веб фронтенда к SVN прикручен WebSVN, закрытый Basic-авторизацией. Таким образом мы можем получить доступ к списку проектов и их ревизиям. В свою очередь, нам необходим процесс, который по расписанию, например раз в минуту будет проверять репозитории проектов на предмет нового коммита. Так как ближе всего к рукам был PHP, решено было писать на нём. Рабочая система — Windows 7 с Apache 2.1 + PHP 5.3.6.
При обнаружении нового коммита, скрипт посылает на COM-порт управляющую команду устройству, о том что надо проиграть мелодию автора коммита.
Итак, у нас есть WebSVN:

Средствами PHP мы парсим список проектов и заглядываем в каждый проект:
index.php
<? // Забъём на время выполнения set_time_limit(0); // Подключим всякие функции и конфиги require 'functions.php'; require 'config.php'; // Готовим CURL $s = curl_init(); curl_setopt($s, CURLOPT_URL, $svnURL); curl_setopt($s, CURLOPT_USERPWD, $authLogin.':'.$authPass); curl_setopt($s, CURLOPT_USERAGENT, 'CommitBeep 1.0'); curl_setopt($s, CURLOPT_REFERER, $svnURL); curl_setopt($s, CURLOPT_RETURNTRANSFER, true); // Получаем список проектов $page = curl_exec($s); $httpCode = curl_getinfo($s, CURLINFO_HTTP_CODE); curl_close($s); // Получим ссылки на проекты $doc = new DOMDocument(); $doc->loadHTML($page); $links = $doc->getElementsByTagName('a'); foreach ($links as $link) { $link = dom2array($link); if (strpos($link['@attributes']['href'], 'listing.php?repname') !== false) { $projectName = $link['#text']; checkSVNCommit($projectName); } }
Для того, чтобы авторизоваться через Basic Auth, воспользуемся функциями библиотеки CURL.
Ещё для разбора HTML воспользуемся DOMDocument и DOM.
Т.к. с DOM из PHP работать довольно муторно, а нам достаточно взять все ссылки, воспользуемся функцией dom2array():
functions.php dom2array()
/** * Парсит DOM узел в массив * @param object $node узел */ function dom2array($node) { $res = array(); if($node->nodeType == XML_TEXT_NODE){ $res = $node->nodeValue; } else{ if($node->hasAttributes()){ $attributes = $node->attributes; if(!is_null($attributes)){ $res['@attributes'] = array(); foreach ($attributes as $index=>$attr) { $res['@attributes'][$attr->name] = $attr->value; } } } if($node->hasChildNodes()){ $children = $node->childNodes; for($i=0;$i<$children->length;$i++){ $child = $children->item($i); $res[$child->nodeName] = dom2array($child); } } } return $res; }
У каждого проекта есть своя страничка, в которой указана последняя ревизия и её автор.

Используя всё тот же CURL, выковыриваем номер ревизии и её автора. Парсинг через explode() и strpos() ужасен, но на скорую руку потянет.
functions.php checkSVNCommit()
/** * Проверяем ревизию и вызываем бибикало, если что * @param string $projectName название проекта */ function checkSVNCommit($projectName) { require 'config.php'; // Конфиг у нас тут подключается локально $old_rev = @file_get_contents('revisions/'.$projectName.'.txt'); $rev = 0; $user = 'unknown'; // Готовим CURL $s = curl_init(); curl_setopt($s, CURLOPT_URL, $svnURL.'/listing.php?repname='.urlencode($projectName).'&path=%2F&sc=0'); curl_setopt($s, CURLOPT_USERPWD, $authLogin.':'.$authPass); curl_setopt($s, CURLOPT_USERAGENT, 'CommitBeep 1.0'); curl_setopt($s, CURLOPT_REFERER, $svnURL); curl_setopt($s, CURLOPT_RETURNTRANSFER, true); // Получаем список проектов $page = curl_exec($s); $httpCode = curl_getinfo($s, CURLINFO_HTTP_CODE); curl_close($s); // Весьма топорная реализация, но зато наглядно и быстро $page = explode("\n", strip_tags($page)); foreach($page as $line) { if (strpos($line, 'Last modification:') !== false) { $line = explode(' ', $line); $rev = $line[3]; $user = $line[5]; } } echo "$projectName $rev $user<br/>\n"; // Ревизия поменялась? Бибикаем! if ($rev != $old_rev) { beep($comNumber, getMelodyByUser($user)); file_put_contents('revisions/'.$projectName.'.txt', $rev); } }
Реализация банальная и местами топорная, но прекрасно работает. Читаем из текстового файлика старую ревизию и сравниваем с текущей. Если различаются, записываем и бибикаем.
Первые подводные камни
Я был на 146% уверен, что работа с COM-портами не представляет сложности. Как оказалось, старый способ fopen(«COM6:», «w+») на Windows 7 уже не работает. Не хватает каких-то прав доступа. Кроме того даже если в консоли перенаправить вывод в порт, то опять возникнет ошибка доступа. Так что из cmd (bat) — файлов у нас тоже ничего не получится, и через exec() тоже не прокатит.
Крепкое гугление вывело меня на виндовое расширение — PHP Serial.
Подк��ючается как и все расширения, довольно просто и работает как два пальца:
functions.php beep()
/** * Посылает сигнал бибикалу * @param integer $com номер COM-порта * @param integer $melody номер мелодии */ function beep($com = 6, $melody = 1) { ser_open("COM".$com, 9600, 8, "None", "1", "None"); ser_write("$melody"); ser_close(); }
Бибикало с микроконтроллером
Настала очередь программировать LaunchPad. Возьмём Energia в качестве среды разработки.
В отличие от Arduino, для MSP430 не так очевидно, как работать с Serial port. Оказывается, сужествует библиотечка TimerSerial, которая является модификацией Arduino-вской базовой библиотеки Serial.
Немного усилий, и можно писать и читать в терминал.
Пример работы с терминалом
#include <TimerSerial.h> TimerSerial mySerial; // Начальные установки void setup() { mySerial.begin(); mySerial.println("Welcome to CommitBeep 1.0"); } // Главный цикл void loop() { while (mySerial.available()) { char inChar = (char)mySerial.read(); mySerial.write(inChar); delay(100); } }
Эта простенькая программка читает символ и тут же пишет его обратно. Как бы эхо.
Подводные камни. Продолжение.
Так, с приёмом-передачей данных разобрались. Теперь бибикало.
Стандартная библиотека Tone, также как-то видоизменена в Energia. И самое неприятное, они никак не дружат с TimerSerial. Вероятно используются общие таймеры или ещё что-то в этом духе, но вместе их использовать не получилось.
Пришлось писать свой велосипед.
Приручаем бибикало
#define NOTE_G3 196 #define NOTE_A3 220 #define NOTE_C4 262 int speakerPin; // Начальные установки void setup() { speakerPin = 14; // контакт 1.6 (или 14) у нас подключён к бибикалу pinMode(speakerPin, OUTPUT); beep(); } void playTone(int tone, int duration) { for (long i = 0; i < duration * 1000L; i += tone * 2) { digitalWrite(speakerPin, HIGH); delayMicroseconds(tone); digitalWrite(speakerPin, LOW); delayMicroseconds(tone); } digitalWrite(speakerPin, LOW); } // Бибикало void beep() { int melody[] = {NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3}; int noteDurations[] = {4,8,8,4,4}; for (int thisNote = 0; thisNote < 4; thisNote++) { int noteDuration = 1000/noteDurations[thisNote]; playTone(melody[thisNote], noteDuration); } }
Все заморочки решены, осталось написать код. Вообщем-то остальное довольно просто.
Объединяем и получаем наш скетч:
commit_beep.ino
#include <TimerSerial.h> #define NOTE_B0 31 #define NOTE_C1 33 #define NOTE_CS1 35 #define NOTE_D1 37 #define NOTE_DS1 39 #define NOTE_E1 41 #define NOTE_F1 44 #define NOTE_FS1 46 #define NOTE_G1 49 #define NOTE_GS1 52 #define NOTE_A1 55 #define NOTE_AS1 58 #define NOTE_B1 62 #define NOTE_C2 65 #define NOTE_CS2 69 #define NOTE_D2 73 #define NOTE_DS2 78 #define NOTE_E2 82 #define NOTE_F2 87 #define NOTE_FS2 93 #define NOTE_G2 98 #define NOTE_GS2 104 #define NOTE_A2 110 #define NOTE_AS2 117 #define NOTE_B2 123 #define NOTE_C3 131 #define NOTE_CS3 139 #define NOTE_D3 147 #define NOTE_DS3 156 #define NOTE_E3 165 #define NOTE_F3 175 #define NOTE_FS3 185 #define NOTE_G3 196 #define NOTE_GS3 208 #define NOTE_A3 220 #define NOTE_AS3 233 #define NOTE_B3 247 #define NOTE_C4 262 #define NOTE_CS4 277 #define NOTE_D4 294 #define NOTE_DS4 311 #define NOTE_E4 330 #define NOTE_F4 349 #define NOTE_FS4 370 #define NOTE_G4 392 #define NOTE_GS4 415 #define NOTE_A4 440 #define NOTE_AS4 466 #define NOTE_B4 494 #define NOTE_C5 523 #define NOTE_CS5 554 #define NOTE_D5 587 #define NOTE_DS5 622 #define NOTE_E5 659 #define NOTE_F5 698 #define NOTE_FS5 740 #define NOTE_G5 784 #define NOTE_GS5 831 #define NOTE_A5 880 #define NOTE_AS5 932 #define NOTE_B5 988 #define NOTE_C6 1047 #define NOTE_CS6 1109 #define NOTE_D6 1175 #define NOTE_DS6 1245 #define NOTE_E6 1319 #define NOTE_F6 1397 #define NOTE_FS6 1480 #define NOTE_G6 1568 #define NOTE_GS6 1661 #define NOTE_A6 1760 #define NOTE_AS6 1865 #define NOTE_B6 1976 #define NOTE_C7 2093 #define NOTE_CS7 2217 #define NOTE_D7 2349 #define NOTE_DS7 2489 #define NOTE_E7 2637 #define NOTE_F7 2794 #define NOTE_FS7 2960 #define NOTE_G7 3136 #define NOTE_GS7 3322 #define NOTE_A7 3520 #define NOTE_AS7 3729 #define NOTE_B7 3951 #define NOTE_C8 4186 #define NOTE_CS8 4435 #define NOTE_D8 4699 #define NOTE_DS8 4978 TimerSerial mySerial; int speakerPin; // Начальные установки void setup() { speakerPin = 14; // контакт 1.6 (или 14) у нас подключён к бибикалу mySerial.begin(); mySerial.println("Welcome to CommitBeep 1.0"); pinMode(speakerPin, OUTPUT); } void playTone(int tone, int duration) { for (long i = 0; i < duration * 1000L; i += tone * 2) { digitalWrite(speakerPin, HIGH); delayMicroseconds(tone); digitalWrite(speakerPin, LOW); delayMicroseconds(tone); } digitalWrite(speakerPin, LOW); } // Бибикало void beep(int melody) { switch ((int)melody) { case '2': { int mel[] = {NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3}; int noteDurations[] = {4,8,8,4,4}; for (int thisNote = 0; thisNote < 4; thisNote++) { int noteDuration = 1000/noteDurations[thisNote]; playTone(mel[thisNote], noteDuration); } break; } case '3': { int mel[] = {NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3}; int noteDurations[] = {4,8,8,4,4}; for (int thisNote = 0; thisNote < 4; thisNote++) { int noteDuration = 1000/noteDurations[thisNote]; playTone(mel[thisNote], noteDuration); } break; } default: { int mel[] = {NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3}; int noteDurations[] = {4,8,8,4,4}; for (int thisNote = 0; thisNote < 4; thisNote++) { int noteDuration = 1000/noteDurations[thisNote]; playTone(mel[thisNote], noteDuration); } } } } // Главный цикл void loop() { while (mySerial.available()) { char inChar = (char)mySerial.read(); beep(inChar); delay(100); } }
Подключаем, заливаем, открываем окошко терминала, пишем туда 1, и слушаем дефолтную мелодию.

Планировщик задач
Как ни странно, но мы будем использовать именно планировщик задач для фонового выполнения нашего PHP скрипта.
Напишем CMD-файлик, который вызывает PHP и сам скрипт:
D:\denwer\usr\bin\php5.exe D:\denwer\home\test\www\commit_beep\index.phpА чтобы это хозяйство не мозолило глаза чёрным окном консоли, воспользуемся нехитрой утилитой hidcon от Андрея Гречкина.
Подключение планировщика:



Последние штрихи
Само по себе всё работает и усердно пищит при каждом коммите. Но на столе выглядит не очень красиво. Осталось обернуть нашу новую железку в приятный пластиковый корпус, поработав чуток канцелярским ножом и изолентой:


Итоги
Один день на разработку от идеи до готового устройства. Интересная и увлекательная борьба с подводными камнями и знакомство с Energia и MSP430.
Бюджет проекта ~200-300 руб.
Исходники на github.
Update: Добавил поддержку RTTTL — мелодий в хардовом режиме UART.
Для того, чтобы включить хардовый режим, надо переткнуть перемычки RT/TX (Спасибо BoxaShu).
Исходники обновил.