[Из песочницы] Очередной CI светофор. На этот раз attiny2313 и Node.js

Воодушевленный такой прекрасной идеей, как светофор, отображающий состояние билда, я решил, что нашей команде нужен такой же. Я не хотел руководствоваться прочитанными мною статьями, поскольку они используют слишком дорогие для меня решения. Поэтому, вооружившись идеей, я набросал очень приблизительный план работы для команды, а у себя в голове держал простые принципы, которым хочу следовать.e8c528b8eb6148d8b86fa9a5df4f3673.jpg

Под катом светофор из цветомузыки и пластиковых бутылок, USB модуль управления светофором на attiny2313 за доллар, а так же софт для опроса Jenkins и управления USB модулем на Node.js.

Проект был разделен на 3 частиСветофор. Снимать светофор с перекрестка или пытаться купить его мы не собирались. Мы не ставили перед собой цель повесить в офисе настоящий светофор, нам вполне по силам сделать его из подручных материалов. Электроника. Я сразу отбросил Rasbery Pi и Arduino как избыточные и дорогие решения. Мне нужен был доступ к устройству по USB, я планироал использовать напряжение питания USB в качестве источника питания ламп светофора, то есть избавить схему от дополнительного питания. Софт для работы с USB на стороне компьютера. Задача: опрос Jenkins текущего статуса определенного билда и отправка соответствующего сообщения по USB. Светофор Светофор решили делать из старой цветомузыки, ровный корпус и хорошие стекла — это то, что нужно. Козырьки были вырезаны из пластиковых бутылок и покрашены вместе с корпусом в чёрный цвет. От внутренней начинки избавились, ведь он работал от сетевого напряжения, а нам нужно переделать на 5в от USB.Вместо ламп я использовал светодиоды, которые предварительно выпаял из светодиодной ленты. Для большей яркости решил сделать сборки по 4 светодиода, соединенных параллельно и ограничил протекающий через них ток резисторами по 100ом, порты attiny2313 нормально справляются с нагрузкой около 20 мА.

Картинка e7dcab8e92f447dcb5d44a2d5d3a7dab.jpg Для того, чтобы стекла светофора освещались полностью, нужно было сделать отражатели, для этого от пластиковых бутылок была отрезана верхняя часть и покрыта изнутри алюминиевым скотчем. Стекла у нас крепятся снаружи, по этому резать бутылку нужно было так, чтобы диаметр окружности среза был чуть больше диаметра отверстия для стекла, так получившийся отражатель можно было вставить снаружи и зажать стеклом что избавило от необходимости придумывать крепление отражателей.Картинка 7b7d1cb3b489412c9f6f17d91713f481.jpg Светодиодные сборки я прикрепил к крышкам от бутылок при помощи болтов М3 и трубок с внутренним диаметром 2 мм, сделал это для того, чтобы была возможность отрегулировать расстояние для большей яркости.Картинка a5d663cf593c4f8ead71a0ea8ee957ac.jpg Еще картинки 30d33cc563b0455badd639128f931c77.jpg6ea7ec9b1732443389521b9be46e9da5.jpg Электроника В наличии у меня были два AVR микроконтроллера: более дешевый attiny13 с 1kB памяти и чуть дороже attiny2313 c 2kB памяти. Для того, чтобы устройство могло работать с USB протоколом, я не собирался усложнять схему дополнительными USB модулями так как это можно сделать прямо на MK при помощи прекрасной библиотеки V-USB.Минимальный размер памяти, необходимый этой библиотеке, равен около 1.3kB — поэтому выбор пал на attiny2313. Цена этого микроконтроллера 1–2 доллара, купить его тоже не составит никакого труда, так как это очень популярная модель.

Схема электроники Схема устройства очень простая, для наглядности покажу свою, но в качестве руководства советую изучить возможные варианты, так как здесь предложены 3 варианта схем, описаны их достоинства и недостатки: 5c897bfa2ab644d98f32a1eece83926e.png

Основная часть схемы — это обвязка, необходимая для корректного определения компьютером устройства как USB. Она необходима для изменения напряжения на контактах D+ и D- USB. Дело в том, что напряжение на контактах питания USB и питание моей схемы равно 5в, но для сигналов на D+ и D- напряжение должно быть 3.3в, для этого на них стоят стабилитроны. Подключение светодиодов у меня расположено так только потому, что я паял без предварительной трассировки, по этой же причине я не выкладываю чертежи платы. Так же на схеме присутствует динамик, он необходим для воспроизведения мелодии из этой статьи, когда падает билд.

Готовый USB модуль 1d17eebad6694dd0b6f63bebaad1fe33.jpg119769d4634f4316adcb4909a4917c8f.jpg Принцип работы: USB модуль умеет совсем чуть-чуть. Либо светить одним светодиодом, либо моргать одним из них. Звучание мелодии на упавший билд не было задумано изначально, а было добавлено в уже работающее устройство модификацией прошивки.После подключения USB библиотеки памяти для реализации необходимого функционала оставалось очень немного, поэтому я решил максимально упростить логику на стороне МК и возложить всю ответственность за правильную работу устройства на плечи компьютерного софта. Так я пришел к следующей форме протокола: устройство будет принимать сообщения, состоящие из двухзначного числа, где первая цифра — это пин порта, а вторая — это флаг, указывающий, должен ли светодиод гореть или моргать.

