«Achtung!» или мониторим состояние сборки проекта

в 7:20, , рубрики: arduino, python, Блог компании Mail.Ru Group
«Achtung!» или мониторим состояние сборки проекта

Для сборки проекта, запуска тестов и проверки качества кода мы (в проекте «Календарь Mail.ru») используем Jenkins CI. Запуск сборки происходит сразу же после пуша в репозиторий git (по хуку) и, конечно же, хочется своевременно получать информацию о провалившихся сборках. С одной стороны, уведомления по email вроде бы достаточно, с другой стороны хочется чего-то более заметного и весёлого.

Года три или четыре назад, когда я работал над другим проектом, для схожих целей была куплена мигалка, работающая от сети 220 вольт, для управления ею был приобретён модуль MP709 (USB-реле) небезызвестной фирмы MasterKit. Решение оказалось неудачным: во-первых, поддержки Linux у этого модуля на тот момент не было (пришлось реверсить протокол обмена данными и писать свой «драйвер» под Linux), во-вторых, расположение мигалки ограничивали провода, тянущийся от компьютера и от розетки. Так или иначе, конструкция была заброшена в дальний ящик и ждала своего часа.
Недавно у меня в очередной раз зачесались руки, захотелось сделать что-то интересное, но бесполезное, и я вспомнил про мигалку. Покопавшись в куче хлама я выудил оттуда: Arduino Nano — две штуки, модуль реле — одна штука, приёмник MX-05V — одна штука и передатчик MX-FS-03V — одна штука. Засунув весь остальной хлам в дальний угол на балконе, дожидаться своего следующего часа, я приступил к сборке устройства.
Лирическое отсупление: когда-то, давным-давно, когда я был молодой и у меня было много времени, я бы взял пару микроконтроллеров AVR, вытравил бы плату, спаял бы устройство, запрограммировал бы его на языке ассемблера… И получил бы то же самое на выходе. Удовольствия от процесса больше, результат — тот же.

Hardware

Схема получилась предельно простой, благо в наше время огромное количество различных электронных модулей, конструкторов и блоков позволяет собрать электронное устройство любому человеку, не бравшему ни разу паяльник в руки и не изучавшему схемотехнику и программирование.Передатчик (Arduino Nano + MX-FS-03V)

«Achtung!» или мониторим состояние сборки проекта

Приёмник (MX-05V + Arduino Nano + модуль реле):

«Achtung!» или мониторим состояние сборки проекта

Принцип работы ещё проще: Arduino подключается к порту USB компьютера и слушает команды через последовательный интерфейс. Если команда валидна, она будет передана через радиопередатчик. Во втором блоке другая Arduino слушает приёмник и при поступлении команды либо включает, либо выключает реле. Всё. Никакой обратной связи, никаких проверок (контрольных сумм), никакой логики.
Программа передатчика выглядит следующим образом:

#include <VirtualWire.h>
 
int ledPin = 13;  // pin with led on it
 
char incoming_byte;   // a buffer to store the incoming messages
char input_data[12];  // the size of the message
int input_size = 0;  // counter, just counter
 
void setup() {
    // setup led pin
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, LOW);
 
    // initialize serial (9600 bps)
    Serial.begin(9600);
    Serial.println("Ready for achtung");
 
    // initialize the IO and ISR
    vw_set_ptt_inverted(true);  // required for DR3100
    vw_setup(1200); // bits per sec
}
 
void loop() {
    while (Serial.available() > 0) {
        incoming_byte = Serial.read();  // read the incoming byte
 
        if (incoming_byte == 'n') {
            input_data[input_size++] = 0;
            if (strncmp(input_data, "Achtung ON"10) == 0 || strncmp(input_data, "Achtung OFF"11) == 0) {
                digitalWrite(ledPin, HIGH);
                vw_send((uint8_t *)input_data, input_size);
                vw_wait_tx();  // wait until the whole message is gone
                digitalWrite(ledPin, LOW);
 
                Serial.println("OK");
            } else {
                Serial.println("FAIL");
            }
            while (input_size-- >= 0) {
                input_data[input_size] = 0;
            }
            input_size = 0;  // reset counter
        } else {
            if (input_size <= 11) {
                input_data[input_size] = incoming_byte;
            }
            input_size++;
        }
    }
}

