Pull to refresh

Comments 17

Чтобы не припаивать провод прямо на кнопку, можно развести пятаки под диагностический разъем.

То, чего все больше всего боялись — свершилось. Continuous Integration добралась до FW.
Все таки ежедневные сборки прошивки — это пока еще дичайшая дичь. Чтобы полностью проверить прошивку, надо бы тестовый стенд по сложности в 2 раза сложнее, чем подопытное устройство.

Чтобы полностью проверить прошивку, надо бы тестовый стенд по сложности в 2 раза сложнее, чем подопытное устройство.


Ок. А разве CI на стенде запрещает обычную человеческую проверку?
И даже стенд из ардуины помогает найти некоторые косяки.
Ну чего всего в два раза то? У нас например в стенде на пяток устройств — кнопки жимкают 16-канальные i2c PWM контроллеры, питаловом управляют они-же и USB хабы поддерживающие управление питанием портов, 7-ми-сегментники и светодиоды трекаются видеокамерой и все это счастье обмазано JS-фреймворком на промисах, разруливающий свистопляску номеров TTYUSB_N по USB-топологии, выставляющим примитивы похожие на selenium — waitForUARTtext, waitForPixel, powerCycle, buttonPress… Cбоку CI конвеер собирает из С кода + React-вебморды, после вебпака гзипуемого и транслируемого в C-литералы. Все это крееепко сложнее ESP8266+PIC прибора который тестируется.

P.S. есть идеи выложить фремворк в паблик, но как-то не заморачивался думая что мало кто в эмбеде по CI упарывается
Вообще тоже интресно посмотреть технологию и фреймворк. Сами нечто похожее делаем, только на базе Миландра
ок, начну чуть издалека, сорри за фуражку Капитана Очевидности во многих моментах, имхо на них стоит сделать акцент.

«Железячные тестовые стенды» и тестируемые приборы, в отличии от «тестирования кодом кода», часто не совсем детерминированные — взаимные наводки, плохие контакты, помехи, коты и всякое такое, поэтому:

— принцип KISS на первом плане — чем меньше мы навесим тестовой обвязки — тем лучше, стараемся максимально снять инфы по визуальному каналу (вебкамера смотрит на экран тестируемых приборов и их светодиоды), там где можно тестировать демоборды или что-то с питанием от USB — берем USB хаб управляющий питанием по каждому порту (например Dlink H7 в сером корпусе) — $20 за 7 каналов управления питанием и проброса UART-или-что-там-на-борде в систему

— никаких плат разложенных по столам/полкам стендов — во первых коты, во вторых маппинг экранов/светодиодов в пиксели видеокамеры будет слетать. Например, собираем тестовые «лотки» из корпусов от старых CD приводов, и вставляем их в старый ATX корпус, где напротив них стационарно висит вебкамера (см фото). На веб-камеру стоит нацепить полярик, и не особо совать все это под солнечный свет (цвета пикселей будут уходить)

— только качественные и короткие USB провода

Фото
image
image


О фреймворке (точнее сборки из NodeJS+Redis c легаси и велосипедами) — он состоит из серверной части и самих тестов. Серверная часть выставляет веб-морду, из которой можно запустить любой тест и смотреть его прогресс и видеокамеру. (планировалась еще интерактивность, пробросить кнопки прибора в кнопки на морде, но руки пока не дошли). Так-же серверная часть обрабатывает вебкамеру, захватывая кадры через OpenCV, выделяя интересующую область и публикуя ее в Redis-канал для приема на стороне теста, и в вебсокеты UI клиентам.

На стороне теста «фреймворк» выставляет Promise обертки над всякими командами «поуправлять питанием портов USB хаба» и над i2c периферией, которая «нажимает кнопки» 16-канальным PWM драйвером, им-же эмулирует аналоговые сигналы (например, один из наших приборов {реле защиты} измеряет напряжение в сети и от него все действия, PWM хоть и точностью +-3 вольта, но эмулит пере/недо-напряжения)

