Pull to refresh

Практическое применение MSP430 для web-разработчика

Reading time8 min
Views55K
На хабре предостаточно статей для начинающих о том, какой волшебный и замечательный этот MSP430 LaunchPad от Texas Instruments. Однако дальше стандартной мигалки светодиодом обычно никто не заходит. Пора исправлять эту ситуацию.
Работая в команде, мы пользуемся старым добрым 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).
Исходники обновил.
Tags:
Hubs:
+21
Comments37

Articles

Change theme settings