Запуск мелодии происходит только при падении билда, в общем у меня там хардкод, я не планировал конфигураций для него.

Прошивка Код прошивки я всегда пишу на C в Eclipse, компилятор gcc из набора WinAVR, прошивку заливаю программатором USBasp, купленном на алиекспресс за 4.5$, прошиваю фьюзы программой Khazama.Начать стоит с прошивки фьюзов. В схеме используется 12MHz кварц и для его использования фьюзы должны быть установлены на кварц с частотой больше 8MHz (CKSEL=1110). Кроме того, нужно не забыть убрать деление частоты на 8 (CKDIV8=1), если я не ошибаюсь, в attiny2313 делитель установлен по дефолту, повторюсь, его нужно отключить (установить единицу). Кварц на 12MHz — не единственный возможный вариант, о возможных вариантах почитайте внизу этой страницы. С таким малым количеством памяти мне стоило выбрать 16MHz, но я решил остановиться на 12MHz.

Первым делом необходимо воспользоваться шаблонным кодом USB библиотеки. Папка usbdrv библиотеки должна быть в папке проекта.

#include #include #include #include #include

#include «usbdrv/usbconfig.h» #include «usbdrv/usbdrv.h»

PROGMEM const char usbHidReportDescriptor[22] = { 0×06, 0×00, 0xff, 0×09, 0×01, 0xa1, 0×01, 0×15, 0×00, 0×26, 0xff, 0×00, 0×75, 0×08, 0×95, sizeof (uchar), 0×09, 0×00, 0xb2, 0×02, 0×01, 0xc0 };