Пример теста (неканонично, это все делалось для внутр. пользования)
const {
    wait ,
    waitForUart ,
    waitForPixel ,
    timeLabel ,
    infoLabel ,
    buttonPress ,
    buttonUnpress,
    sendPWM ,
    powerCycle
} = require('./test-framework');

const Stage = require('./stage-config');

const PowerLedX = 34;
const PowerLedY = 236;

const PowerReadyLedX = 53;
const PowerReasyLedY = 274;

Promise.resolve(true)
    //
    // turn on PWM driver
    .then(timeLabel("init-section","START"))
    .then(infoLabel("включаем PWM,устанавливаем напряжение входа VP16"))
    .then(powerCycle(true,Stage.PWM))
    .then(wait(500))
    // set input voltage at VP16
    .then(sendPWM(0,220/Stage.VP16.UCONST,Stage.VP16.UINP))
    .then(wait(500))
    .then(timeLabel("init-section","PASS"))
    .catch(timeLabel("init-section","FAIL"))
    .then(infoLabel("включаем VP16"))//
    .then(wait(500))
    .then(timeLabel("pwr-ready-test","START"))
    .then(powerCycle(true,Stage.VP16))
    //.then(powerCycle(true,Stage.TR16))
    .then(infoLabel("ждем готовности по напряжению"))
    .then(waitForPixel(PowerReadyLedX,PowerReasyLedY,(r,g,b,a)=>(r>200 || ((r > (g+b)*2)&&(r>30)) )))
    .then(timeLabel("pwr-ready-test","PASS"))
    .catch(timeLabel("pwr-ready-test","FAIL"))
    .then(wait(2000))
    .then(timeLabel("pwr-button-on-test","START"))
    .then(infoLabel("нажимаем среднюю кнопку"))
    .then(buttonPress(Stage.VP16.BUTTON2))
    .then(waitForPixel(PowerLedX,PowerLedY,(r,g,b,a)=>r>230))
    .then(timeLabel("pwr-button-on-test","PASS"))
    .catch(timeLabel("pwr-button-on-test","FAIL"))
    .then(infoLabel("отпускаем среднюю кнопку"))
    .then(buttonUnpress(Stage.VP16.BUTTON2))
    .then(wait(2000))
    .then(timeLabel("pwr-overvoltage-test","START"))
    .then(infoLabel("имитируем овервольтаж"))
    .then(sendPWM(0,310/Stage.VP16.UCONST,Stage.VP16.UINP))
    .then(waitForPixel(PowerLedX,PowerLedY,(r,g,b,a)=>r<230))
    .then(timeLabel("pwr-overvoltage-test","PASS"))
    .catch(timeLabel("pwr-overvoltage-test","FAIL"))
    .then(wait(2000))
    .then(infoLabel("ждем восстановления"))
    .then(wait(500))
    .then(timeLabel("pwr-overvoltage-return-test","START"))
    .then(sendPWM(0,220/Stage.VP16.UCONST,Stage.VP16.UINP))
    .then(waitForPixel(PowerLedX,PowerLedY,(r,g,b,a)=>r>230))
    .then(timeLabel("pwr-overvoltage-return-test","PASS"))
    .catch(timeLabel("pwr-overvoltage-return-test","FAIL"))
    .then(wait(2000))
    .then(infoLabel("выключаем приборы"))
    .then(powerCycle(false,Stage.VP16))
    .then(wait(500))
    .then(powerCycle(false,Stage.TR16))
    .then(wait(500))
    .then(powerCycle(false,Stage.PWM))
    .then(wait(1000))
    //.then(powerCycle(false,Stage.TR16))
    .then(timeLabel("final-section","PASS"))
    .catch(timeLabel("final-section","FAIL"))
    .then(()=>process.exit());