Программа приёмника:

#include <VirtualWire.h>
 
int ledPin = 13;  // pin with led on it
int relayPin = 3;  // pin with relay on it
 
byte input_data[VW_MAX_MESSAGE_LEN];  // a buffer to store the incoming messages
byte input_size = VW_MAX_MESSAGE_LEN;  // the size of the message
 
void setup() {
    // setup led pin
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, LOW);
 
    // setup relay pin
    pinMode(relayPin, OUTPUT);
    digitalWrite(relayPin, HIGH);
 
    // initialize the IO and ISR
    vw_set_ptt_inverted(true);  // required for DR3100
    vw_setup(1200); // bits per sec
    vw_rx_start(); // start the receiver
}
 
void loop() {
    // check if we have some data to process
    if (vw_get_message(input_data, &input_size)) {
        digitalWrite(ledPin, HIGH);
        if (strncmp((char*) input_data, "Achtung ON"10) == 0) {
            digitalWrite(relayPin, LOW);  // achtung! turn relay on
        } else if (strncmp((char*) input_data, "Achtung OFF"11) == 0) {
            digitalWrite(relayPin, HIGH);  // turn relay off
        } else {
            delay(1000);
        }
        digitalWrite(ledPin, LOW);
    }
}

Как видим, всё предельно просто. Подключаем микроконтроллеры, прошиваем, соответственно, приёмник и передатчик с помощью встроенно в программу Arduino загрузчика, запускаем «Serial Monitor», подключенный к передатчику, и передаём команды включения/выключения реле, а так же невалидные команды (для проверки):

«Achtung!» или мониторим состояние сборки проекта

Реле включается-выключается, всё работает, всё хорошо.
Самое время разобрать мигалку и посмотреть, куда мы разместим приёмник и реле:

«Achtung!» или мониторим состояние сборки проекта

Мы видим, что внутри много места. Приступаем к сборке устройства, но прежде необходимо разобраться с питанием приёмника.
Я поступил просто: взял старый, ставший ненужным, блок питания от сотового телефона Nokia, разобрал его и получил небольшую плату, способную питать наш приёмник от 220 вольт. Вынужден заметить, что процесс извлечения платы из корпуса оказался невероятно сложной задачей. Корпус сделан из качественной пластмассы. Тем не менее мне это удалось:

«Achtung!» или мониторим состояние сборки проекта

Собираем мигалку с нашим приёмником и блоком питания:

«Achtung!» или мониторим состояние сборки проекта

Убеждаемся, что всё работает: включаем прибор в сеть и отправляем через «Serial Monitor» в передатчик команду «Achtung ON». Реле щёлкнуло, лампочка загорелась, двигатель закрутился, ура! Отправляем команду «Achtung OFF» — всё остановилось. Мигалка работает. Осталось только прикрепить платы с помощью винтов и китайского термоклея и собрать всю конструкцию в первозданном виде.
С передатчиком я решил не церемониться и просто спаял всё вместе:

«Achtung!» или мониторим состояние сборки проекта

В будущем планирую упаковать всё это в красивый корпус.

Software

Для того, чтобы отправлять команды с компьютера в передатчик пишем простой скрипт:

#!/usr/bin/env python
"""
Send 'ON' or 'OFF' command to achtung transmitter.
"""