int main (void) { wdt_enable (WDTO_1S); usbInit (); usbDeviceDisconnect ();

for (uchar i = 0; i<250; i++) { // wait 500 ms wdt_reset(); _delay_ms(2); }

usbDeviceConnect ();

sei ();

while (1) { wdt_reset (); usbPoll (); _delay_us (100); }

return 0; } Этот код необходим для работы USB библиотеки. Кроме этого кода необходимо так же настроить несколько параметров, для этого нужно файл usbconfig-prototype.h в папке usbdrv переименовать в usbconfig.h и подправить необходимые параметры, в моем случае — это:

#define USB_CFG_IOPORTNAME D #define USB_CFG_DMINUS_BIT 3 #define USB_CFG_DPLUS_BIT 2 #define USB_CFG_INTERFACE_CLASS 3 #define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH 22 Здесь стоит отметить, что есть возможность использовать любые ноги МК, но USB_CFG_DPLUS_BIT должен указывать на ногу, поддерживающую внешние прерывания, так как по этой ноге будут приходить сигналы с USB порта.

Метод, обрабатывающий пришедшие данные, выглядит так:

#define PORT_D_THRESHOLD 40 // с какого числа начинать использование порта D #define SERIALIZE_DEVIDER 10 // 51 / 10 = пин 5 и моргание 1 #define PLAY_ON_STATUS 30 // 30 — это горящий красный, начать воспроизведение мелодии

volatile uint8_t *blinkPort = &PORTB; uint8_t pin; uint8_t isBlink; uint8_t play;

USB_PUBLIC uchar usbFunctionSetup (uchar data[8]) { usbRequest_t *rq = (void *)data; // cast data to correct type uint8_t status = rq→wValue.bytes[0]; //40/41 — 20/21 — 30/31 (возможные значения) // пишу 0 во все используемые мною пины очищая предыдущие состояния // чтобы избежать одновременного свечения нескольких светодиодов PORTD &=~ ((1 << PD5) | (1 << PD4)); PORTB &=~ ((1 << PD2) | (1 << PD3));

// если в сообщении пины 4 или 5, то работать будем с портом D // иначе с портом B if (status < PORT_D_THRESHOLD) { blinkPort = &PORTB; } else { blinkPort = &PORTD; }

// десериализация isBlink = status % SERIALIZE_DEVIDER; pin = status / SERIALIZE_DEVIDER; play = status % PLAY_ON_STATUS == 0;

return 0; } Этот метод отрабатывает при получении устройством данных по USB, здесь я меняю переменные состояния в зависимости от пришедших данных. Дальше эти переменные будут использоваться в главном цикле программы.

Чтение данных происходит из значения wValue, не самый правильный вариант передачи данных, но я посчитал его вполне уместным в моем случае.

В моей схеме для зеленого сигнала светофора используется порт D, а для желтого и красного порт B, поэтому мне пришлось объявить указатель на используемый порт и добавить условие для использования порта D, если пришел сигнал об успешном билде. Если для всех сигналов использовать разные пины одного порта, то от этого условия можно отказаться.

Метод main (): Кроме инициализации USB библиотеки необходимо настроить порты на output, в моем случае нельзя настраивать весь порт D, т.к. порт D будет использоваться USB библиотекой. Я выставил единички только на пинах, которые буду использовать: DDRD = 0×30; //пины 5 и 4 порта D DDRB = 0xC; //пины 2 и 3 порта B Main loop: Главный цикл начинается с необходимых для работы USB: wdt_reset (); usbPoll (); Дальше идет логика, отвечающая за свечение/моргание одного из светодиодов:

blinkDelay++; if (blinkDelay > BLINK_DELAY) { blinkDelay = -BLINK_DELAY; }

if (isBlink == 0) { *blinkPort |= (1 << pin); } else { if(blinkDelay == 0) { *blinkPort ^= (1 << pin); } } Если флаг моргания не установлен, то просто пишется единица в необходимый бит

*blinkPort |= (1 << pin) . Иначе необходимый бит инвертируется *blinkPort ^= (1 << pin) , таким образом получается моргание светодиодом. Задержка в цикле всего 100us, поэтому для более редкого моргания добавлена искусственная задержка в виде переменной-счетчика blinkDelay.Дальше идет блок отвечающий, за воспроизведение мелодии:

if (play == 1) { if (soundDelay < duration) { if(soundDelay % frequency <= FREQUECY_EDGE) { PORTD ^= (1 << PD5); } } else { soundDelay = 0; if(note == NOTES_COUNT) { note = 0; play = 0; }

duration = pgm_read_word_near (durations + note) * DURATION_MULTIPLIER; frequency = FREQUENCY_DEV / pgm_read_word_near (frequences + note);

note++; }

soundDelay++; } Из публикации «Музыкальный дверной звонок в стиле Star Wars на Arduino» были взяты только массивы частот и длина нот, как я уже упоминал, памяти оставалось очень мало по этому процесс воспроизведения мелодии интегрирован в общий цикл, чтобы не нарушить работу USB библиотеки. Я решил не делать кастомных задержек для задания нужной частоты и длинны ноты.

Попытаюсь описать процесс как можно детальнее. Для того, чтобы подать на динамик звук нужной тональности и длительности, необходимо переключать состояние пина с частотой заданной ноты и делать это на протяжении времени длительности заданной ноты. У меня этот процесс происходит в основном цикле и выполняется параллельно остальным задачам. Для этого я считаю длительность текущей ноты и ее частоту (константы здесь зависят от задержки в конце главного цикла и были подобраны вручную):

duration = pgm_read_word_near (durations + note) * DURATION_MULTIPLIER; frequency = FREQUENCY_DEV / pgm_read_word_near (frequences + note); Пока длительность не достигнута, переключаю состояние пина, но не на каждый тик, а при условии, что текущий тик делиться без остатка на frequency, то есть, таким образом я задаю нужную частоту для достижения нужной тональности. Здесь я немного схитрил, у меня в коде допускаю остаток после деления, сделал это потому, что некоторые ноты будут звучать одинаково из-за низкой точности таких расчетов, что сильно портит мелодию, а допуск по остатку делает такие ноты разными.

Софт После того, как схема собрана, а микроконтроллер прошит (как и чем прошивать ищите в интернетах вашего города), можно перейти к компьютерному софту.Вариантов здесь предостаточно. Для работы с DIY USB большой популярностью пользуется виртуальный COM порт, та же Arduino создает виртуальный COM порт, работать с которым довольно просто, с ним можно работать даже обычным bat файлом. Но мне очень хотелось попробовать работу именно с USB, поэтому моя прошивка настроена как HID устройство, и софт должен уметь работать именно с USB.

Пишем в USB После небольшого расследования оказалось, что один из самых простых вариантов — это пакет USB для Node.js. Для того, чтобы отправить сигнал об упавшем билде, достаточно написать 4 строчки: var usb = require ('usb'); var device = usb.findByIds (5824, 1500); // VID и PID нашего устройства (эти стоят по умолчанию в V-USB конфиге) device.open (); device.controlTransfer (usb.LIBUSB_REQUEST_TYPE_RESERVED, usb.LIBUSB_TRANSFER_TYPE_CONTROL, 30, 0, new Buffer (0), function (error, data){}); Здесь число 30 отсылается параметром wValue на устройство, а то в свою очередь подаст напряжение на третий пин порта B и начнет проигрывание мелодии.

Узнаем статус джобы у Jenkins С опросом Jenkins у Node.js тоже никаких проблем, подключаем пакет jenkins-api и пишем: var jenkinsapi = require ('jenkins-api'); var JOB_NAME = 'JOB'; var JENKINS_URL = 'URL'; var jenkins = jenkinsapi.init (JENKINS_URL);

jenkins.last_build_info (JOB_NAME, function (err, data){ console.log ('Статус билда', data.result); }); В консоли увидим: SUCCESS, FAILURE или null (в процессе сборки).

Драйвер для Windows Для работы с USB модулем необходимо установить драйвер libUSB, это можно сделать либо вручную, либо воспользоваться программой zadig, взятой со страницы пакета USB из пункта Installation; за подробностями — туда, там же инструкции для Linux и OSX.Всё вместе Короткое видео:[embedded content]

Полные исходники здесь.

Спасибо за внимание.

© Habrahabr.ru