И пример конфига для него:
const Stage = {
    "PWM" :  {
        id: "PWM",
        usb: "8.7",
        power_hub_port: 7,
        serial: undefined
    },

    "VP16": {
        id: "VP16",
        usb: "8.1",
        power_hub_port: 1,
        serial: undefined,
        // voltage calibration
        UCONST : 0.2980,

        // pwm shield port map
        UINP    : 0, // U input emulation
        BUTTON1 : 2, // buttons...
        BUTTON2 : 1, //
        BUTTON3 : 3, //
    },
    "TR16": {
        id: "TR16",
        usb: "8.6",
        power_hub_port: 6,
        serial: undefined,
    },

    "FLASHER" :  {
        id: "FLASHER",
        usb: "8.2",
        power_hub_port: 2,
        serial: undefined
    },

};

module.exports = Stage;



Тест можно запускать из-под UI вручную, можно из cmd-line для интеграции пайплайнов да и просто с IDE по SSH (у меня CLion скриптом собирает бинарь, scp его на стенд, ssh туда, выгрузка на плату и запуск теста при необходимости — получается эмбед писать с ноута удаленно от стенда и проводов). При запуске из командной строки вывод и вэбка все-равно транслируются в UI для стороннего контроля.

Крутится все это на i5 под убунтой, жрет CPU 10..15% (в основном вэбка и ее данные по шине Redis, там не особо оптимизировать успел)

В целом получается что-то типа сильно упрощенного Redd от EasyLy ( habr.com/ru/post/440156 ) только «мясом вовнутрь» и за шапку сухарей.
Спасибо, за детальный ответ. его можно и расписать в виде отдельной статьи.
Увы, статьей аккаунт не позволяет. По свободе заделаю git-репозиторий с кодом и примером «как помигать светодиодом» под автотестами.
Ну уж скажите, дичь. Давайте я расскажу вам, как это все живет у нас.

Для начала масштаб проблемы. У нас пяток классов устройств, у каждого — своя система. Одновременно для каждой системы ведется разработка над текущим релизом, следующим мажорным релизом и опционально следующим мажорным релизом + 1. Помимо этого есть минорные релизы, над котороыми работа может начаться одновременно с мажорным. Плюс специальные ветки для бета-версий, фиксов безопасности и отдельные ветки для фабрик, чтобы не показывать им весь софт до релиза. Это все дает примерно с десяток активных каналов разработки для каждого класса устройств. Примерно каждый год выходит несколько новых моделей почти во всех классах, поддержка старых осуществляется в течение нескольких лет. Это дает до нескольких десятков поддерживаемых моделей устройств на класс. У каждой системы есть несколько вариантов, для пользователей, для фабричного тестирования, для разработчиков, etc. Итого мы ежедневно получаем примерно 30-50 сборок систем в день и примерно 100 моделей устройств, на которых нужно проверить, что интересующие нас варианты систем хотя бы базово работают. Сейчас у нас больше 250 устройств, которые висят подключенными на почти трех десятках серверов и работают в CI. Осложняется дело необходимым на все это временем. Сборка всех изменений за сутки занимает примерно 6-8 часов, сборка всего — 13 часов. Еще 2-4 часа на то, чтобы смастерить образы системы для всех вариантов и для всех устройств. Отдельная история про построение OTA патчей и загрузку всего этого дела в архив, это идет в фоне и может занимать до суток. erase-install на одно устройство занимает примерно полчаса. Минимальный набор тестов еще минут 15.

Всем вышеописанным занимается наш отдел. Все что можно автоматизировать — автоматизируется, в том числе и проверка на железе. Без этого с ума бы сходили. Старожили еще помнят, как делали все это вручную, но тогда было меньше классов устройств, выпущенных моделей и параллельных каналов разработки.

