Автоматическое Обновление Версии Прошивки

#системы сборки #Си #makefile #версия #обновление версии #sed #Техникум:

4d1f457083d5eda4f69006252e5928a8.png

В чем проблема?

Как устроена жизнь сейчас? В программировании микроконтроллеров новые сборки прошивок появляются и исчезают как вспышки на Солнце. Какие-то сборки выкатываются в релиз, а какие-то в циркулируют на разных электронных платах внутри промзоны. Это вызывает путаницу.

Как понять, что прошивки на двух одинаковых устройствах разные как снежинки?
Как понять, какая прошивка новее, а какая старее?

Проблема в том, что вручную обновлять номер версии прошивки очень утомительно и рутийно. Поэтому ручным обновлением версии прошивки все всегда пренебрегают. Да, это факт…

В связи с этим замаячила цель развернуть полноценный механизм для автоматического обновления версии прошивки. Это позволило бы навести хоть какое-то подобие порядка.

Постановка задачи. Требования.

Мысль в следующем… Надо написать утилиту, которая возьмет из корня проекта файл version_auto.h, найдёт там макро-определение SUCCESSFUL_BUILD_COUNTER, распознает из строки натуральное число, увеличит это число на единицу и автоматически сохранит результат обратно в файл version_auto.h

af3388c6783b8856576d8a6d474e0adb.png

Надо чтобы утилита, не много не мало, увеличила константу в исходниках прошивки. Назовём эту утилиту auto_version_build.exe

Одно из возможных решений

Так как основная кодовая база у нас на Си, то и писать эту PC утилиту логично тоже прямо на Си.

Фаза 1

Очевидно, что сначала надо открыть файл version_auto.h и просканировать его строчка за строчкой

bool auto_version_proc_headr( const char* const file_name){
    bool res = false;
    if(file_name) {
        LOG_INFO(AUTO_VERSION, "ProcFile:[%s]",file_name);
        FILE *FilePtr = NULL;
        FilePtr = fopen(file_name, "r");
        if(FilePtr) {
            LOG_INFO(AUTO_VERSION, "OpenOk File:[%s]",file_name);
            //res = true;
            char line[500]={0};
            uint8_t line_num = 0;
            while (NULL != fgets(line, sizeof(line), FilePtr)) {
              LOG_DEBUG(AUTO_VERSION,"%s", line);
              res = auto_version_proc_line(&AutoVersionInstance, line);
              memset(line,0,sizeof(line));
              line_num++;
            }

            fclose(FilePtr);

            char prev_str[80]={0};
            snprintf(prev_str,sizeof(prev_str),"%s%u",
                     PREFIX,AutoVersionInstance.number_of_builds);

            char new_str[80]={0};
            snprintf(new_str,sizeof(new_str),"%s%u",
                     PREFIX,AutoVersionInstance.number_of_builds_new);

            res = file_pc_replace_substr(file_name,prev_str ,new_str);
        }else{
            LOG_ERROR(AUTO_VERSION, "OpenErr File:[%s]",file_name);
            res = false;
        }
    }
    return res;
}

Затем найти ту строчку, где присутствует префикс »#define SUCCESSFUL_BUILD_COUNTER » и выделить из этой строки натуральное число

bool auto_version_proc_line(AutoVersionHandle_t* Node, char* line) {
    bool res = false;
    if(Node){
        if(line){
            size_t len = strlen(line);
            int ret = strncmp(PREFIX, line,strlen(PREFIX));
            if(0==ret){
                LOG_WARNING(AUTO_VERSION, "SpotVerLine,Len:%u,Line:[%s]",
                            len,line);
                char out_text[80]={0};
                uint16_t ret_len =0;
                res =  parse_text_after_prefix(line,len, 
                                               out_text, &ret_len, PREFIX,' ');
                if(res) {
                    LOG_WARNING(AUTO_VERSION,
                                "SpotValueLine,Len:%u,Line:[%s]",
                                ret_len,out_text);
                    res = try_str2number(out_text,
                                         &Node->number_of_builds);
                    if(res){
                        LOG_DEBUG(AUTO_VERSION, 
                                  "ParseOk,Num:%u",
                                  Node->number_of_builds);
                        Node->number_of_builds_new = Node->number_of_builds+1;
                    }else{
                        LOG_ERROR(AUTO_VERSION, "ParseNumErr[%s]",out_text);
                    }
                }
            }else{
                LOG_DEBUG(AUTO_VERSION, "NoPrefix[%s]",PREFIX);
            }
            res = true;
        }
    }
    return res;
}

Этот кусок кода подразумевает, что у вас уже есть такие функции, как выделение подстроки по префиксу (parse_text_after_prefix) и распознавание числа из строки (try_str2number). Про это у меня есть отдельный текст https://habr.com/ru/articles/757122/

Фаза 2

На этом этапе мы полностью знаем, что надо заменить и на что. И тут на сцену выходит культовая консольная утилита sed. Она как раз отлично решает задачу авто замены подстроки в файле. Утилиту sed можно вызваться прямо из консольной утилиты на Си функцией system ().

bool win_cmd_run(const char* const command){
    bool res = false;
    LOG_INFO(PC,"ExeCmd:[%s]", command);
    int ret = system(command);
    LOG_DEBUG(PC,"Ret %d", ret);
    if(0==ret){
        res = true;
    }
    return res;
}