from __future__ import print_function
import os
import sys
import serial
 
 
if __name__ == '__main__':
    if len(sys.argv) !3:
        print("Usage: achtung /dev/ttyXX <ON|OFF>")
        sys.exit(1)
 
    tty = sys.argv[1]
    if not os.path.exists(tty):
        print("ERROR: device '%s' is not exists" % tty)
        sys.exit(1)
 
    command = sys.argv[2]
    if command not in ('ON''OFF'):
        print("ERROR: unknown command '%s'" % command)
        sys.exit(1)
 
    transmitter = serial.Serial(tty9600)
 
    if transmitter.readline().strip() !'Ready for achtung':
        print("ERROR: device is not initialized")
        sys.exit(1)
 
    transmitter.write("Achtung %sn" % command)
 
    result = transmitter.readline().strip()
    if result !'OK':
        print("ERROR: device response is not OK")
        sys.exit(1)

Чтобы скрипт работал, необходимо установить зависимость — модуль pyserial:

    pip install pyserial

Запускаем, наслаждаемся включающейся/выключающейся мигалкой:

    python achtung.py /dev/tty.usbserial-A900FYDU ON
    python achtung.py /dev/tty.usbserial-A900FYDU OFF

Для получения статуса сборок в Jenkins CI лучше всего воспользоваться модулем jenkinsapi, в этом случае скрипт получится элементарным. Установка зависимости:

    pip install jenkinsapi

Сам скрипт:

#!/usr/bin/env python
"""
Check for Jenkins jobs status and run alarm if job is not OK.
"""

import os
import sys
import time
 
import serial
from jenkinsapi.jenkins import Jenkins
from jenkinsapi.exceptions import JenkinsAPIException
 
 
URL = 'http://jenkins.example.ru/'
USERNAME = 'jenkins'
PASSWORD = 'secret'
JOBS = ['build''test''lint']
TTY = '/dev/tty.usbserial-A900FYDU'
 
 
def job_failed(connect, job_name):
    """
    Returns boolean job status (True - success, False - fail).
    """

    if job_name not in connect:
        return True
 
    last_build = connect[job_name].get_last_build()
 
    if last_build.is_running():
        return False
 
    build_status = last_build.get_status()
 
    if build_status in ('FAIL''FAILED''FAILURE''ERROR''REGRESSION'):
        return True
 
    return False
 
 
def send_to_transmitter(device, command):
    """
    Send command to device and die on error.
    """

    device.write("Achtung %sn" % command)
 
    result = device.readline().strip()
    if result !'OK':
        print("ERROR: device response is not OK")
        sys.exit(1)
 
 
if __name__ == '__main__':
    if not os.path.exists(TTY):
        print("ERROR: device '%s' is not exists" % TTY)
        sys.exit(1)
 
    try:
        connect = Jenkins(URL, USERNAME, PASSWORD)
 
        if any(job_failed(connect, job_name) for job_name in JOBS):
            transmitter = serial.Serial(TTY, 9600)
 
            if transmitter.readline().strip() !'Ready for achtung':
                print("ERROR: device is not initialized")
                sys.exit(1)
 
            send_to_transmitter(transmitter, 'ON')
            time.sleep(5)
            send_to_transmitter(transmitter, 'OFF')
 
    except JenkinsAPIException:
        sys.exit(1)

Запущенный скрипт подключается к Jenkins, получает статус указанных заданий и, если одно из них провалено, включает мигалку на пять секунд (это сделано для того, чтобы мигание не раздражало, но давало понять, что билд завершён неудачно). Скрипт запускается в кроне каждую минуту с 11:00 до 20:00 в будни (рабочее время наших сотрудников) — нет смысла включать мигалку в ночи или на выходных.
Конечно, можно было не поллить результаты сборок, а настроить Jenkins таким образом, чтобы при неудачной сборке он сам инициировал включение мигалки, но требования безопасности в нашей компании исключают такую возможность: любые коннекты из сети с серверами в офисную сеть категорически запрещены.

Profit!

Всё настроено, работает, и радует всех разработчиков (кроме того, который сфейлил билд) весёлым миганием в самый подходящий момент.
Исходники: https://github.com/dreadatour/jenkins-achtung.

Update: видео работы устройства:

Автор: Dreadatour

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js