Сложно сказать, что сложнее, стенд или устройства. В качестве стенда выступает сервер, к которому подключается десяток устройств. На интерфейсном кабеле устройств висят специальные умные девайсы, которые через тот же кабель снимают с устройства serial, умеют нажимать на кнопки, сбрасывать устройство, насильно менять его текущий режим работы и отлаживать в случае паники. Управляет этим всем жуткая мешанина проприетарных технологий специально разработанных для работы с нашими устройствами и тестирования софта на них. Наша скромная высокоуровневая автоматизация и тесты пишутся на питоне и боле-менее справляется с задачей.

Ну и да, мы единственная команда, которая проверяет все поддерживаемые устройства. Правда и набор тестов у нас скудный. Чтобы его пройти образ должен успешно установиться на устройство, не жрать 100% cpu, не крешиться в цикле, а процесс UI должен запуститься и не падать. Мы буквально отвечаем на вопрос «а пригоден ли билд к дальнейшему тестированию?», зато для всего.

У других команд есть всевозможные автотесты на функциональность, они работают на том же технологическом стеке, но на другом пулле устройств и они проверяют, как правило, одну модель для каждого SoC. Полностью же покрыть весь софт автотестами не реально — слишком быстро происходят изменения. В ручную тестируется просто большая часть системы.
Мы буквально отвечаем на вопрос «а пригоден ли билд к дальнейшему тестированию?», зато для всего.

В том-то и дело, что при этом по аналогии с софтом может возникнуть ложная уверенность, что раз CI "дала добро", можно смело начинать выкатывать OTA на пользовательские устройства, хотя это на самом деле только самый базовый уровень тестирования и в реале экономит не так уж много времени, если думать об "успешно установиться на устройство, не жрать 100% cpu, не крешиться в цикле, а процесс UI должен запуститься и не падать" еще на этапе архитектуры, а не кодирования.

ложная уверенность — плохо, но это к теории тестирования)) для OTA имхо как минимум один тест — «способен ли новый билд посредством OTA откатиться на старый» должен быть автоматизирован в пайплайне
— «способен ли новый билд посредством OTA откатиться на старый» должен быть автоматизирован в пайплайне

И вот у меня почему-то на 99% уверенности, что комментатор выше это не тестирует на своей CI ферме. :-)

У нас в принципе нет механизмов даунгрейда версии. Нужен даунгрейд — бэкап данных/erase-install/восстановление бэкапа. Ну и все прекрасно понимают, что то что мы тестируем — позволяет поймать то, что билд совсем сломан, а не проверить то, что он хороший. Учитывая, что главные каналы обновления получают свежие сборки один раз в день, это просто ответ на вопрос, могут ли разработчики продолжать работать на сегодняшнем билде или лучше остаться на предыдущем.
Тут дело не в даунгрейде версии, а в работоспособности OTA в новой версии — не сможем откатиться на старую (пусть и под новым номером) — значит багфикс и новую версию тоже не втянем, канал ОТА лег, уже проданные приборы — кирпичи.
Вот это, кстати, интересный кейс. Было бы интересно его тестировать. В текущем варианте мы этого не можем сделать, так как чтобы протестировать OTA, надо собрать еще одну новую версию и сделать патчи с текущей версии на новую. Я полагаю, этот кейс обязательно входит в финальное ручное тестирование апдейтов, которые уходят пользователям.
Тема автоматизации тестирования весьма интересная, но ожида прочитать что-то большее, чем примитивное заыкание 2х реде для имитации кнопки и сброса питания. Раз уж такая тема, никому не приходилось реализовывать USB хост для FTDI на стороне МК? Попадался драйвер для этого дела под USB стек для ST. Но больше интересует, есть ли в наличии внешние микрухи, чтобы просто подключить к МК по уарту и не париться с реализацией всего этого?

Мне кажется все проще. Подключите любой МК аля ардуины с USB и UART на борту к вашему МК. В подключаемом — простая программа проброса данных с порта на порт

Sign up to leave a comment.

Articles