bool file_pc_replace_substr(const char* const file_name,
                            const char * const prev_str ,
                            const char* const new_str){
    bool res = false;
    if(file_name){
        if(prev_str){
            if(new_str){
                LOG_INFO(FILE_PC,
                         "PeplaceSubStr File:[%s],Prev:[%s]->New[%s]",
                         file_name,prev_str,new_str);
                char command_line[200]={0};
                snprintf(command_line,sizeof(command_line),
                         "sed -i -e 's/%s/%s/g' %s",
                         prev_str,new_str,file_name);
                LOG_WARNING(FILE_PC,"PefrormSed:[%s]",command_line);
                res = win_cmd_run(command_line);
            }
        }
    }
    return res;
}

Вот список всех функций, которые потребовались для решения данной задачи

Название функции

Назначение функции

1

system

Запуск процесса

2

snprintf

компоновка строки

3

try_str2number

парсинг числа из строки

4

parse_text_after_prefix

парсинг подстроки из строки

5

strncmp

сравнение строк

6

strlen

получить длину строки

7

fopen

открыть файл

8

fgets

извлечь строку из файла

9

memset

затереть RAM память

10

fclose

закрыть файл

Фаза 3

Теперь следует встроить утилиту auto_version_build.exe в общий сценарий сборки проекта.

Это make файл скрипт auto_version_target.mk для интеграции процесса обновления версии в общий процесс построения артефактов.

$(info auto_version_script)
$(info MK_PATH=$(MK_PATH))
MK_PATH_WIN := $(subst /cygdrive/c/,C:/, $(MK_PATH))
$(info MK_PATH_WIN=$(MK_PATH_WIN))

$(info WORKSPACE_LOC=$(WORKSPACE_LOC))
WORKSPACE_LOC := $(subst /cygdrive/c/,C:/, $(WORKSPACE_LOC))
$(info WORKSPACE_LOC=$(WORKSPACE_LOC))
#$(error WORKSPACE_LOC=$(WORKSPACE_LOC))

AUTO_VERSION_TOOL=$(WORKSPACE_LOC)../tool/auto_version_build.exe
$(info AUTO_VERSION_TOOL=$(AUTO_VERSION_TOOL))

auto_version_target: $(ARTIFACTS)
	$(info RunAutoVersionTarget)
	$(AUTO_VERSION_TOOL) avp $(MK_PATH_WIN)version_auto.h

Интерес представляют именно успешные сборки, ибо только после них появляется новый функционал, поэтому увеличение версии будет происходить только в случае, когда кристаллизовались файлы с артефактами (*.bin, *.hex, *.elf)

Осталось только добавить цель auto_version_target к общему списку целей построения проекта all

.PHONY:all

all:   $(OBJ) $(ARTIFACTS) auto_version_target
	@echo RunTargetAll...

Отладка

Как же теперь прочитать версию прошивки? Очень просто. Надо взять электронную плату (PCB) и NetTop PC, соединить их через переходник USB-UART, открыть на PC программу TeraTerm, подать электропитание на PCB и проанализировать лог загрузки прошивки.

Во всех взрослых прошивках должен быть лог загрузки в UART, подобно тому как это происходит на любых маршрутизаторах. Такой лог загрузки часто называют «портянкой».

В портянке будет строчка со словом OkBuildCnt. Там и будет как номер этой сборки, так и вся остальная исчерпывающая информация по этой конкретной прошивке.

вот так выглядит считывание версии

вот так выглядит считывание версии «в натуре»

Теперь лог загрузки как лакмусовая бумажка покажет, какая прошивка новее, а какая старее.

Итоги

Как видите, сборка из make файлов даёт такие преимущества как встраивание в процесс сборки свои собственные скрипты. В частности такие полезные в реальной жизни вещи как авто обновление версии программного обеспечения. Достаточно вызвать make all и как по волшебству увеличится версия прошивки.

Сборка из make скриптов хороша тем, что Вы можете не только собирать бинарь прошивки как в какой-нибудь IDE. С make Вы можете полноценно и гибко управлять ходом сборки и добавить ещё, например, сортировку конфигов, подпись прошивку, прогнать разные статические анализаторы, выполнить авто выравнивание отступов в исходных кодах, сгенерировать дерево зависимостей на Graphviz, собирать документацию (вызвать Latex, Doxygen), можете прямо из make отправлять файлы прошивок потребителям по email, вызывать процедуру пере прошивки утилитами программатора. И всё это произойдет само собой.

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

Links

--Почему Важно Собирать Код из Скриптов https://habr.com/ru/articles/723054/

--Сортировка Конфигов для Make Сборок https://habr.com/ru/articles/745244/

--Генерация зависимостей внутри программы https://habr.com/ru/articles/765424/

--Техникум: Распознавание Вещественного Числа из Строчки https://habr.com/ru/articles/757122/

--Полезные Заготовки Вызова Утилит Командной Строки https://habr.com/ru/articles/754858/

--Сколько Надо Строк Кода Для Того Чтобы Подписать Артефакты? https://habr.com/ru/articles/731484/

© Habrahabr.ru