Русский
Русский
English
Статистика
Реклама

Шаблоны

Перевод Примеры грамотного применения SSH-шаблонов

17.02.2021 20:19:33 | Автор: admin


SSH-сертификаты очень мощный инструмент. Первоначально в удостоверяющем центре step-ca мы реализовали только минимальный набор функций для аутентификации по сертификатам пользователя и хоста. Затем добавили шаблоны сертификатов X.509, а ещё в августе прошлого года и SSH-шаблоны, в версии 0.15.2. Наконец, мы задокументировали эту функцию и готовы о ней рассказать.

Шаблоны для сертификатов SSH действуют аналогично шаблонам X.509: это JSON-файлы, написанные в Go text/template. Они применяются для настройки SSH-сертификатов, которые выдаёт step-ca. Давайте посмотрим, что представляют собой эти шаблоны и как их можно использовать.

По умолчанию шаблон пользовательского SSH-сертификата выглядит так:

{"type": {{ toJson .Type }},"keyId": {{ toJson .KeyID }},"principals": {{ toJson .Principals }},"extensions": {{ toJson .Extensions }},"criticalOptions": {{ toJson .CriticalOptions }}}

А вот SSH-сертификат, выданный по этому шаблону:

$ step ssh inspect id_ct-cert.pubid_ct-cert.pub:        Type: ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate        Public key: ECDSA-CERT SHA256:iczSh1XiBBE36yfJcDidgp6fqY3qWx1RtEwFfAN9jDs        Signing CA: ECDSA SHA256:MKwRQ/SDKk/pCJbbCk5bfhZACjSjv7uZXLyc5n4Wx6k        Key ID: "carl@smallstep.com"        Serial: 2831574724231262409        Valid: from 2020-11-17T16:48:11 to 2020-11-18T08:49:11        Principals:                carl                carl@smallstep.com        Critical Options: (none)        Extensions:                permit-X11-forwarding                permit-agent-forwarding                permit-port-forwarding                permit-pty                permit-user-rc

Он позволяет юзеру carl (или carl@smallstep.com) пройти аутентификацию на любом SSH-хосте, который доверяет моему SSH CA. Сертификат включает в себя некоторые основные расширения:

  • permit-x11-forwarding: Разрешает переадресацию X11 (с помощью ssh -X) для запуска удалённых программ X11 на локальном дисплее.
  • permit-agent-forwarding: Разрешает переадресацию агента (с помощью ssh -A) для пересылки ключей из локального агента SSH на удалённый хост (подробнее про агента SSH см. здесь).
  • permit-port-forwarding: Разрешает переадресацию портов (туннель) с локального на удалённый порт (ssh -L) или с удалённого на локальный (ssh -R).
  • permit-pty: Очень важное расширение. Если хотите открыть интерактивную сессию в консоли, хост должен выделить вам pty (псевдо-tty). В противном случае не предусмотрено никакой интерактивности. Например, для проверки SSH-аутентификации на GitHub можно запустить ssh -T git@github.com (-T отключает запрос на pty).
  • permit-user-rc: Запуск личного RC-файла после подключения (находится в ~/.ssh/rc на удалённом хосте).

Сертификаты пользователя и хоста поддерживают расширения и критические параметры, но OpenSSH не определяет никаких встроенных расширений или критических параметров для сертификатов хоста. Таким образом, всё самое интересное происходит именно с сертификатами пользователя, поэтому в данной статье рассмотрим только их.

Примеры шаблонов сертификатов


Внесём несколько изменений в дефолтный шаблон.

Запрещаем переадресацию агента и портов

Если пользователи подключаются к внутренним хостам через хост-бастион, то хорошо бы запретить перенаправление портов по соображениям безопасности. Вы же не хотите, чтобы юзеры перенаправили трафик с продакшн-сервера MySQL на свой localhost. Так же и переадресация агента сопряжена с риском для безопасности. Вот шаблон, который просто удаляет эти два расширения из сертификатов SSH:

{"type": {{ toJson .Type }},"keyId": {{ toJson .KeyID }},"principals": {{ toJson .Principals }},"extensions": {           "permit-x11-forwarding": "",           "permit-pty": "",           "permit-user-rc": ""  },"criticalOptions": {{ toJson .CriticalOptions }}}

Встраиваем директиву force-command

ForceCommand это серверная директива конфигурации SSHD, которая вместо интерактивного терминала запускает на хосте альтернативную команду. Но с тем же эффектом можно встроить force-command прямо в сертификат в раздел Critical Options:. Может пригодиться для служебных аккаунтов, которым необходимо выполнить только одну команду, например, запустить задание в удалённой системе.

Ограничиваем соединения по адресам

Чтобы ограничить область использования сертификата, в него можно встроить список разрешённых IP-адресов (блоков CIDR).

Вот шаблон сертификата, который использует и source-address, и force-command.

{"type": {{ toJson .Type }},"keyId": {{ toJson .KeyID }},"principals": {{ toJson .Principals }},"extensions": {{ toJson .Extensions }},"criticalOptions": {"force-command": "echo \"Hello World\"","source-address": "10.20.30.0/24,1.1.1.1/32"}}

Это нормальный пример, но здесь список IP жёстко зафиксирован и не изменяется. А нам обычно хочется использовать разные списки адресов для разных пользователей. Давайте попробуем

Вставляем разные значения для разных юзеров

Очевидно, пользователям нельзя давать право редактировать диапазон адресов. Следовательно, динамические значения должны поступать из надёжного источника.

Для этого в step-ca можно через поставщика OpenID Connect (OIDC) настроить провайдера OAuth на добавление к токену нестандартных требований (custom claim), содержащих блоки адресов CIRD, которые мы хотим добавить в сертификат этого пользователя.

Поставщик OIDC представляет собой идеальный способ выдачи SSH-сертификатов в step-ca. В статье DIY Single Sign-On for SSH я рассказывал, как настроить SSH CA, чтобы он выдавал краткосрочные SSH-сертификаты по ID-токенам от доверенного провайдера OAuth. Если step-ca настроен как доверенный клиент OAuth, он будет считывать поле email из токена ID и извлекать оттуда список участников SSH-сертификата (например, по полю carl@smallstep.com сгенерируются сертификаты для carl и carl@smallstep.com).

Но OIDC позволяет через шаблоны считать из токенов ID и другие поля. Вот где начинается настоящая магия. Таким образом, добавляем в каталог пользователей на стороне провайдера идентификации отдельное поле source_address и отражаем его в нашем ID-токене. Затем через шаблон SSH можно ввести значение из токена в сертификат. Вот шаблон:

{"type": {{ toJson .Type }},"keyId": {{ toJson .KeyID }},"principals": {{ toJson .Principals }},"extensions": {{ toJson .Extensions }},{{ if .Token.source_address }}"criticalOptions": {"source-address": "{{ .Token.source_address }}"}{{ else }}"criticalOptions": {{ toJson .CriticalOptions }}{{ end }}}

Аутентификация на GitHub по сертификату

Рассмотрим ещё один пример custom claim. С помощью GitHub Enterprise Cloud или GitHub Enterprise Server можно настроить GitHub на использование SSH-сертификатов. В частности, GitHub будет доверять вашему центру сертификации SSH. Но чтобы всё заработало, нужно создать для каждого пользователя отдельный SSH-сертификат с расширением login@github.com, в котором прописывается имя пользователя на GitHub. С помощью этого расширения сертификат аутентифицирует пользователя на GitHub Enterprise. И это здорово: один и тот же сертификат позволяет и подключиться к вашему серверу по SSH, и запушить код на GitHub.

Вот шаблон сертификата с поддержкой индивидуального расширения GitHub:

{"type": {{ toJson .Type }},"keyId": {{ toJson .KeyID }},"principals": {{ toJson .Principals }},"criticalOptions": {{ toJson .CriticalOptions }},{{ if .Token.ghu }}"extensions": {  "login@github.com": {{ toJson .Token.ghu }}}{{ else }}"extensions": {{ toJson .Extensions }}{{ end }}}

Для использования шаблона нужно добавить к токенам идентификации OIDC индивидуальное требование ghu (GitHub Username). Давайте подробно рассмотрим, как создать этот custom claim с помощью вашего провайдера OAuth.

Регистрация заявления у провайдера идентификации

Не все провайдеры идентификации поддерживают индивидуальные требования, но если поддержка всё-таки есть, то процесс довольно похож. Вот как это делается с помощью Okta:

  1. Добавьте приложение OAuth в Okta и установите доверие к нему у поставщика OIDC в step-ca, как описано в статье DIY SSO for SSH.


  2. Добавьте новое поле в каталог пользователей Okta (например, GitHub Username)
  3. Добавьте к OIDC-токену индивидуальное требование, например, с сокращённым названием ghu
  4. Теперь заполняем поле для тестового юзера и проверяем требование. В Okta есть инструмент тестирования токена ID. Или можно использовать step для проверки всего потока OAuth:

    OIDC_ENDPOINT="http://personeltest.ru/aways/[your organization].okta.com/oauth2/default/.well-known/openid-configuration"CLIENT_ID="[your OAuth client ID]"CLIENT_SECRET="[your OAuth client secret]"step oauth --oidc --provider $OIDC_ENDPOINT \    --client-id $CLIENT_ID --client-secret $CLIENT_SECRET \    --listen=":10000" --bare |step crypto jwt inspect --insecure
    

  5. Наконец, настройте step-ca для использования этого шаблона. Конфигурация поставщика должна ссылаться на файл шаблона:

    {  "provisioners": [    {      "type": "OIDC",      "name": "Okta",      "clientID": "[your OAuth client ID]",      "clientSecret": "[your OAuth client secret]",      "configurationEndpoint": "https://[your organization].okta.com/oauth2/default/.well-known/openid-configuration",      "listenAddress": ":10000",      "options": {        "ssh": {            "templateFile": "templates/certs/ssh/github.tpl"        }      }    },      ...  ]}
    

Что дальше


Мы добавили в документацию раздел о шаблонах SSH, где более подробно рассматриваются все параметры и переменные.

Если есть вопросы не стесняйтесь задавать.
Подробнее..

Перевод Идеальная версия недельной сетки календаря для печати

02.03.2021 12:21:43 | Автор: admin
Очень многие люди до сих пор пользуются печатной версии ежедневников, и дело тут не в том, что не хватает онлайн версии, а в том, что это когнитивная потребность.

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



Вы можете почитать исследования профессора Audrey van der Meer из Норвежского Университета Науки и Технологии (NTNU)

Огромный выбор шаблонов календарей и планировщиков



В мире существует тысячи разных бесплатных шаблонов и сеток на любой вкус. Есть строгие и классические, есть разноцветные с картинками, есть с фиксированным временем, а есть без.

Существуют даже целые фан клубы людей можно сказать помешанных на планировании (Planner Addicts), где каждый пытается сделать лучший для себя планировщик.

Минималистичная недельная сетка без привязки к точному времени



Изучив большое количество разных шаблонов, мы постарались создать максимально простой и универсальный шаблон недельного вида. Вернее будет сказать самый классический.

Мы использовали четкую, контрастную черно-белую недельную сетку где нету привязки к дневному времени. Шаблон разделен на 7 дней с секции Someday, которая может служить для дел когда-нибудь или просто для заметок.

Шаблон для печати абсолютно бесплатный. Вы можете скачать PDF файл либо заполнить календарь online и потом распечатать.
Подробнее..

Шаблоны GRASP Polymorphism, Pure Fabrication, Indirection, Protected Variations

01.10.2020 18:11:51 | Автор: admin
Привет, Хабр! Меня зовут Владислав Родин. В настоящее время я являюсь руководителем курса Архитектор высоких нагрузок в OTUS, а также преподаю на курсах, посвященных архитектуре ПО.

Специально к старту нового набора на курс Архитектура и шаблоны проектирования я продолжаю серию своих публикаций про шаблоны GRASP.



Введение


Описанные в книге Craig'а Larman'а Applying UML and patterns, 3rd edition, GRASP'овские паттерны являются обобщением GoF'овских паттернов, а также непосредственным следствием принципов ООП. Они дополняют недостающую ступеньку в логической лестнице, которая позволяет получить GoF'овские паттерны из принципов ООП. Шаблоны GRASP являются скорее не паттернами проектирования (как GoF'овские), а фундаментальными принципами распределения ответственности между классами. Они, как показывает практика, не обладают особой популярностью, однако анализ спроектированных классов с использованием полного набора GRASP'овских паттернов является необходимым условием написания хорошего кода.

Полный список шаблонов GRASP состоит из 9 элементов:

  • Information Expert
  • Creator
  • Controller
  • Low Coupling
  • High Cohesion
  • Polymorphism

В прошлый раз мы обсудили паттерн Controller. Сегодня предлагаю рассмотреть оставшиеся паттерны из списка.

Polymorphism


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

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

Наличие в коде конструкции switch является нарушением данного принципа, switch'и подлежат рефакторингу.

Злоупотребление полиморфизмом приводит к переусложнению кода и в общем случае не приветствуется.

Pure Fabrication


Необходимо обеспечивать low coupling и high cohesion. Для этой цели может понадобиться синтезировать искуственную сущность. Паттерн Pure Fabrication говорит о том, что не стоит стесняться это сделать. В качестве примера можно рассматривать фасад к базе данных. Это чисто искуственный объект, не имеющий аналогов в предметной области. В общем случае любой фасад относится к Pure Fabrication (если это конечно не архитектурный фасад в соответствующим приложении).

Indirection


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

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

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

Protected Variations


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

На самом деле, это не паттерн, а цель, достигаемая соблюдением остальных паттернов.

Вывод


Шаблоны GRASP состоят из 8 паттернов:
1) Information Expert информацию обрабатываем там, где она содержится.
2) Creator создаем объекты там, где они нужны.
3) Controller выносим логику многопоточности в отдельный класс или компонент.
4) Low Coupling 5) High Cohesion проектируем классы с однородной бизнес-логикой и минимальным количеством связей между собой.
6) Polymorphism различные варианты поведения системы при необходимости оформляем в виде полиморфных вызовов.
7) Pure Fabrication не стесняемся создавать классы, не имеющие аналог в предметной области, если это необходимо для соблюдения Low Coupling и High Cohesion.
8) Indirection любой класс вызываем через его интерфейс.
9) Protected Variations применяя все вышесказанное, получаем устойчивый к изменениям код.



Читать ещё:


Подробнее..

Из песочницы Включаем периферию контроллера за 1 такт или магия 500 строк кода

16.11.2020 12:07:26 | Автор: admin


Как часто, при разработке прошивки для микроконтроллера, во время отладки, когда байтики не бегают по UART, вы восклицаете: Ааа, точно! Не включил тактирование!. Или, при смене ножки светодиода, забывали подать питание на новый порт? Думаю, что довольно часто. Я, по крайней мере, уж точно.

На первый взгляд может показаться, что управление тактированием периферии тривиально: записал 1 включил, 0 выключил.

Но просто, не всегда оказывается эффективно

Постановка задачи


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

  • Во встраиваемых системах, один из самых главных критериев это минимально-возможный результирующий код, исполняемый за минимальное время
  • Легкая масштабируемость. Добавление или изменение в проекте какой-либо периферии не должно сопровождаться code review всех исходников, чтобы удалить строчки включения/отключения тактирования
  • Пользователь должен быть лишен возможности совершить ошибку, либо, по крайней мере эта возможность должна быть сведена к минимуму
  • Нет необходимости работы с отдельными битами и регистрами
  • Удобство и однообразность использования независимо от микроконтроллера
  • Помимо основных возможностей включения и выключения тактирования периферии необходим расширенный функционал (о нем речь пойдет далее)

После выяснения критериев оценки, поставим конкретную задачу, попутно определив условия и окружение для реализации:

Компилятор: GCC 10.1.1 + Make
Язык: C++17
Среда: Visual Studio Code
Контроллер: stm32f103c8t6 (cortex-m3)
Задача: включение тактирования SPI2, USART1 (оба интерфейса с использованием DMA)

Выбор данного контроллера обусловлен, естественно, его распространённостью, особенно, благодаря одному из китайских народных промыслов производству плат Blue Pill.



С точки зрения идеологии, совершенно неважно, какой именно контроллер выбран: stmf1, stmf4 или lpc, т.к. работа с системой тактирования периферии сводится лишь к записи в определенный бит либо 0 для выключения, либо 1 для включения.

В stm32f103c8t6 имеется 3 регистра, которые ответственны за включение тактирования периферии: AHBENR, APB1ENR, APB2ENR.

Аппаратные интерфейсы передачи данных SPI2 и USART1 выбраны неслучайно, потому что для их полноценного функционирования необходимо включить биты тактирования, расположенные во всех перечисленных регистрах биты самих интерфейсов, DMA1, а также биты портов ввода-вывода (GPIOB для SPI2 и GPIOA для USART1).




Следует отметить, что для оптимальной работы с тактированием, необходимо учитывать AHBENR содержит разделяемый ресурс, используемые для функционирования как SPI2, так и USART1. То есть, отключение DMA сразу приведет к неработоспособности обоих интерфейсов, вместе с тем, КПД повторного включения будет даже не нулевым, а отрицательным, ведь эта операция займет память программ и приведет к дополнительному расходу тактов на чтение-модификацию-запись volatile регистра.

Разобравшись с целями, условиями и особенностями задачи, перейдем к поиску решений.

Основные подходы


В этом разделе собраны типовые способы включения тактирования периферии, которые мне встречались и, наверняка, Вы их также видели и/или используете. От более простых, реализуемых на C, до fold expression из C++17. Рассмотрены присущие им достоинства и недостатки.

Если Вы хотите перейти непосредственно к метапрограммированию, то этот раздел можно пропустить и перейти к следующему.

Прямая запись в регистры


Классический способ, доступный из коробки и для С и для C++. Вендор, чаще всего, представляет заголовочные файлы для контроллера, в которых задефайнены все регистры и их биты, что дает возможность сразу начать работу с периферией:

int main(){  RCC->AHBENR  |= RCC_AHBENR_DMA1EN;  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN               |  RCC_APB2ENR_IOPBEN               |  RCC_APB2ENR_USART1EN;  RCC->APB2ENR |= RCC_APB1ENR_SPI2EN;}

Листинг
    // AHBENR(Включение DMA1)  ldr     r3, .L3  ldr     r2, [r3, #20]  orr     r2, r2, #1  str     r2, [r3, #20]    // APB2ENR(Включение GPIOA, GPIOB, USART1)  ldr     r2, [r3, #24]  orr     r2, r2, #16384  orr     r2, r2, #12  str     r2, [r3, #24]    // APB1ENR(Включение SPI2)  ldr     r2, [r3, #28]  orr     r2, r2, #16384  str     r2, [r3, #28]


Размер кода: 36 байт. Посмотреть

Плюсы:

  • Минимальный размер кода и скорость выполнения
  • Самый простой и очевидный способ

Минусы:

  • Необходимо помнить и названия регистров и названия битов, либо постоянно обращаться к мануалу
  • Легко допустить ошибку в коде. Читатель, наверняка, заметил, что вместо SPI2 был повторно включен USART1
  • Для работы некоторых периферийных блоков требуется также включать другую периферию, например, GPIO и DMA для интерфейсов
  • Полное отсутствие переносимости. При выборе другого контроллера этот код теряет смысл

При всех недостатках, этот способ остается весьма востребованным, по крайней мере тогда, когда нужно пощупать новый контроллер, написав очередной Hello, World! мигнув светодиодом.

Функции инициализации


Давайте попробуем абстрагироваться и спрятать работу с регистрами от пользователя. И в этом нам поможет обыкновенная C-функция:

void UART1_Init(){ RCC->AHBENR  |= RCC_AHBENR_DMA1EN; RCC->APB2ENR |= RCC_APB2ENR_IOPAEN              |  RCC_APB2ENR_USART1EN;  // Остальная инициализация}void SPI2_Init(){ RCC->AHBENR  |= RCC_AHBENR_DMA1EN; RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;  // Остальная инициализация}int main(){  UART1_Init();  SPI2_Init();}

Размер кода: 72 байта. Посмотреть

Листинг
UART1_Init():    // AHBENR(Включение DMA1)  ldr     r2, .L2  ldr     r3, [r2, #20]  orr     r3, r3, #1  str     r3, [r2, #20]    // APB2ENR(Включение GPIOA, USART1)  ldr     r3, [r2, #24]  orr     r3, r3, #16384  orr     r3, r3, #4  str     r3, [r2, #24]  bx      lrSPI2_Init():    //Повторно (!) AHBENR(Включение DMA1)  ldr     r3, .L5  ldr     r2, [r3, #20]  orr     r2, r2, #1  str     r2, [r3, #20]    //Повторно (!) APB2ENR(Включение GPIOB)  ldr     r2, [r3, #24]  orr     r2, r2, #8  str     r2, [r3, #24]    //Запись в APB1ENR(Включение SPI2)  ldr     r2, [r3, #28]  orr     r2, r2, #16384  str     r2, [r3, #28]  bx      lrmain:   push    {r3, lr}   bl      UART1_Init()   bl      SPI2_Init()


Плюсы:

  • Можно не заглядывать в мануал по каждому поводу
  • Ошибки локализованы на этапе написания драйвера периферии
  • Пользовательский код легко воспринимать

Минусы:

  • Количество необходимых инструкций возросло кратно количеству задействованной периферии
  • Очень много дублирования кода для каждого номера UART и SPI он будет фактически идентичен

Хоть мы и избавились от прямой записи в регистры в пользовательском коде, но какой ценой? Требуемый размер памяти и время исполнения для включения увеличились в 2 раза и продолжат расти, при большем количестве задействованной периферии.

Функция включения тактирования


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

void PowerEnable(uint32_t ahb, uint32_t apb2, uint32_t apb1){    RCC->AHBENR  |= ahb;    RCC->APB2ENR |= apb2;    RCC->APB1ENR |= apb1;}void UART_Init(int identifier){    uint32_t ahb = RCC_AHBENR_DMA1EN, apb1 = 0U, apb2 = 0U;    if (identifier == 1){      apb2 = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN;    }     else if (identifier == 2){}    PowerEnable(ahb, apb2, apb1);  // Остальная инициализация}void SPI_Init(int identifier){    uint32_t ahb = RCC_AHBENR_DMA1EN, apb1 = 0U, apb2 = 0U;    if (identifier == 1){}     else if (identifier == 2){      apb2 = RCC_APB2ENR_IOPBEN;      apb1 = RCC_APB1ENR_SPI2EN;    }    PowerEnable(ahb, apb2, apb1);  // Остальная инициализация}int main(){  UART_Init(1);  SPI_Init(2);}

Размер кода: 92 байта. Посмотреть

Листинг
PowerEnable(unsigned long, unsigned long, unsigned long):  push    {r4}  ldr     r3, .L3  ldr     r4, [r3, #20]  orrs    r4, r4, r0  str     r4, [r3, #20]  ldr     r0, [r3, #24]  orrs    r0, r0, r1  str     r0, [r3, #24]  ldr     r1, [r3, #28]  orrs    r1, r1, r2  str     r1, [r3, #28]  pop     {r4}  bx      lrUART_Init(int):  push    {r3, lr}  cmp     r0, #1  mov     r2, #0  movw    r1, #16388  it      ne  movne   r1, r2  movs    r0, #1  bl      PowerEnable(unsigned long, unsigned long, unsigned long)  pop     {r3, pc}SPI_Init(int):  push    {r3, lr}  cmp     r0, #2  ittee   eq  moveq   r1, #8  moveq   r1, #16384  movne   r1, #0  movne   r2, r1  movs    r0, #1  bl      PowerEnable(unsigned long, unsigned long, unsigned long)  pop     {r3, pc}main:   push    {r3, lr}   movs    r0, #1   bl      UART_Init(int)   movs    r0, #2   bl      SPI_Init(int)


Плюсы:
  • Удалось сократить код описания драйверов микроконтроллера
  • Результирующее количество инструкций сократилось*

Минусы:

  • Увеличилось время выполнения

*Да, в данном случае размер исполняемого кода возрос, по сравнению с предыдущим вариантом, но это связано с появлением условных операторов, влияние которых можно нивелировать, если задействовать хотя бы по 2 экземпляра каждого вида периферии.

Т.к. функция включения принимает параметры, то в ассемблере появились операции работы со стеком, что также негативно сказывается на производительности.

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

Свойства-значения и шаблоны


Начиная рассматривать плюсовый подход, сразу пропустим вариант включения тактирования в конструкторе класса, т.к. этот метод фактически не отличается от инициализирующих функций в стиле C.

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

struct Power{template< uint32_t valueAHBENR, uint32_t valueAPB2ENR, uint32_t valueAPB1ENR>    static void Enable(){// Если значение = 0, то в результирующем коде операций с регистром не будет        if constexpr (valueAHBENR)            RCC->AHBENR |= valueAHBENR;        if constexpr (valueAPB2ENR)            RCC->APB2ENR |= valueAPB2ENR;        if constexpr (valueAPB1ENR)            RCC->APB1ENR |= valueAPB1ENR;    };};template<auto identifier>struct UART{// С помощью identifier на этапе компиляции можно выбрать значения для периферии  static constexpr auto valueAHBENR = RCC_AHBENR_DMA1EN;  static constexpr auto valueAPB1ENR = identifier == 1 ? 0U : RCC_APB1ENR_USART2EN;  static constexpr auto valueAPB2ENR = RCC_APB2ENR_IOPAEN                                    |  (identifier == 1 ? RCC_APB2ENR_USART1EN : 0U);    // Остальная реализация};template<auto identifier>struct SPI{  static constexpr auto valueAHBENR = RCC_AHBENR_DMA1EN;  static constexpr auto valueAPB1ENR = identifier == 1 ? 0U : RCC_APB1ENR_SPI2EN;  static constexpr auto valueAPB2ENR = RCC_APB2ENR_IOPBEN                                    |  (identifier == 1 ? RCC_APB2ENR_SPI1EN : 0U);    // Остальная реализация};int main(){    // Необязательные псевдонимы для используемой периферии  using uart = UART<1>;  using spi = SPI<2>;  Power::Enable<                uart::valueAHBENR  | spi::valueAHBENR,                uart::valueAPB2ENR | spi::valueAPB2ENR,                uart::valueAPB1ENR | spi::valueAPB1ENR                >();}

Размер кода: 36 байт. Посмотреть

Листинг
main:    // AHBENR(Включение DMA1)  ldr     r3, .L3  ldr     r2, [r3, #20]  orr     r2, r2, #1  str     r2, [r3, #20]    // APB2ENR(Включение GPIOA, GPIOB, USART1)  ldr     r2, [r3, #24]  orr     r2, r2, #16384  orr     r2, r2, #12  str     r2, [r3, #24]    // APB1ENR(Включение SPI2)  ldr     r2, [r3, #28]  orr     r2, r2, #16384  str     r2, [r3, #28]


Плюсы:

  • Размер и время выполнения получились такими же, как и в эталонном варианте с прямой записью в регистры
  • Довольно просто масштабировать проект достаточно добавить воды соответствующее свойство-значение периферии

Минусы:

  • Можно совершить ошибку, поставив свойство-значение не в тот параметр
  • Как и в случае с прямой записью в регистры страдает переносимость
  • Перегруженность конструкции

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

Идеальный вариант почти


Чтобы уменьшить количество пользовательского кода и возможностей для ошибок воспользуемся parameter pack, который уберет обращение к свойствам классов периферии в пользовательском коде. При этом изменится только метод включения тактирования:

struct Power{template<typename... Peripherals>  static void Enable(){      // Для всех параметров пакета будет применена операция |       // В нашем случае value = uart::valueAHBENR | spi::valueAHBENR и т.д.    if constexpr (constexpr auto value = (Peripherals::valueAHBENR | ... ); value)      RCC->AHBENR |= value;    if constexpr (constexpr auto value = (Peripherals::valueAPB2ENR | ... ); value)      RCC->APB2ENR |= value;    if constexpr (constexpr auto value = (Peripherals::valueAPB1ENR | ... ); value)      RCC->APB1ENR |= value;  };};    int main(){    // Необязательные псевдонимы для используемой периферии  using uart = UART<1>;  using spi = SPI<2>;  Power::Enable<uart, spi>();}

Размер кода: 36 байт. Посмотреть

Листинг
main:    // AHBENR(Включение DMA1)  ldr     r3, .L3  ldr     r2, [r3, #20]  orr     r2, r2, #1  str     r2, [r3, #20]    // APB2ENR(Включение GPIOA, GPIOB, USART1)  ldr     r2, [r3, #24]  orr     r2, r2, #16384  orr     r2, r2, #12  str     r2, [r3, #24]    // APB1ENR(Включение SPI2)  ldr     r2, [r3, #28]  orr     r2, r2, #16384  str     r2, [r3, #28]


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

И, вроде бы, можно на этом остановиться, но


Расширяем функционал


Обратимся к одной из поставленных целей:
Помимо основных возможностей включения и выключения тактирования периферии необходим расширенный функционал

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

В контексте условий, озвученных в начале статьи, будем считать, что генератором события пробуждения будет USART1, а SPI2 и соответствующий ему порт GPIOB необходимо отключать. При этом, общиий ресурс DMA1 должен оставаться включенным.

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

int main(){  using uart = UART<1>;  using spi = SPI<2>;    // Включаем USART, SPI, DMA, GPIOA, GPIOB  Power::Enable<uart, spi>();    // Some code    // Выключаем SPI и GPIOB вместе (!) с DMA  Power::Disable<spi>();        // Включаем обратно DMA вместе(!) с USART и GPIOA  Power::Enable<uart>();        // Sleep();    // Включаем SPI и GPIOB вместе(!) с DMA  Power::Enable<spi>();}

Размер кода: 100 байт. Посмотреть

Листинг
main:        // AHBENR(Включение DMA1)        ldr     r3, .L3        ldr     r2, [r3, #20]        orr     r2, r2, #1        str     r2, [r3, #20]       // APB2ENR(Включение GPIOA, GPIOB, USART1)        ldr     r2, [r3, #24]        orr     r2, r2, #16384        orr     r2, r2, #12        str     r2, [r3, #24]       // APB1ENR(Включение SPI2)        ldr     r2, [r3, #28]        orr     r2, r2, #16384        str     r2, [r3, #28]        // Выключение SPI2       // AHBENR(Выключение DMA1)        ldr     r2, [r3, #20]        bic     r2, r2, #1        str     r2, [r3, #20]       // APB2ENR(Выключение GPIOB)        ldr     r2, [r3, #24]        bic     r2, r2, #8        str     r2, [r3, #24]       // APB1ENR(Выключение SPI2)        ldr     r2, [r3, #28]        bic     r2, r2, #16384        str     r2, [r3, #28]        // Повторное (!) включение USART1        // AHBENR(Включение DMA1)        ldr     r2, [r3, #20]        orr     r2, r2, #1        str     r2, [r3, #20]       // APB2ENR(Включение GPIOA, USART1)        ldr     r2, [r3, #24]        orr     r2, r2, #16384        orr     r2, r2, #4        str     r2, [r3, #24]        // Sleep();        // AHBENR(Включение DMA1)        ldr     r2, [r3, #20]        orr     r2, r2, #1        str     r2, [r3, #20]       // APB2ENR(Включение GPIOB)        ldr     r2, [r3, #24]        orr     r2, r2, #8        str     r2, [r3, #24]       // APB1ENR(Включение SPI2)        ldr     r2, [r3, #28]        orr     r2, r2, #16384        str     r2, [r3, #28]


В это же время эталонный код на регистрах занял 68 байт. Посмотреть

Очевидно, что для подобных задач, камнем преткновения станут разделяемые ресурсы, такие как DMA. Кроме того, конкретно в этом случае возникнет момент, когда оба интерфейса станут неработоспособными и, фактически, произойдет аварийная ситуация.

Давайте попробуем найти решение

Структура


Для упрощения понимания и разработки изобразим общую структуру тактирования, какой мы ее хотим видеть:



Она состоит всего из четырех блоков:

Независимые:

  • IPower интерфейс взаимодействия с пользователем, подготавливающий данные для записи в регистры
  • Hardware запись значений в регистры контроллера

Аппаратно-зависимые:
  • Peripherals периферия, которая используется в проекте и сообщает интерфейсу, какие устройства надо включить или выключить
  • Adapter передает значения для записи в Hardware, указывая в какие именно регистры их следует записать

Интерфейс IPower


С учетом всех требований, определим методы, необходимые в интерфейсе:

template<typename Peripherals>Enable();template<typename EnableList, typename ExceptList>EnableExcept();template<typename EnableList, typename DisableList>Keep();

Enable включение периферии, указанной в параметре шаблона.

EnableExcept включение периферии, указанной в параметре EnableList, за исключением той, что указана в ExceptList.

Пояснение
Таблица истинности
Бит включения Бит исключения Результат включение Результат выключение
0 0 0 0
0 1 0 0
1 0 1 0
1 1 0 0

Например, вызов:
EnableExcept<spi, uart>();

должен установить бит SPI2EN и бит IOPBEN. В то время, как общий DMA1EN, а также USART1EN и IOPAEN останутся в исходном состоянии.

Чтобы получить соответствующую таблицу истинности, необходимо произвести следующие операции:

resultEnable = (enable ^ except) & enable


К ним в дополнение также идут комплементарные методы Disable, выполняющие противоположные действия.

Keep включение периферии из EnableList, выключение периферии из DisableList, при этом, если периферия присутствует в обоих списках, то она сохраняет свое состояние.

Пояснение
Таблица истинности
Бит включения Бит выключения Результат включение Результат выключение
0 0 0 0
0 1 0 1
1 0 1 0
1 1 0 0

Например, при вызове:
Keep<spi, uart>();

установятся SPI2EN и IOPBEN, при этом USART1EN и IOPAEN сбросятся, а DMA1EN останется неизменным.

Чтобы получить соответствующую таблицу истинности, необходимо произвести следующие операции:

resultEnable = (enable ^ disable) & enableresultDisable = (enable ^ disable) & disable


Методы включения/выключения уже реализованы довольно неплохо с помощью fold expression, но как быть с остальными?

Если ограничиться использованием 2 видов периферии, как это сделано в пояснении, то никаких сложностей не возникнет. Однако, когда в проекте используется много различных периферийных устройств, появляется проблема в шаблоне нельзя явно использовать более одного parameter pack, т.к. компилятор не сможет определить где заканчивается один и начинается второй:

template<typename EnableList, typename ExceptList>EnableExcept(){};  // Невозможно определить где заканчивается EnableList и начинается ExceptListEnableExcept<spi2, pin3, uart1, pin1, i2c3>();

Можно было бы создать отдельный класс-обертку для периферии и передавать его в метод:

template<typename Peripherals>PowerWrap{  static constexpr auto valueAHBENR = (Peripherals::valueAHBENR | );  static constexpr auto valueAPB1ENR = (Peripherals:: valueAPB1ENR | );  static constexpr auto valueAPB2ENR = (Peripherals:: valueAPB2ENR | );};using EnableList = PowerWrap<spi2, uart1>;using ExceptList = PowerWrap<pin1, i2c1>;EnableExcept<EnableList, ExceptList>();

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

Поскольку вся используемая периферия и регистры тактирования известны на этапе компиляции, то поставленную задачу возможно решить с использованием метапрограммирования.

Метапрограммирование


Из-за того, что в основе метапрограммирования лежит работа не с обычными типами, а с их списками, то определим две сущности, которые будут оперировать типовыми и нетиповыми параметрами:

template<typename... Types>struct Typelist{};template<auto... Values>struct Valuelist{};using listT = Typelist<char, int> ;// Список из последовательности типов char и intusing listV = Valuelist<8,9,5,11> ;// Список из 4 нетиповых параметров

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

1. Извлечение первого элемента из списка

front
  // Прототип функцииtemplate<typename List>struct front;  // Специализация для списка типов  // Разделение списка в пакете параметров на заглавный и оставшиесяtemplate<typename Head, typename... Tail>struct front<Typelist<Head, Tail...>>{     // Возвращение заглавного типа  using type = Head; }; // Специализация для списка нетиповых параметровtemplate<auto Head, auto... Tail>struct front<Valuelist<Head, Tail...>> {  // Возвращение заглавного значения  static constexpr auto value = Head;};  // Псевдонимы для простоты использованияtemplate<typename List>using front_t = typename front<List>::type;template<typename List>static constexpr auto front_v = front<List>::value;  // Примерыusing listT = Typelist<char, bool, int>;using type = front_t<listT>; // type = charusing listV = Valuelist<9,8,7>;constexpr auto value = front_v<listV>; //value = 9


2. Удаление первого элемента из списка

pop_front
template<typename List>struct pop_front;  // Специализация для списка типов  // Разделение списка в пакете параметров на заглавный и оставшиесяtemplate<typename Head, typename... Tail>struct pop_front<Typelist<Head, Tail...>> {  // Возвращение списка, содержащего оставшиеся типы  using type = Typelist<Tail...>;};template<auto Head, auto... Tail>struct pop_front<Valuelist<Head, Tail...>> {  using type = Valuelist<Tail...>;};template<typename List>using pop_front_t = typename pop_front<List>::type; // Примерыusing listT = Typelist<char, bool, int>;using typeT = pop_front_t<listT>; // type = Typelist<bool, int>using listV = Valuelist<9,8,7>;using typeV = pop_front_t<listV>; // type = Valuelist<8,7>


3. Добавление элемента в начало списка
push_front
template<typename List, typename NewElement>struct push_front;template<typename... List, typename NewElement>struct push_front<Typelist<List...>, NewElement> {  using type = Typelist<NewElement, List...>;};template<typename List, typename NewElement>using push_front_t = typename push_front<List, NewElement>::type;  // Примерusing listT = Typelist<char, bool, int>;using typeT = push_front_t<listT, long >; // type = Typelist<long, char, bool, int>


4. Добавление нетипового параметра в конец списка

push_back_value
template<typename List, auto NewElement>struct push_back;template<auto... List, auto NewElement>struct push_back<Valuelist<List...>, NewElement>{  using type = Valuelist<List..., NewElement>;};template<typename List, auto NewElement>using push_back_t = typename push_back<List, NewElement>::type;  // Примерusing listV = Valuelist<9,8,7>;using typeV = push_back_t<listV, 6>; // typeV = Valuelist<9,8,7,6>


5. Проверка списка на пустоту

is_empty
template<typename List>struct is_empty{    static constexpr auto value = false;}; // Специализация для базового случая, когда список пустtemplate<>struct is_empty<Typelist<>>{    static constexpr auto value = true;};template<typename List>static constexpr auto is_empty_v = is_empty<List>::value; // Примерusing listT = Typelist<char, bool, int>;constexpr auto value = is_empty_v<listT>; // value = false


6. Нахождение количества элементов в списке

size_of_list
  // Функция рекурсивно извлекает по одному элементу из списка,  // инкрементируя счетчик count, пока не дойдет до одного из 2 базовых случаевtemplate<typename List, std::size_t count = 0>struct size_of_list : public size_of_list<pop_front_t<List>, count + 1>{};  // Базовый случай для пустого списка типовtemplate<std::size_t count>struct size_of_list<Typelist<>, count>{  static constexpr std::size_t value = count;};  // Базовый случай для пустого списка нетиповых параметров template<std::size_t count>struct size_of_list<Valuelist<>, count>{  static constexpr std::size_t value = count;};template<typename List>static constexpr std::size_t size_of_list_v = size_of_list<List>::value;  // Примерusing listT = Typelist<char, bool, int>;constexpr auto value = size_of_list_v <listT>; // value = 3


Теперь, когда определены все базовые действия, можно переходить к написанию метафункций для битовых операций: or, and, xor, необходимых для методов интерфейса.

Поскольку эти битовые преобразования однотипны, то попытаемся сделать реализацию максимально обобщенно, чтобы избежать дублирования кода.

Функция, выполняющая абстрактную операцию над списком

lists_operation
template<template<typename first, typename second> class operation,         typename Lists, bool isEnd = size_of_list_v<Lists> == 1>class lists_operation{  using first = front_t<Lists>; // (3)  using second = front_t<pop_front_t<Lists>>; // (4)  using next = pop_front_t<pop_front_t<Lists>>; // (5)  using result = operation<first, second>; // (6)public:  using type = typename       lists_operation<operation, push_front_t<next, result>>::type; // (7)};template<template<typename first, typename second> class operation, typename List>class lists_operation<operation, List, true>{ // (1)public:  using type = front_t<List>; // (2)};

Lists список, состоящий из типов или списков, над которым необходимо провести некоторое действие.
operation функциональный адаптер, который принимает 2 первых элемента Lists и возвращает результирующий тип после операции.
isEnd граничное условие метафункции, которое проверяет количество типов в Lists.

В базовом случае (1) Lists состоит из 1 элемента, поэтому результатом работы функции станет его извлечение(2).

Для остальных случаев определяют первый (3) и второй (4) элементы из Lists, к которым применяется операция (6). Для получения результирующего типа (7) происходит рекурсивный вызов метафункции с новым списком типов, на первом месте которого стоит (6), за которым следуют оставшиеся типы (5) исходного Lists. Окончанием рекурсии становиться вызов специализации (1).

Далее, реализуем операцию для предыдущей метафункции, которая почленно будет производить абстрактные действия над нетиповыми параметрами из двух списков:

valuelists_operation
template<template <auto value1, auto value2> typename operation,          typename List1, typename List2, typename Result = Valuelist<>>struct operation_2_termwise_valuelists{  constexpr static auto newValue =       operation<front_v<List1>, front_v<List2>>::value; // (2)    using nextList1 = pop_front_t<List1>;  using nextList2 = pop_front_t<List2>;      using result = push_back_value_t<Result, newValue>; // (3)  using type = typename       operation_2_termwise_valuelists <operation, nextList1, nextList2, result>::type; // (4)};template<template <auto value1, auto value2> typename operation, typename Result>struct operation_2_termwise_valuelists <operation, Valuelist<>, Valuelist<>, Result>{ // (1)  using type = Result;};

List1 и List2 списки нетиповых параметров, над которыми необходимо произвести действие.
operation операция, производимая над нетиповыми параметрами.
Result тип, используемый для накопления промежуточных результатов.

Базовый случай (1), когда оба списка пусты, возвращает Result.

Для остальных случаев происходит вычисление значения операции (2) и занесение его в результирующий список Result (3). Далее рекурсивно вызывается метафункция (4) до того момента, пока оба списка не станут пустыми.

Функции битовых операций:

bitwise_operation
template<auto value1, auto value2>struct and_operation{ static constexpr auto value = value1 & value2;};template<auto value1, auto value2>struct or_operation{ static constexpr auto value = value1 | value2;};template<auto value1, auto value2>struct xor_operation{ static constexpr auto value = value1 ^ value2;};


Осталось создать псевдонимы для более простого использования:
псевдонимы
  // Псевдонимы для битовых почленных операций над 2 спискамиtemplate<typename List1, typename List2>using operation_and_termwise_t = typename           operation_2_termwise_valuelists<and_operation, List1, List2>::type;template<typename List1, typename List2>using operation_or_termwise_t = typename           operation_2_termwise_valuelists<or_operation, List1, List2>::type;template<typename List1, typename List2>using operation_xor_termwise_t = typename           operation_2_termwise_valuelists<xor_operation, List1, List2>::type;  // Псевдонимы почленных битовых операций для произвольного количества списковtemplate<typename... Lists>using lists_termwise_and_t = typename           lists_operation<operation_and_termwise_t, Typelist<Lists...>>::type;template<typename... Lists>using lists_termwise_or_t= typename           lists_operation<operation_or_termwise_t, Typelist<Lists...>>::type;template<typename... Lists>using lists_termwise_xor_t = typename           lists_operation<operation_xor_termwise_t, Typelist<Lists...>>::type;

Пример использования (обратите внимание на вывод ошибок).

Возвращаясь к имплементации интерфейса


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

template<typename adapter>  struct IPower{  template<typename... Peripherals>  static void Enable(){           // Раскрытие пакета параметров периферии, содержащей свойство power      // и применение побитового или к значениям    using tEnableList = lists_termwise_or_t<typename Peripherals::power...>;      // Псевдоним Valuelist<>, содержащий только 0,       // количество которых равно количеству регистров    using tDisableList = typename adapter::template fromValues<>::power;         // Передача списков включения/отключения адаптеру   adapter:: template _Set<tEnableList , tDisableList>();  }  template<typename EnableList, typename ExceptList>  static void EnableExcept(){    using tXORedList = lists_termwise_xor_t <        typename EnableList::power, typename ExceptList::power>;    using tEnableList = lists_termwise_and_t <        typename EnableList::power, tXORedList>;    using tDisableList = typename adapter::template fromValues<>::power;    adapter:: template _Set<tEnableList , tDisableList>();  }  template<typename EnableList, typename DisableList>    static void Keep(){    using tXORedList = lists_termwise_xor_t <        typename EnableList::power, typename DisableList::power>;    using tEnableList = lists_termwise_and_t <        typename EnableList::power, tXORedList>;    using tDisableList = lists_termwise_and_t <        typename DisableList::power, tXORedList>;    adapter:: template _Set<tEnableList , tDisableList>();  }  template<typename... PeripheralsList>  struct fromPeripherals{    using power = lists_termwise_or_t<typename PeripheralsList::power...>;  };};

Также, интерфейс содержит встроенный класс fromPeripherals, позволяющий объединять периферию в один список, который, затем, можно использовать в методах:

  using listPower = Power::fromPeripherals<spi, uart>;  Power::Enable<listPower>();

Методы Disable реализуются аналогично.

Адаптер контроллера


В классе адаптера необходимо задать адреса регистров тактирования и определить последовательность, в которой будет производиться запись в них, а затем передать управление непосредственно классу, который установит или сбросит биты указанных регистров.

struct Power: public IPower<Power>{  static constexpr uint32_t     _addressAHBENR  = 0x40021014,    _addressAPB2ENR = 0x40021018,    _addressAPB1ENR = 0x4002101C;    using AddressesList = Valuelist<      _addressAHBENR, _addressAPB1ENR, _addressAPB2ENR>;  template<typename EnableList, typename DisableList>  static void _Set(){    // Вызов метода класса, осуществляющий запись в регистры    HPower:: template ModifyRegisters<EnableList, DisableList, AddressesList>();  }      template<uint32_t valueAHBENR = 0, uint32_t valueAPB1ENR = 0, uint32_t valueAPB2ENR = 0>  struct fromValues{    using power = Valuelist<valueAHBENR, valueAPB1ENR, valueAPB2ENR>;  };};

Периферия


Наделяем периферию свойством power, используя структуру fromValues адаптера:

template<int identifier>struct SPI{  // С помощью identifier можно выбирать необходимые биты на этапе компиляции  using power = Power::fromValues<      RCC_AHBENR_DMA1EN, // Значения для соответствующих регистров,      RCC_APB1ENR_SPI2EN, // последовательность которых определена в адаптере      RCC_APB2ENR_IOPBEN>::power;};template<int identifier>struct UART{  using power = Power::fromValues<      RCC_AHBENR_DMA1EN,      0U,       RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN>::power;};

Запись в регистры


Класс состоит из рекурсивного шаблонного метода, задача которого сводится к записи значений в регистры контроллера, переданные адаптером.

В качестве параметров, метод принимает 3 списка нетиповых параметров Valuelist<>:

  • SetList и ResetList списки из последовательностей значений битов, которые необходимо установить/сбросить в регистре
  • AddressesList список адресов регистров, в которые будет производится запись значений из предыдущих параметров

struct HPower{  template<typename SetList, typename ResetList, typename AddressesList>    static void ModifyRegisters(){    if constexpr (!is_empty_v<SetList> && !is_empty_v<ResetList> &&   !is_empty_v<AddressesList>){        // Получаем первые значения списков      constexpr auto valueSet = front_v<SetList>;      constexpr auto valueReset = front_v<ResetList>;      if constexpr(valueSet || valueReset){        constexpr auto address = front_v<AddressesList>;        using pRegister_t = volatile std::remove_const_t<decltype(address)>* const;        auto& reg = *reinterpret_cast<pRegister_t>(address);        // (!)Единственная строчка кода, которая может попасть в ассемблерный листинг        reg = (reg &(~valueReset)) | valueSet;      }        // Убираем первые значения из всех списков                  using tRestSet = pop_front_t<SetList>;      using tRestReset = pop_front_t<ResetList>;      using tRestAddress = pop_front_t<AddressesList>;              // Вызывается до тех пор, пока списки не станут пустыми      ModifyRegisters<tRestSet, tRestReset, tRestAddress>();    }  };};

В классе присутствует единственная строчка кода, которая попадет в ассемблерный листинг.

Теперь, когда все блоки структуры готовы, перейдём к тестированию.

Тестируем код


Вспомним условия последней задачи:

  • Включение SPI2 и USART1
  • Выключение SPI2 перед входом в режим энергосбережения
  • Включение SPI2 после выхода из режима энергосбережения

// Необязательные псевдонимы для периферииusing spi = SPI<2>;using uart = UART<1>;// Задаем списки управления тактированием (для удобства)using listPowerInit = Power::fromPeripherals<spi, uart>;using listPowerDown = Power::fromPeripherals<spi>;using listPowerWake = Power::fromPeripherals<uart>;int main() {   // Включение SPI2, UASRT1, DMA1, GPIOA, GPIOB    Power::Enable<listPowerInit>();    // Some code        // Выключение только SPI2 и GPIOB    Power::DisableExcept<listPowerDown, listPowerWake>();    //Sleep();    // Включение только SPI2 и GPIOB    Power::EnableExcept<listPowerDown, listPowerWake>();}

Размер кода: 68 байт*, как и в случае с прямой записью в регистры.

Листинг
main:  // AHBENR(Включение DMA1)  ldr     r3, .L3  ldr     r2, [r3, #20]  orr     r2, r2, #1  str     r2, [r3, #20]  // APB1ENR(Включение SPI2  ldr     r2, [r3, #28]  orr     r2, r2, #16384  str     r2, [r3, #28]  // APB2ENR(Включение GPIOA, GPIOB, USART1)  ldr     r2, [r3, #24]  orr     r2, r2, #16384  orr     r2, r2, #12  str     r2, [r3, #24]  // APB1ENR(Выключение SPI2)  ldr     r2, [r3, #28]  bic     r2, r2, #16384  str     r2, [r3, #28]  // APB2ENR(Выключение GPIOB)  ldr     r2, [r3, #24]  bic     r2, r2, #8  str     r2, [r3, #24]  // APB1ENR(Включение SPI2  ldr     r2, [r3, #28]  orr     r2, r2, #16384  str     r2, [r3, #28]  // APB2ENR(Выключение GPIOB)  ldr     r2, [r3, #24]  orr     r2, r2, #8  str     r2, [r3, #24]


*При использовании GCC 9.2.1 получается на 8 байт больше, чем в версии GCC 10.1.1. Как видно из листинга добавляются несколько ненужных инструкций, например, перед чтением по адресу (ldr) есть инструкция добавления (adds), хотя эти инструкции можно заменить на чтение со смещением. Новая версия оптимизирует эти операции. При этом clang генерирует одинаковые листинги.

Итоги


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

Возможно, объем исходного кода и сложность разработки покажутся избыточными, однако, благодаря такому количеству абстракций, переход к новому контроллеру займет минимум усилий: 30 строчек понятного кода адаптера + по 5 строк на периферийный блок.

Полный код
type_traits_custom.hpp
#ifndef _TYPE_TRAITS_CUSTOM_HPP#define _TYPE_TRAITS_CUSTOM_HPP#include <type_traits>/*!  @file  @brief Traits for metaprogramming*//*!  @brief Namespace for utils.*/namespace utils{/*-----------------------------------Basic----------------------------------------*//*!  @brief Basic list of types  @tparam Types parameter pack*/template<typename... Types>struct Typelist{};/*!  @brief Basic list of values  @tparam Values parameter pack*/template<auto... Values>struct Valuelist{};/*------------------------------End of Basic--------------------------------------*//*----------------------------------Front-------------------------------------------  Description:  Pop front type or value from list  using listOfTypes = Typelist<int, short, bool, unsigned>;  using listOfValues = Valuelist<1,2,3,4,5,6,1>;  |-----------------|--------------------|----------|  |      Trait      |    Parameters      |  Result  |  |-----------------|--------------------|----------|  |     front_t     |   <listOfTypes>    |    int   |  |-----------------|--------------------|----------|  |     front_v     |   <listOfValues>   |     1    |  |-----------------|--------------------|----------| */namespace{template<typename List>struct front;template<typename Head, typename... Tail>struct front<Typelist<Head, Tail...>>{   using type = Head; };template<auto Head, auto... Tail>struct front<Valuelist<Head, Tail...>> {  static constexpr auto value = Head;};}template<typename List>using front_t = typename front<List>::type;template<typename List>static constexpr auto front_v = front<List>::value;/*----------------------------------End of Front----------------------------------*//*----------------------------------Pop_Front---------------------------------------  Description:  Pop front type or value from list and return rest of the list  using listOfTypes = Typelist<int, short, bool>;  using listOfValues = Valuelist<1,2,3,4,5,6,1>;  |-----------------|--------------------|------------------------|  |      Trait      |    Parameters      |         Result         |  |-----------------|--------------------|------------------------|  |   pop_front_t   |    <listOfTypes>   | Typelist<short, bool>  |  |-----------------|--------------------|------------------------|  |   pop_front_t   |   <listOfValues>   | Valuelist<2,3,4,5,6,1> |  |-----------------|--------------------|------------------------| */namespace{template<typename List>struct pop_front;template<typename Head, typename... Tail>struct pop_front<Typelist<Head, Tail...>> {  using type = Typelist<Tail...>;};template<auto Head, auto... Tail>struct pop_front<Valuelist<Head, Tail...>> {  using type = Valuelist<Tail...>;};}template<typename List>using pop_front_t = typename pop_front<List>::type;/*------------------------------End of Pop_Front----------------------------------*//*----------------------------------Push_Front--------------------------------------  Description:  Push new element to front of the list  using listOfTypes = Typelist<short, bool>;  |-----------------------|--------------------------|-------------------------------|  |      Trait            |        Parameters        |             Result            |  |-----------------------|--------------------------|-------------------------------|  |      push_front_t     |   <listOfTypes, float>   | Typelist<float, short, bool>  |  |-----------------------|--------------------------|-------------------------------| */namespace{template<typename List, typename NewElement>struct push_front;template<typename... List, typename NewElement>struct push_front<Typelist<List...>, NewElement> {  using type = Typelist<NewElement, List...>;};}template<typename List, typename NewElement>using push_front_t = typename push_front<List, NewElement>::type;/*------------------------------End of Push_Front---------------------------------*//*----------------------------------Push_Back---------------------------------------  Description:  Push new value to back of the list  using listOfValues = Valuelist<1,2,3,4,5,6>;  |-----------------------|--------------------------|-------------------------------|  |      Trait            |        Parameters        |             Result            |  |-----------------------|--------------------------|-------------------------------|  |   push_back_value_t   |     <listOfValues, 0>    |    Valuelist<1,2,3,4,5,6,0>   |  |-----------------------|--------------------------|-------------------------------| */namespace{template<typename List, auto NewElement>struct push_back_value;template<auto... List, auto NewElement>struct push_back_value<Valuelist<List...>, NewElement>{  using type = Valuelist<List..., NewElement>;};}template<typename List, auto NewElement>using push_back_value_t = typename push_back_value<List, NewElement>::type;/*----------------------------------End of Push_Back------------------------------*//*-----------------------------------Is_Empty---------------------------------------  Description:  Check parameters list for empty and return bool value  using listOfTypes = Typelist<int, short, bool, unsigned>;  using listOfValues = Valuelist<>;  |-------------------------|--------------------|----------|  |          Trait          |     Parameters     |  Result  |  |-------------------------|--------------------|----------|  |        is_empty_v       |    <listOfTypes>   |  false   |  |-------------------------|--------------------|----------|  |        is_empty_v       |   <listOfValues>   |   true   |  |-------------------------|--------------------|----------| */namespace{/*!  @brief Check the emptiness of the types in parameters.   \n     E.g.: is_empty<int, short, bool>::value;*/ template<typename List>struct is_empty{    static constexpr auto value = false;};/*!  @brief Check the emptiness of the types in parameter. Specializatio for empty parameters   \n     E.g.: is_empty<>::value;*/ template<>struct is_empty<Typelist<>>{    static constexpr auto value = true;};template<>struct is_empty<Valuelist<>>{    static constexpr auto value = true;};}/*!  @brief Check the emptiness of the types-list in parameter.   \n     E.g.: using list = Typelist<int, short, bool>; is_empty_v<list>;*/ template<typename List>static constexpr auto is_empty_v = is_empty<List>::value;/*--------------------------------End of Is_Empty---------------------------------*//*---------------------------------Size_Of_List-------------------------------------  Description:  Return number of elements in list  using listOfTypes = Typelist<int, float, double, bool>;  |------------------|--------------------|----------|  |       Trait      |     Parameters     |  Result  |  |------------------|--------------------|----------|  |  size_of_list_v  |     listOfTypes    |    4     |  |------------------|--------------------|----------| */namespace{template<typename List, std::size_t count = 0U>struct size_of_list : public size_of_list<pop_front_t<List>, count + 1>{};template<std::size_t count>struct size_of_list<Typelist<>, count>{  static constexpr std::size_t value = count;};template<std::size_t count>struct size_of_list<Valuelist<>, count>{  static constexpr std::size_t value = count;};}template<typename List>static constexpr std::size_t size_of_list_v = size_of_list<List>::value;/*-------------------------------End Size_Of_List---------------------------------*//*---------------------------------Lists Operation--------------------------------*/  /*Description: Operations with lists of values  using list1 = Valuelist<1, 4, 8, 16>;  using list2 = Valuelist<1, 5, 96, 17>;  |------------------------------|-------------------|---------------------------|  |               Trait          |    Parameters     |           Result          |  |------------------------------|-------------------|---------------------------|  |     lists_termwise_and_t     |  <list1, list2>   |  Valuelist<1, 4, 0, 16>   |  |------------------------------|-------------------|---------------------------|  |     lists_termwise_or_t      |  <list1, list2>   |  Valuelist<1, 5, 104, 17> |  |---------------------------- -|-------------------|---------------------------|  |     lists_termwise_xor_t     |  <list1, list2>   |  Valuelist<0, 1, 104, 1>  |  |------------------------------|-------------------|---------------------------| */namespace{template<template <auto value1, auto value2> typename operation,          typename List1, typename List2, typename Result = Valuelist<>>struct operation_2_termwise_valuelists{  constexpr static auto newValue = operation<front_v<List1>, front_v<List2>>::value;  using nextList1 = pop_front_t<List1>;  using nextList2 = pop_front_t<List2>;      using result = push_back_value_t<Result, newValue>;  using type = typename       operation_2_termwise_valuelists<operation, nextList1, nextList2, result>::type;};template<template <auto value1, auto value2> typename operation, typename Result>struct operation_2_termwise_valuelists<operation, Valuelist<>, Valuelist<>, Result>{  using type = Result;};template<template <auto value1, auto value2> typename operation,          typename List2, typename Result>struct operation_2_termwise_valuelists<operation, Valuelist<>, List2, Result>{  using type = typename       operation_2_termwise_valuelists<operation, Valuelist<0>, List2, Result>::type;};template<template <auto value1, auto value2> typename operation,          typename List1, typename Result>struct operation_2_termwise_valuelists<operation, List1, Valuelist<>, Result>{  using type = typename       operation_2_termwise_valuelists<operation, List1, Valuelist<0>, Result>::type;};template<template<typename first, typename second> class operation,         typename Lists, bool isEnd = size_of_list_v<Lists> == 1>class lists_operation{  using first = front_t<Lists>;  using second = front_t<pop_front_t<Lists>>;  using next = pop_front_t<pop_front_t<Lists>>;  using result = operation<first, second>;public:  using type = typename lists_operation<operation, push_front_t<next, result>>::type;};template<template<typename first, typename second> class operation,         typename Lists>class lists_operation<operation, Lists, true>{public:  using type = front_t<Lists>;};template<auto value1, auto value2>struct and_operation{ static constexpr auto value = value1 & value2;};template<auto value1, auto value2>struct or_operation{ static constexpr auto value = value1 | value2;};template<auto value1, auto value2>struct xor_operation{ static constexpr auto value = value1 ^ value2;};template<typename List1, typename List2>using operation_and_termwise_t = typename     operation_2_termwise_valuelists<and_operation, List1, List2>::type;template<typename List1, typename List2>using operation_or_termwise_t = typename     operation_2_termwise_valuelists<or_operation, List1, List2>::type;template<typename List1, typename List2>using operation_xor_termwise_t = typename     operation_2_termwise_valuelists<xor_operation, List1, List2>::type;}template<typename... Lists>using lists_termwise_and_t =     typename lists_operation<operation_and_termwise_t, Typelist<Lists...>>::type;template<typename... Lists>using lists_termwise_or_t = typename     lists_operation<operation_or_termwise_t, Typelist<Lists...>>::type;template<typename... Lists>using lists_termwise_xor_t = typename     lists_operation<operation_xor_termwise_t, Typelist<Lists...>>::type;/*--------------------------------End of Lists Operation----------------------------*/} // !namespace utils#endif //!_TYPE_TRAITS_CUSTOM_HPP



IPower.hpp
#ifndef _IPOWER_HPP#define _IPOWER_HPP#include "type_traits_custom.hpp"#define __FORCE_INLINE __attribute__((always_inline)) inline/*!  @brief Controller's peripherals interfaces*/namespace controller::interfaces{/*!  @brief Interface for Power(Clock control). Static class. CRT pattern  @tparam <adapter> class of specific controller*/template<typename adapter>  class IPower{  IPower() = delete;public:  /*!    @brief Enables peripherals Power(Clock)    @tparam <Peripherals> list of peripherals with trait 'power'  */  template<typename... Peripherals>  __FORCE_INLINE static void Enable(){    using tEnableList = utils::lists_termwise_or_t<typename Peripherals::power...>;    using tDisableList = typename adapter::template fromValues<>::power;   adapter:: template _Set<tEnableList, tDisableList>();  }  /*!    @brief Enables Power(Clock) except listed peripherals in 'ExceptList'.       If Enable = Exception = 1, then Enable = 0, otherwise depends on Enable.    @tparam <EnableList> list to enable, with trait 'power'    @tparam <ExceptList> list of exception, with trait 'power'  */  template<typename EnableList, typename ExceptList>  __FORCE_INLINE static void EnableExcept(){    using tXORedList = utils::lists_termwise_xor_t<typename EnableList::power, typename ExceptList::power>;    using tEnableList = utils::lists_termwise_and_t<typename EnableList::power, tXORedList>;    using tDisableList = typename adapter::template fromValues<>::power;    adapter:: template _Set<tEnableList, tDisableList>();  }  /*!    @brief Disables peripherals Power(Clock)    @tparam <Peripherals> list of peripherals with trait 'power'  */  template<typename... Peripherals>  __FORCE_INLINE static void Disable(){    using tDisableList = utils::lists_termwise_or_t<typename Peripherals::power...>;    using tEnableList = typename adapter::template fromValues<>::power;    adapter:: template _Set<tEnableList, tDisableList>();  }  /*!    @brief Disables Power(Clock) except listed peripherals in 'ExceptList'.       If Disable = Exception = 1, then Disable = 0, otherwise depends on Disable.    @tparam <DisableList> list to disable, with trait 'power'    @tparam <ExceptList> list of exception, with trait 'power'  */  template<typename DisableList, typename ExceptList>  __FORCE_INLINE static void DisableExcept(){    using tXORedList = utils::lists_termwise_xor_t<typename DisableList::power, typename ExceptList::power>;    using tDisableList = utils::lists_termwise_and_t<typename DisableList::power, tXORedList>;    using tEnableList = typename adapter::template fromValues<>::power;    adapter:: template _Set<tEnableList, tDisableList>();  }  /*!    @brief Disable and Enables Power(Clock) depends on values.       If Enable = Disable = 1, then Enable = Disable = 0, otherwise depends on values    @tparam <EnableList> list to enable, with trait 'power'    @tparam <DisableList> list to disable, with trait 'power'  */  template<typename EnableList, typename DisableList>  __FORCE_INLINE static void Keep(){    using tXORedList = utils::lists_termwise_xor_t<typename EnableList::power, typename DisableList::power>;    using tEnableList = utils::lists_termwise_and_t<typename EnableList::power, tXORedList>;    using tDisableList = utils::lists_termwise_and_t<typename DisableList::power, tXORedList>;    adapter:: template _Set<tEnableList, tDisableList>();  }  /*!    @brief Creates custom 'power' list from peripherals. Peripheral driver should implement 'power' trait.      E.g.: using power = Power::makeFromValues<1, 512, 8>::power;     @tparam <PeripheralsList> list of peripherals with trait 'power'  */ template<typename... PeripheralsList>  class fromPeripherals{    fromPeripherals() = delete;    using power = utils::lists_termwise_or_t<typename PeripheralsList::power...>;    friend class IPower<adapter>;  };};} // !namespace controller::interfaces#undef   __FORCE_INLINE#endif // !_IPOWER_HPP



HPower.hpp
#ifndef _HPOWER_HPP#define _HPOWER_HPP#include "type_traits_custom.hpp"#define __FORCE_INLINE __attribute__((always_inline)) inline/*!  @brief Hardware operations*/namespace controller::hardware{/*!  @brief Implements hardware operations with Power(Clock) registers*/class HPower{  HPower() = delete;protected:/*!  @brief Set or Reset bits in the registers  @tparam <SetList> list of values to set   @tparam <ResetList> list of values to reset  @tparam <AddressesList> list of registers addresses to operate*/  template<typename SetList, typename ResetList, typename AddressesList>  __FORCE_INLINE static void ModifyRegisters(){    using namespace utils;    if constexpr (!is_empty_v<SetList> && !is_empty_v<ResetList> &&   !is_empty_v<AddressesList>){      constexpr auto valueSet = front_v<SetList>;      constexpr auto valueReset = front_v<ResetList>;      if constexpr(valueSet || valueReset){        constexpr auto address = front_v<AddressesList>;                  using pRegister_t = volatile std::remove_const_t<decltype(address)>* const;        auto& reg = *reinterpret_cast<pRegister_t>(address);        reg = (reg &(~valueReset)) | valueSet;      }              using tRestSet = pop_front_t<SetList>;      using tRestReset = pop_front_t<ResetList>;      using tRestAddress = pop_front_t<AddressesList>;            ModifyRegisters<tRestSet, tRestReset, tRestAddress>();    }  };};} // !namespace controller::hardware#undef __FORCE_INLINE#endif // !_HPOWER_HPP



stm32f1_Power.hpp
#ifndef _STM32F1_POWER_HPP#define _STM32F1_POWER_HPP#include <cstdint>#include "IPower.hpp"#include "HPower.hpp"#include "type_traits_custom.hpp"#define __FORCE_INLINE __attribute__((always_inline)) inline/*!  @brief Controller's peripherals*/namespace controller{/*!  @brief Power managment for controller*/class Power: public interfaces::IPower<Power>, public hardware::HPower{  Power() = delete;public:  /*!    @brief Creates custom 'power' list from values. Peripheral driver should implement 'power' trait.      E.g.: using power = Power::fromValues<1, 512, 8>::power;     @tparam <valueAHB=0> value for AHBENR register    @tparam <valueAPB1=0> value for APB1ENR register    @tparam <valueAPB2=0> value for APB1ENR register  */  template<uint32_t valueAHBENR = 0, uint32_t valueAPB1ENR = 0, uint32_t valueAPB2ENR = 0>  struct fromValues{    fromValues() = delete;    using power = utils::Valuelist<valueAHBENR, valueAPB1ENR, valueAPB2ENR>;  };private:   static constexpr uint32_t     _addressAHBENR  = 0x40021014,    _addressAPB2ENR = 0x40021018,    _addressAPB1ENR = 0x4002101C;    using AddressesList = utils::Valuelist<_addressAHBENR, _addressAPB1ENR, _addressAPB2ENR>;  template<typename EnableList, typename DisableList>  __FORCE_INLINE static void _Set(){    HPower:: template ModifyRegisters<EnableList, DisableList, AddressesList>();  }  friend class IPower<Power>;};} // !namespace controller#undef __FORCE_INLINE#endif // !_STM32F1_POWER_HPP



stm32f1_SPI.hpp
#ifndef _STM32F1_SPI_HPP#define _STM32F1_SPI_HPP#include "stm32f1_Power.hpp"namespace controller{template<auto baseAddress>class SPI{  static const uint32_t RCC_AHBENR_DMA1EN = 1;  static const uint32_t RCC_APB2ENR_IOPBEN = 8;  static const uint32_t RCC_APB1ENR_SPI2EN = 0x4000;  /*!    @brief Trait for using in Power class. Consists of Valueslist with      values for AHBENR, APB1ENR, APB2ENR registers   */  using power = Power::fromValues<           RCC_AHBENR_DMA1EN,           RCC_APB1ENR_SPI2EN,            RCC_APB2ENR_IOPBEN>::power;  template<typename>  friend class interfaces::IPower;};}#endif // !_STM32F1_SPI_HPP



stm32f1_UART.hpp
#ifndef _STM32F1_UART_HPP#define _STM32F1_UART_HPP#include "stm32f1_Power.hpp"namespace controller{template<auto baseAddress>class UART{  static const uint32_t RCC_AHBENR_DMA1EN = 1;  static const uint32_t RCC_APB2ENR_IOPAEN = 4;  static const uint32_t RCC_APB2ENR_USART1EN = 0x4000;  /*!    @brief Trait for using in Power class. Consists of Valueslist with      values for AHBENR, APB1ENR, APB2ENR registers   */  using power = Power::fromValues<           RCC_AHBENR_DMA1EN,           0U,            RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN>::power;  template<typename>  friend class interfaces::IPower;};}#endif // !_STM32F1_UART_HPP



main.cpp
#include "stm32f1_Power.hpp"#include "stm32f1_UART.hpp"#include "stm32f1_SPI.hpp"using namespace controller;using spi = SPI<2>;using uart = UART<1>;using listPowerInit = Power::fromPeripherals<spi, uart>;using listPowerDown = Power::fromPeripherals<spi>;using listPowerWake = Power::fromPeripherals<uart>;int main(){  Power::Enable<listPowerInit>();  //Some code  Power::DisableExcept<listPowerDown, listPowerWake>();  //Sleep();  Power::EnableExcept<listPowerDown, listPowerWake>();  while(1);  return 1;};



Github
Подробнее..

Худшие места в C для написания кода

28.05.2021 12:18:53 | Автор: admin

Любой язык программирования, а особенно C++, предоставляет всевозможные средства для написания кода. В начале карьеры любому программисту кажется, что для того, чтобы мастерски овладеть языком, надо использовать по максимуму его возможностей. Но так ли это? Может, лучше наоборот, ограничиться необходимым минимумом средств, и не использовать сложные, подверженные ошибками конструкции? Давайте посмотрим на те возможности C++, где код нужно стараться не писать вовсе.

Шаблоны

Думаю, каждый разработчик на C++ сталкивался с нечитаемой простыней шаблонного кода. Шаблоны сложно писать, ошибки, возникающие при провале компиляции шаблонов, разрастаются на десятки экранов, а стоит отвлечься хотябы на неделю от поддержки шаблонного куска кода, как вспомнить хотябы примерно, что там происходит, за разумное время практически невозможно.

Вместе с этим, можно вспомнить, что все распространенные промышленные языки программирования, в том числе C++, являются тьюринг-полными, что означает, что на любом языке можно реализовать любую программу. Зачем же тогда нужны шаблоны?

Мой ответ такой, что шаблоны нужны для упрощения работы программиста, взаимодействующего с вашим кодом. То есть у программиста должен быть вариант заиспользовать ваш код без влезания в шаблонные дерби, возможно с небольшими правками (здесь идет речь о поддержке кода внутри одного проекта, с библиотечным кодом ситуация может быть сложнее). Причем у программиста должна быть возможность легко избавиться от шаблонов как в одном определенном месте, так и во всем проекте целиком. Таким образом, если шаблонный код действительно дает какие-то преимущества над нешаблонным, пользователь выберет именно его, и в любой момент он сможет избавиться от шаблонов, оставив основную функциональность без изменений.

Например, однажды мне был нужен класс, похожий на std::tuple, для того чтобы разные компоненты программы могли обмениваться экземплярами класса друг с другом. Например, компоненты c1 и c2 создают набор элементов A1, A2, A3 и B1, B2, B3 соответственно, а функции f1 и f2 их потребляют

void f1(const tuple<A1, A2, A3>& t);void f2(const tuple<B1, B2, B3>& t1, const tuple<A1, A2, A3>& t2);void main() {  //...  tuple<A1, A2, A3> t1 = c1.get();  tuple<B1, B2, B3> t2 = c2.get();    f1(t1);  f2(t2, t1);}

Но, как правило, потребляющим функциям бывают нужны не все элементы, которые производят компоненты. Например, функции f2 могут быть нужны элементы A1 и A3, и не нужен элемент A2. Зачем же тогда его указывать в заголовке функции?

Давайте в таком случае создадим обертку над классом tuple, которая будет "отсекать" лишние элементы

Tuple
// Не стоит бездумно использовать этот класс, так как он проектировался с учетом локальной спецификиtemplate<typename T0, typename ...T>class Tuple {private:    template<typename Arg>    struct ChooseTagType{        using type = typename std::conditional<IsTuple<Arg>(), TupleChoiseTag, typename std::conditional<IsPair<Arg>(), PairChoiseTag, OtherChoiseTag>::type>::type;    };    template<typename T1, typename Arg>    static T1 getImpl(OtherChoiseTag, Arg &&arg) {        return arg;    }    template<typename T1, typename ...Args>    static T1 getImpl(PairChoiseTag, const std::pair<Args...> &arg) {        return std::get<T1>(arg);    }    template<typename T1, typename ...Args>    static T1 getImpl(TupleChoiseTag, const std::tuple<Args...> &arg) {        return std::get<T1>(arg);    }    template<typename T1, typename Arg>    static T1 get(Arg &&arg) {        const typename ChooseTagType<Arg>::type t;        return getImpl<T1>(t, std::forward<Arg>(arg));    }    template<typename StdTuple, typename M>    static auto makeLambda(StdTuple stdTuple) {        return [stdTuple]() -> M& {            return *get<M*>(stdTuple);        };    }public:    template<typename StdTuple>    Tuple(StdTuple stdTuple) {        getter = std::make_tuple(            makeLambda<StdTuple, T0>(stdTuple),            makeLambda<StdTuple, T>(stdTuple)...        );    }    template<bool B = true, typename Dummy = typename std::enable_if<sizeof...(T) == 0 && B>::type>    T0& get() {        return std::get<0>(getter)();    }    template<typename M>    M& get() {        return std::get<std::function<M&()>>(getter)();    }private:    std::tuple<std::function<T0&()>, std::function<T&()>...> getter;};

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

Использование этого класса будет выглядеть следующим образом

void f1(const Tuple<A1, A2, A3>& t);void f2(const Tuple<B1, B2, B3>& t1, const Tuple<A1, A3>& t2);void main() {  //...  tuple<A1, A2, A3> t1 = c1.get();  tuple<B1, B2, B3> t2 = c2.get();    f1(t1);  f2(t2, t1);}

В решаемой задаче предполагается, что классы элементы классов A*, B* будут уникальны во всех tuple, что значит, что например tuple<B1, B1, B2> нам никогда не встретится. Но что, если требования все же изменятся, и такое станет возможно? Или что, если в реализации класса Tuple есть неочевидная ошибка? Что если данный код откажется компилироваться под каким-то компилятором? В таких случаях всегда можно отказаться от использования данного класса, как в одном конкретном месте, так и во всех местах сразу, просто чуть подправив код. Таким образом, мы не завязываем пользователя на использование нашего класса, если наш класс действительно дает какие-то реальные преимущества, пользователь им воспользуется.

Конструктор

Казалось бы, конструктор - неотъемленный аттрибут C++. Что с ним может быть не так? Да много чего.

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

Также конструктору нельзя дать хорошее описательное имя, он же конструктор. Например, мы хотим распарсить какой-то класс из json

class A {public:  A(const string& json);};

Но что, если в дальнейшем нам потребуется распарсить не только json, но и xml? Как решить данную задачу? Передавать дополнительный булев параметр isJson?

class A {public:  A(const string& jsonOrXml, bool isJson);};

Эти и другие проблемы конструкторов можно прочитать в статье
http://personeltest.ru/aways/habr.com/ru/post/460831/

Отдельная боль случается, когда приходится писать код не просто в конструкторе, а в member initializer list конструктора. Этого можно добиться путем добавления модификатора const к членам класса и некоторыми другими способами. Чем плох initializer list конструктора, пояснять, надеюсь не нужно: помимо стандартных проблем с конструктором здесь возникает множество других проблем, таких как ужасный синтаксис перехвата исключений, зависимость порядка инициализации от порядка объявления полей в классе, невозможность доинициализировать поле позже в процессе инициализации.

Что делать? В решении этой проблемы помогут 2 подхода: фабричная функция (не путать с фабричным методом) и dependency injection.

Например, создать экземпляр класса из json можно так

class A {public:  A(int i, const string& s)    : i(i)    , s(s)  {}public:  static optinal<A> fromJson(const string& s) {    // parse s    if (!parsed) {      return nullopt;    }    return A(iField, sField);  }    static optional<A> fromXml(const string& s) {    // parse s    if (!parsed) {      return nullopt;    }    return A(iField, sField);  }  private:  int i;  string s;};

Тут у нас уже нет никаких препятствий ни в придумывании выразительного имени функции, ни в возврате удобного нам значения, ни других ограничений.

В случае же конструирования комплексных классов, состоящих из сложных классов-полей, нужно воспользоваться паттерном "Внедрение зависимости".

Деструктор

Еще более сложный и неустойчивый элемент по сравнению с конструктором, это конечно же, деструктор. Вдобавок к недостатком, присущим конструктору, деструктор имеет и свои собственные.

Во-первых, из деструктора нельзя вернуть результат выполнения никаким образом, даже через исключение.

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

Какое решение данной проблемы? К сожалению, избавиться от кода в деструкторе не так просто, как от кода в конструкторах. В каждом конкретном случае придется действовать уникальным образом. Но можно дать несколько советов.

Во-первых, следует разделять RAII и не-RAII деструкторы. Некоторые программисты ошибочно полагают, что если они написали деструктор, то они реализовали идиому RAII. Но это не так. RAII - это о захвате/освобождении ресурса, и только. Захватили ресурс "память" - отдали память через деструктор. Захватили ресурс "файловый дескриптор" - отдали его через деструктор. Причем отдаем мы дескриптор лишь с одной целью - чтобы в последствии мы могли захватить этот дескриптор вновь (ну и уменьшить счетчик активных дескрипторов тоже). Ни о каком сбросе буфера на диск здесь речи идти не будет.

Что же делать с деструкторами, которые не являются RAII? Здесь можно обратиться к языку rust и увидеть, что в нем нету требования на обязательный вызов деструктора. Деструктор может как вызываться при выходе из скоупа, так и не вызваться, если мы его вызов где-то раньше отменили. Я предлагаю действовать похожим образом. Давайте будем считать, что деструктор в C++ может не выполниться вовсе или выполниться только частично, и строить логику программы исходя из этого.

Например, мы хотим создать класс "BdTransaction", который будет управлять временем жизни транзакции. Какой код следует поместить в деструктор - код, который закоммитит созданную транзакцию или код, который отменит ее?

Код, который закоммитит транзакцию, включать в деструктор не стоит, так как в этом случае коммит транзакции будет происходить всегда, даже в случае выброса исключения в пользовательском коде, приводя данные в транзакции в несогласованное состояние. Но нас в данном случае интересует даже не это. Что, если в процессе попытки создания транзакции связь с базой данных прервется? Мы можем попробовать сделать несколько попыток восстановить связь, но эти попытки нам ничего не гарантируют. Таким образом, мы не можем даже сообщить никуда извне о нашем неуспехе, из-за чего пользователь, смотрящий на наш класс, будет считать, что подтвержение транзакции произойдет в любом случае, что очевидно не так.

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

Виртуальные (но не чисто виртуальные) методы

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

Заключение (редко получается удачным, поэтому оставлю пустым)

Подробнее..

АнтиBIMing

22.05.2021 20:11:20 | Автор: admin
image
Сама по себе автоматизация лишь инструмент и как каждый инструмент у нее есть своя область применения, своя техника безопасности внедрения и применения, а так же свои преимущества и негатив. Традиционно бизнес стремится внедряться IT-разработки там, где существуют достаточно высокая маржа, а значит проще получить прибыль и уменьшать издержки, однако существуют области в которых давно назрела необходимость что-нибудь внедрить с тем что бы упростить и тогда все сформируется. Речь о личном опыте решения таких задач при составлении исполнительной документации в строительстве.

Программа, в которой описываются основные понятия и определения встречающиеся в тексте
Состав ПСД. Приемо-сдаточная документация#1 делится на:
1. Разрешительная документация, включая ППР;
2. Исполнительная документация.

Вся структура приемо-сдаточной документации субподрядной организации по спецмонтажным работам будет выглядеть так:
Разрешительная документация#2 термин, используемый для обозначения документации, оформляемой в соответствии со статьями 45 51 Градостроительного кодекса РФ вплоть до получения разрешения на строительство (ст. 51 ГрКРФ), а также получение разрешения на ввод объекта в эксплуатацию (ст. 55 ГрКРФ).
Исполнительная документация (ИД)#2 представляет собой текстовые и графические материалы, отражающие фактическое исполнение проектных решений и фактическое положение объектов капитального строительства и их элементов в процессе строительства, реконструкции, капитального ремонта объектов капитального строительства по мере завершения определенных в проектной документации работ.

Исполнительная документация, оформленная соответствующим образом, является документом построенного здания или сооружения, облегчающим процесс эксплуатации, отражающим техническое состояние, дающим четкое представление об ответственных производителях работ по любому из видов выполненных работ.

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

АОСР Акт освидетельствования скрытых работ. Некоторые работы после окончания строительства невозможно проверить. Это касается работ, проведенных внутри строительных конструкций и коммуникаций, например:
установки арматуры в бетонных стенах,
грунтования поверхности перед окрашиванием,
толщины песочной подложки под брусчаткой,
укладки труб перед засыпанием грунтом,
гидро, звуко-, теплоизоляции и т.п.

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

Действие Первое. В котором рассказывается про предпосылки для автоматизации и проблемные рутины в Строительстве

Ты что делаешь?
Анекдоты читаю.
А отчет?
Час назад уже у тебя на столе лежит.
Погоди, тогда почему твой предшественник на его подготовку тратил три часа?
Послушай, я тоже могу тратить три часа на его подготовку. Если хочешь, я могу читать анекдоты в столовой. Но результат будет тот же.
Структура договорных отношений между участниками строительства #1
Обычно Инвестор нанимает организацию, занимающуюся управлением строительства, она может называться заказчиком. Этот заказчик нанимает проектный институт (лицо, осуществляющее подготовку проектной документации), чтобы тот ему нарисовал проект, бывает, так же нанимает генпроектировщика, а тот нанимает субчиков. Потом играют в тендер (кстати, то же самое может быть и с институтом) и выбирают генподрядчика это ответственный за строительную площадку (лицо, осуществляющее строительство) и заключает с ним договор. Для заказчика существует только генподрядчик (подрядчик) так как им так легче и удобней работать. Генподрядчик уже без тендера выбирает себе субподрядчиков (лицо, выполняющее работы), обычно по видам работ и заключает с ними договора. Субподрядчик или даже сам генподрядчик так же часто себе набирает субчиков, но уже не официально как бы под своим флагом. Заказчик нанимает технический надзор или сам может выполнять данную функцию (представитель заказчика или технический надзор заказчика). Если объект подпадает под государственный строительный надзор (ГСН), то и следит за всем этим он в виде инспекторов, их уведомляет заказчик о начале строительства, те приезжают со своей инспекцией, пишут замечания и уезжают. Все отношения регулируются договорами и действующим законодательством.

Строительство ведется по рабочим чертежам (заверенных печатью В производство работ) теми материалами и оборудованием, что договорились, их может поставлять как заказчик с генподрядчиком, так и субподрядчик. Строительство ведется в определенные сроки по графику. Каждый месяц Заказчик платит Подярдчику, а Позрядчик Субподрядчику и т.д. по цепочке, за построенные объемы работ денежку, из которой в т.ч. платится зарплата, а представители Подрядчика/Судподрядчика отчитываются за материалы и оборудование, которые были переданы (все это обговорено в договоре). Проверяет объемы и качество выполненных работ технический надзор, авторский надзор так же может следить за соответствием выполняемых работ по проекту и рассматривает возможность изменений в проекте (если с институтом был подписан на это договор и на опасном производственном объекте он должен быть по любому).

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

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

При производстве работ по строительству объекта ведется всевозможная документация, основной и самой сложной частью из которой является исполнительная.


Согласно Википедии
BIM (англ. Building Information Model или Modeling) информационная модель (или моделирование) зданий и сооружений, под которыми в широком смысле понимают любые объекты инфраструктуры, например инженерные сети (водные, газовые, электрические, канализационные, коммуникационные), дороги, железные дороги, мосты, порты и тоннели и т. д.

Информационное моделирование здания это подход к возведению, оснащению, эксплуатации и ремонту (а также сносу) здания (к управлению жизненным циклом объекта), который предполагает сбор и комплексную обработку в процессе проектирования всей архитектурно-конструкторской, технологической, экономической и иной информации о здании со всеми её взаимосвязями и зависимостями, когда здание и всё, что имеет к нему отношение, рассматриваются как единый объект.

Трёхмерная модель здания, либо другого строительного объекта, связанная с базой данных, в которой каждому элементу модели можно присвоить все необходимые атрибуты. Особенность такого подхода заключается в том, что строительный объект проектируется фактически как единое целое: изменение какого-либо из его параметров влечёт за собой автоматическое изменение связанных с ним параметров и объектов, вплоть до чертежей, визуализаций, спецификаций и календарного графика.

Что касается документации и информационной модели на стройке и откуда она там берется. Как правило Заказчик передает Подрядчику проект со штампом В производство работ на бумажном носителе (очень редко в формате pdf и почти никогда в dwg) для того что бы последний в соответствии с контрактом за оговоренную сумму произвел некоторые работы. Прораб/мастер/нач.участка заказывает через снабженцев (привет бухгалтерия и 1С) материалы согласно потребностям, проекту и графику производства работ, нанимается техника, она же ремонтируется, под нее покупается ГСМ и прочие расходы связанные с объектом, часть из которых связана с машинами и механизмами, часть с материалами которые будут монтироваться. Затем на объекте ведутся на бумажном носителе: журнал входного контроля, общий журнал работ и прочие специализированные журналы которые зависят от видов выполняемых работ. К концу каждого операционного цикла подготавливается исполнительная документация, которая представляет из себя акты и схемы выполненных работ (схемы по сути копируют проект, ибо отступление от проекта, без согласования с Заказчиком и контролирующими органами, недопустимо и будет означать лишь проблемы для Подрядчика). Такие акты и схемы на бумажном носителе подписываются представителями Подрядчика, Заказчика, контролирующих органов и организаций и только после того как пройдет успешная защита составляются финансовые акты (обычно по форме КС-2 и КС-3, но это не обязательно, достаточно к договору приложить свой шаблон), на основании которых в особо упоротых случаях бухгалтерия Заказчика может позволить списать материалы бухгалтерии Подрядчика (помимо актов выполненных работ составляются так же акты входного контроля и все вместе это передается Заказчику) в соответствии со сметными расценками.

Сегодня, в отличие от СССР, прораб/мастер/нач.участка не составляют исполнительную документацию. Это не означает что они не заполняют и не составляют бумаг, просто они другие, больше связаны с непосредственной организации управленческих процессов (открытие и закрытие нарядов, журналы инструктажа, выдачи заданий, заявки, письма и т.п.) объем бумаг достаточно большой и это нормальная (в том плане, что распространенная практика) брать в штат сотрудников с высшим (!) инженерным образованием инженеров ПТО, которые будут заниматься всей остальной документацией, а проще говоря исполнять работу технического секретаря. (На самом деле порог вхождения в процессию очень низок, т.к. базового школьного курса Черчения достаточно, что бы читать строительные чертежи и даже перерисовывать схемы, конечно потребуется навык работы с Word/Excel/Paint/AutoCAD/Компас, но это не так сложно как может показаться и потому такая специальность утилизирует людей как с профильным образованием, так и с гуманитарным менеджеров/юристов/учителей и т.д. и т.п.)

Как правило рабочее место, которое может быть и удалено (вагончик в поле), оборудовано МФУ, Wi-Fi точкой, раздающей 3G интернет, ноутбуком. В отсутствие сис.админа все это работает в меру сил и понимания инженера ПТО, который за все это отвечает, который выполняет не только прямые обязанности, но и те от которых не удалось отбрыкаться по разным причинам. Надо ли говорить, что общая техническая грамотность страдает. Обычно на ноутбуки установлен, хорошо если заботливо, Windows, MS Office, редактор для векторной графики, GIMP, программа оптического распознавания текстов. Такой скудный выбор связан с тем, что з/п и оснащение такого инженера находится в составе Накладных Расходов, а не в статьях Общей Заработной Платы, как в случае, например, рабочих, т.е. разные статьи расценок сметы.

Действие Второе. В котором рассказывается про предпосылки для автоматизации и проблемные рутины в Строительстве

Исаак Ньютон:
От флюенции возьму флюксию и обратно.
Лейбниц:
Могу делать то же самое!

Идея создания Программы родилась спонтанно, после 3-х закрытых объектов в 2016году ПАО Транснефть. Помимо сбора и компоновки информации большой блок времени отнимали задачи, связанные с банальным заполнением документов по шаблону, среди которых преобладали Акты входного контроля и Акты освидетельствования скрытых работ. Особенно много времени уходило на проверки в случае описок или различного рода неточностей. Т.к. если они выявлялись, то приходилось заново открывать и проверять такие акты. Иногда, как в ситуации в 2018году, когда Ростехнадзор поменял форму актов скрытых работ, их счет шел на десятки. Но так родилась идея: А что, если я соберу все данные, необходимые для заполнения актов, в таблицу, а уже Программа будет прописывать их в шаблоны за меня?.

Самой простой и пригодной из доступных для этого является MS Office с макросами VBA. Учитывая тот факт, что в 90-е годы я в школе ударно изучал QBasic 4.5 и Borland Pascal 7.0, то выбор платформы оказался более чем очевиден. Пробелы в синтаксисе помог закрыть Гугл. Сама Идея не нова, но в 2016-м году в открытом доступе, так сказать в open source, я нашел только один вариант через Слияние, который тогда, в далеком 2016-м году меня не устроил. И вот я начал разрабатывать свой велосипед:

1. Самое главное и без чего не имело все дальнейшее смысл это без наглядности и удобства в работе. Поэтому для варианта с экспортом в Excel был выбран путь перебора текста в ячейках с целью поиска комбинаций текстовых маркеров, которые априори не встречаются в русскоязычных регламентных формах актов, с последующей авто подстановкой значений из таблицы. (Например, f1, d3, b8 и т.д. и т.п.) Для того что бы не пришлось перебирать всю матрицу я ввел упрощение, при создании шаблона если в первой ячейке за областью печати располагается символ арабской единицы, то только в этом случае макрос ищет текст во всех ячейках этой строки. Позднее я решил вопрос как получить в макрос диапазон ячеек, отправляемых на печать.

В случае с экспортом в World тут все гораздо проще Закладки и ссылки на содержимое закладок, при повторном упоминании содержимого в тексте.

2. В отличие от многих конкурентов с более проработанными приложениями я пошел дорогой структурирования информации (привет BIM) и наглядного представления данных, а потому, не смотря на то что тот же Access, Visual Studio, 1С и т.п. предоставляет большие возможности и функционал все эти программы грешат тем, что в них нельзя протянуть строку или столбец с одинаковыми данными, а переключение между полями ввода требует большей точности при позиционировании (выбор поля через TAB или позиционирование курсора с кликом проигрывает в удобстве перемещению стрелками по ячейкам таблицы, не говоря про то, что копировать ячейками проще, чем из через выделения текста из поля ввода)

Следующий шаг это формирование логики заполнения данных, т.е.:

А) Данные организаций и участников строительства, а также общие характеристики объекта;

Б) Данные для формирования Актов входного контроля, т.е. в первую очередь определяемся не с работами, а с материалами

В) Затем определяемся с месячно-суточным графиком, в котором делаем привязку не только к сметам (т.е. сперва вводим сметные расценки в Заголовки), а уже затем формируем перечень работ из ВР (Ведомости объемов работ по РД (Рабочей документации)), соблюдая очередность работ. Т.е. алгоритм после проставления в графике объемов работ сам определит, в рамках каждого отчетного периода, какие Акты скрытых работ и в какой последовательности под каким номером, от каких дат необходимо будет оформить. Пользователю останется лишь внести сопутствующую информацию, которая является больше рутиной. Кроме того, благодаря связям Пользователь может видеть сколько материалов было списано по предыдущим Актам скрытых работ, что

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

И вот таким образом ПК/ноутбук превращаются из печатной машинки в помощника, который берет часть обязанностей на себя. Т.е. то, что часто многими упускается из виду при создании программ.

3. Но ведь регламентных форм актов сильно больше чем указанных мною выше 2-х шт., пусть они и представляют собой свыше 50% всех актов Исполнительной документации. Значит нужно создать, и это было сделано, аналогичную программу, которая могла бы заполнять таким же образом любой Пользовательский шаблон, а затем такие шаблоны можно было бы импортировать в основную программу, с тем что бы можно было увязывать текущие данные с новыми формами. А раз новые формы, то значит нужна и в меру удобная навигация. Все эти задачи были решены.

И вот теперь такая программа распространяется абсолютно безвозмездно, т.е. даром. Не смотря на долгий срок разработки одним человеком с длительными перерывами в нерабочее время с 2017-го года по 2021-й год, когда появились все эти Алтиусы, Адепты и прочий платный софт той же тематики я хочу сказать следующие слова критики:

1. Зачастую архитектурные ограничения методов ограничивают юзабилити пользователей. Потому что разработчик не является исполнителем, который в дальнейшем будет пользоваться этой программой, и не изучают технологические операции конкретной специальности, взамен формируя свои технологические-схемы работ с таким софтом, которые не всегда являются удобнее аналогов.

2. Пользователю, т.е. инженерам-ПТО, нужен такой продукт и помощь, который будет прост, удобен и понятен, а еще по совместительству предложит такую схему работы, ошибок при которой будет меньше, чем без нее и трудоемкость уменьшится.

3. Зачастую люди, которым нужна автоматизация, не могут за нее заплатить, т.к. их оклад не такой уж и большой, а в их опыте даже нет рабочих примеров, когда софт облегчал им рабочую рутины, да еще и уменьшал ИХ КОЛИЧЕСТВО ОШИБОК. В то время как цены на такие программы сравнимы со стоимостью Сметных-комплексов. Но без сметных комплексов очень трудно обойтись, а вот без автоматизации Исполнительной документации элементарно.

4. Информационную модель BIM в ходе формирования исполнительной документации приходится формировать заново, работать дешифратором Рабочей документации на бумажном носителе, переводить ее в язык цифровых сигналов с тем, чтобы выводить результат в виде оформленных актов на бумажном носителе, но с подписью. Прочувствуйте всю иронию.

Действие третье. В котором рассказывается о том как кристаллизовалась программа


На стройке самое важное что? График производства работ и ключевые даты на нем (врезка, подключение, начало работ и окончание работ и некоторые другие). На участке ведется ОЖР, в котором записывается вручную что было выполнено за каждый конкретный рабочий день. Но если взять график (Месячно-суточный график) и заполнять его, то мы получим графическое представление, который и легче воспринимается и, затем, легче автоматизируется, служа исходными данными для актов и аналитики.

Рис.1 Пример Месячно-суточного графика

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

Следующий Важный момент в строительстве это Материалы. Они должны соответствовать проекту и при их приемке осуществляется Входной контроль комиссией с оформлением бумаг (Акт входного контроля и Журнал Входного контроля). Это держим в уме. А дальше большая часть ИД в строительстве составляют Акты скрытых работ или полностью, Акт освидетельствования скрытых работ. Работы, их очередность и данные подтягиваем с Месячно-суточного графика, а материалы из перечня актов Входного контроля теперь еще и наглядно мониторить можно какие материалы в каком количестве списываются по актам АОСР.

А что если нам нужно иметь гибкость, что бы Пользователь добавлял свои формы актов, делал их вручную или использовал текущие? И это возможно, но обсудим мы это в

Действие четвертое. В котором речь пойдет о макросах VBA

Далее пойдут спойлеры с кодом, призванные решить те или иные вопросы/проблемы.
Немного ускоряем MS Excel при работе с макросами
'Ускоряем Excel путём отключения всего "тормозящего" Public Sub AccelerateExcel()   'Больше не обновляем страницы после каждого действия  Application.ScreenUpdating = False   'Расчёты переводим в ручной режим  Application.Calculation = xlCalculationManual   'Отключаем события  Application.EnableEvents = False   'Не отображаем границы ячеек  If Workbooks.Count Then      ActiveWorkbook.ActiveSheet.DisplayPageBreaks = False  End If   'Отключаем статусную строку  Application.DisplayStatusBar = False   'Отключаем сообщения Excel  Application.DisplayAlerts = False  End Sub

а теперь возвращаем настройки обратно
'Включаем всё то что выключили процедурой AccelerateExcelPublic Sub disAccelerateExcel()   'Включаем обновление экрана после каждого события  Application.ScreenUpdating = True   'Расчёты формул - снова в автоматическом режиме  Application.Calculation = xlCalculationAutomatic   'Включаем события  Application.EnableEvents = True   'Показываем границы ячеек  If Workbooks.Count Then      ActiveWorkbook.ActiveSheet.DisplayPageBreaks = True  End If   'Возвращаем статусную строку  Application.DisplayStatusBar = True   'Разрешаем сообшения Excel  Application.DisplayAlerts = True End Sub

Заполнение шаблонного файла в формате MS Excel
Здесь очень важна наглядность! Очень много проектов встали на абсолютных ссылках, по-этому я изначально решил сделать все универсально. После долгих мытарств я открыл для себя тот факт, что формулой можно получить диапазон ячеек, который будет выводится на печать.


Рис.2 Пример файла шаблона в формате MS Excel

Здесь в ячейке А1 содержится формула:
=СЦЕПИТЬ(АДРЕС(СТРОКА(Область_печати);СТОЛБЕЦ(Область_печати);1;1);":";АДРЕС(СТРОКА(Область_печати)+ЧСТРОК(Область_печати)-1;СТОЛБЕЦ(Область_печати)+ЧИСЛСТОЛБ(Область_печати)-1;1;1))
Т.е. мы можем получить область печати, обратившись к переменной, фигурирующей в диспетчере имен. Полученные абсолютные границы печати, которые будут автоматически меняться, если нам придется увеличить или уменьшить область печати. Зачем? Здесь следует сделать отступление.


Рис.3 Пример листа с хранящимися данными для автоматического заполнения актов.

Дело в том, что мною был выбран способ-маркеров в тексте, т.е. при составлении шаблона маркеры (a1, b0, c7, d8 и т.д. и т.п.) однозначно характеризуют с одной стороны строку, из которой будут браться данные (порядковый номер элемента массива, который автоматически завязан на номер строки), с другой стороны в русскоязычных шаблонах в строительстве абсурдное сочетание букв латиницы с цифрой не используется. А значит это наглядно. После чего обычный перебор текста решает, НО (!) чем больше ячеек в области печати, тем медленнее будет работать алгоритм. Значит ему надо помочь и подсветить только те строки, в которых априори что-то есть.
Код макроса VBA осуществляющий экспорт в шаблон в формате MS Excel
          With Workbooks.Open(ThisWorkbook.Path + "\Шаблоны аддонов\" + NameShablonPrimer, ReadOnly:=True)               .Sheets(NameShablonPrimerList).Visible = -1               .Sheets(NameShablonPrimerList).Copy after:=wb.Worksheets(Worksheets.Count)                              Let НачальныйНомерСтрокиВФайле = .Sheets(NameShablonPrimerList).Cells(1, 2)       ' Начальный номер строки в файле шаблона               Let НачальныйНомерСтолбцаВФайле = .Sheets(NameShablonPrimerList).Cells(1, 3)      ' Начальный номер столбца в файле шаблона               Let КонечныйНомерСтрокиВФайле = .Sheets(NameShablonPrimerList).Cells(1, 4)        ' Конечный номер строки в файле шаблона               Let КонечныйНомерСтолбцаВФайле = .Sheets(NameShablonPrimerList).Cells(1, 5)       ' Конечный номер столбца в файле шаблона                              .Close True          End With       End If    End If    Do       ИмяФайла = BDList + " "                                                                  ' Префикс имени файла       wb.Worksheets(NameShablonPrimerList).Copy after:=Worksheets(Worksheets.Count)       Set новыйЛист = wb.Worksheets(NameShablonPrimerList + " (2)")              For X = НачальныйНомерСтолбцаВФайле To КонечныйНомерСтолбцаВФайле Step 1                  ' Перебираем столбцы в листе Примера формы-шаблона           For Y = НачальныйНомерСтрокиВФайле To КонечныйНомерСтрокиВФайле Step 1                ' Перебираем строк в листе Примера формы-шаблона               If Sheets(новыйЛист.Name).Cells(Y, КонечныйНомерСтолбцаВФайле + 1) = 1 Then       ' При наличии спец символа проверяем на совпадении строку                  Let k = CStr(Sheets(новыйЛист.Name).Cells(Y, X))                               ' Ищем только если в ячейке что-то есть                  If k <> "" Then                     For i = 1 To Кол_воЭл_овМассиваДанных Step 1                         ContentString = CStr(Worksheets(BDList + " (2)").Cells(i + 1, НомерСтолбца))                         If Len(arrСсылкиДанных(i)) > 1 Then                            If ContentString = "-" Or ContentString = "0" Then ContentString = ""                            Let k = Replace(k, arrСсылкиДанных(i), ContentString)                         End If                     Next i                     новыйЛист.Cells(Y, X) = k                  End If               End If           Next Y       Next X                     For Y = НачальныйНомерСтрокиВФайле To КонечныйНомерСтрокиВФайле Step 1           Sheets(новыйЛист.Name).Cells(Y, КонечныйНомерСтолбцаВФайле + 1) = ""       Next Y            

Заполнение шаблонного файла в формате MS Word
вывода в шаблон формата Word, и здесь на самом деле есть 2 способа вывода текста:

1. Это через функционал закладок,
            Rem -= Открываем файл скопированного шаблона по новому пути и заполняем его=-            Set Wapp = CreateObject("word.Application"): Wapp.Visible = False            Set Wd = Wapp.Documents.Open(ИмяФайла)                        NameOfBookmark = arrСсылкиДанных(1)            ContentOfBookmark = Worksheets("Данные для проекта").Cells(3, 3)            On Error Resume Next            UpdateBookmarks Wd, NameOfBookmark, ContentOfBookmark            Dim ContentString As String            For i = 4 To Кол_воЭл_овМассиваДанных Step 1                If Len(arrСсылкиДанных(i)) > 1 Then                   NameOfBookmark = arrСсылкиДанных(i)                   ContentString = CStr(Worksheets("БД для АОСР (2)").Cells(i, НомерСтолбца))                   If ContentString = "-" Or ContentString = "0" Then ContentString = ""                   ContentOfBookmark = ContentString                   On Error Resume Next                   UpdateBookmarks Wd, NameOfBookmark, ContentOfBookmark                End If            Next i                         Rem -= Обновляем поля, что бы ссылки в документе Word так же обновились и приняли значение закладок, на которые ссылаются =-            Wd.Fields.Update                         Rem -= Сохраняем и закрываем файл =-            Wd.SaveAs Filename:=ИмяФайла, FileFormat:=wdFormatXMLDocument            Wd.Close False: Set Wd = Nothing

Sub UpdateBookmarks(ByRef Wd, ByVal NameOfBookmark As String, ByVal ContentOfBookmark As Variant)    On Error Resume Next    Dim oRng As Variant    Dim oBm    Set oBm = Wd.Bookmarks    Set oRng = oBm(NameOfBookmark).Range    oRng.Text = ContentOfBookmark    oBm.Add NameOfBookmark, oRngEnd Sub


2. Если рисовать таблицы средствами Word, то к ним можно обращаться с адресацией в ячейку
 Rem -= Заполняем данными таблицы ЖВК =-       Dim y, k As Integer       Let k = 1       For y = Worksheets("Титул").Cells(4, 4) To Worksheets("Титул").Cells(4, 5)           Wd.Tables(3).cell(k, 1).Range.Text = Worksheets("БД для входного контроля (2)").Cells(6, 4 + y)           Let k = k + 1       Next y       End With       


Между выводами в файлы форматов Word и Excel есть огромная пропасть, которая заключается в следующем:

Шаблон Excel требует перед использованием настроить отображение под конкретный принтер, т.к. фактическая область печати разнится от модели к модели. Так же перенос строки текста возможен, но только в пределах ячейки/объединенных ячеек. В последнем случае не будте автораздвигания строки, в случае переноса текста. Т.е. Вам вручную придется заранее определит границы области, которые будут содержать текст, который в свою очередь в них еще должен убраться. Зато Вы точно задали границы печати и выводимого текста и уверены, что не съедет информация (но не содержание) с одного листа на другой.

Шаблон Word при настройке автоматически переносит текст на последующую строку, если он не убрался по ширине ячейки/строки, однако этим самым он вызывает непрогнозируемый сдвиг текста по вертикали. Учитывая тот факт, что по требованиям к Исполнительной документации в строительстве ЗАПРЕЩЕНО один акт печатать на 2х и более листах, то это в свою очередь так же рождает проблемы.


С проектом можно ознакомиться тут:
vk.com/softpto
Все программы распространяются абсолютно бесплатно. Всем Добра! ;)
Подробнее..

Достучаться до небес, или FSM на шаблонах

05.02.2021 02:04:48 | Автор: admin

Здравствуйте! Меня зовут Александр, я работаю инженером-программистом микроконтроллеров.

Пишу на С/С++, причем предпочитаю плюсы, ибо верую в их эволюционную неизбежность в embedded.

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

Я стараюсь следовать этому очевидному посылу, благо небожители ведущие С++ программисты и консультанты щедро делятся своим опытом и идеями на разных площадках (например здесь, или здесь).

Некоторое время назад я посмотрел мощный доклад Сергея Федорова про построение конечного автомата с таблицей переходов на шаблонах.

Если внезапно: "а что такое конечный автомат?"

Конечный автомат, или FSM(finite state maсhine) - один из самых востребованных и популярных приемов в программировании на МК. В свое время за кратким и практическим руководством по готовке FSM я ходил заброшенные, земли.

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

впечатлила
// Transition table definitionusing transitions =  transition_table<  /*  State       Event       Next       */  tr< initial,    start,      running    >,  tr< running,    stop,       terminated >>;};// State machine objectusing minimal = state_machine<transitions>;minimal fsm;//...and then callfsm.process_event(start{});fsm.process_event(stop{});

А если добавить к этому перенос части функциональности кода в компайл тайм, заявленную автором потокобезопасность, улучшенные по сравнению с Boost::MSM выразительность, читаемость кода и скорость сборки, header only модель библиотеки, то - надо брать, решил я.

Вот только попытка собрать и запустить даже простейший пример на STM-ке закончилась матерком компилятора: "cannot use 'typeid' with "-fno-rtti" и "exception handling disabled".

Да, все так. Более того, помимо отключенной поддержки RTTI и исключений, у меня также выставлены флаги -fno-cxa-atexit, -fno-threadsafe-static. А еще в линкере применены настройки --specs=nano.specs (используем урезанную версию стандартной библиотеки с++ newlib-nano), --specs=nosys.specs (применяем легковесные заглушки для системных вызовов).

Зачем же таскать на себе вериги?

Embedded разработчикам хорошо известны особенности и ограничения при разработке встроенного ПО, а именно:

  • лимитированная память с недопустимостью фрагментации;

  • детерменированность времени выполнения;

  • штатно исполняющаяся программа никогда не выходит из main

С++ имеет в своем могучем арсенале средства и методы, неосторожное использование которых может войти в критическое противоречие с указанными выше условиями.

Как закружить в гармоничном танце С++ и bare metal отлично разъяснено у этого автора. Также порекомендую этот доклад.

Исходники проекта докладчика, включая зависимости, - это двадцать файлов со смертоноснейшей шаблонной магией. Перекроить их так, чтобы не юзать typeid и exceptions, простому смертному в моем лице - too much.

Делать нечего, поступимся принципами, включим поддержку RTTI, а вместо throw в исходниках автора проставим заглушки.

На этот раз все собралось. Вот только при использовании тулчейна gcc-arm-none-eabi-9-2020-q2-update и уровне оптимизации -O3, размер исполняемого файла превысил 200Кб.

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

И хотя старшие камни, например у STM, имеют на борту флеш от 1Мб, отдавать почти четверть только под конечный автомат, пусть и на закиси азота, как у докладчика, согласитесь, довольно расточительно.

Итак, с наскоку взять высоту не удалось, и я на некоторое время переключился на другие задачи. Но красота идеи меня не отпускала, и на днях я все-таки решился "достучаться до небес" - написать extra light embedded версию FSM из упомянутого доклада самостоятельно.

Уточню свои хотелки:

  • Оперировать состояниями, эвентами и действиями как пользовательскими типами.

  • Таблицу переходов реализовать в виде шаблонного параметра

  • Перетащить что возможно в компайл тайм

  • Асинхронно и атомарно постить эвенты

  • Переключать состояния за константное время

  • Выйти в итоге на приемлемый по меркам встроенного ПО размер кода

  • Повторить header only модель библиотеки

Забегая вперед, скажу, что в итоге что-то получилось и даже взлетело.

А вот как - не сочтите за труд, давайте посмотрим вместе. И не сдерживайте себя в метании тухлых яиц, ибо лучшей питательной среды для самосовершенстования трудно представить.

Первым делом опишем базовые сущности:

Состояние/State
struct StateBase{};template <base_t N, typename Action = void>struct State : StateBase{  static constexpr base_t idx = N;  using action_t = Action;  };

Здесь и далее base_t - платформозависимый тип, машинное слово. В моем случае это unsigned int.

Состояния пусть будут двух типов - пассивное, в котором никаких действий не происходит, и активное - при нахождении в котором будет выполнятся переданный шаблонным параметром функтор, action_t.

Цель статического члена idx уточню далее по тексту.

Событие/Event
struct EventBase{};template <base_t N>struct Event : EventBase{  static constexpr base_t idx = N;};

Элементарная структура, все ясно.

Действие при наступлении события и смене состояний:

Action
struct action{  void operator()(void){    // do something};

Безусловно, сигнатура operator() может и должна варьироваться от задач приложения, пока же для упрощения остановимся на самом легковесном варианте.

Сторож состояния:

Guard
enum class Guard : base_t{  OFF,  CONDITION_1,  CONDITION_2,  //etc.};

Идея сторожа - допустить переход в новое состояние, только если в данный момент выполнения программы текущее значение сторожа соответствует заданному пользователем значению в типе перехода/transition-a. Если такого соответствия нет, то переход в новое состояние не происходит. Но тут возможны варианты. Например, все же переходить, но не выполнять действие, переданное в состояние. Up to you.

Итак, пока все тривиально. Идем дальше.

Переход:

Transition
struct TrBase{};template <typename Source,          typename Event,          typename Target,          typename Action,          Guard G,          class =          std::enable_if_t<std::is_base_of_v<StateBase, Source>&&          std::is_base_of_v<EventBase, Event> &&          std::is_base_of_v<StateBase, Target>>          >  struct Tr : TrBase{  using source_t = Source;  using event_t  = Event;  using target_t = Target;  using action_t = Action;    static constexpr Guard guard = G;};

Структура Tr тоже элементарна. Она параметризуется типом исходного состояния - Source, типом события Event, по наступлению которого произойдет переход в целевое состояние Target, и типом Guard.

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

Таблица переходов:

Transition table
struct TransitionTableBase{};template<typename... T>struct TransitionTable : TransitionTableBase{    using test_t = typename NoDuplicates<Collection<T...>>::Result;    static_assert(std::is_same_v<test_t, Collection<T...>>,                "Repeated transitions");    using transition_p = type_pack<T...>;    using state_collection = typename NoDuplicates   <Collection<typename T::source_t... ,typename T::target_t...>   >::Result;    using event_collection = typename NoDuplicates  <Collection<typename T::event_t...>    >::Result;    using state_v = decltype(get_var(state_collection{}));  using event_v = decltype(get_var(event_collection{}));  using transition_v = std::variant<T...>;};

Нуу, тут я набросил на вентилятор, конечно. Хотя все не настолько пугающе, как выглядит.

Структура TransitionTable параметризуется списком переходов/transition-ов, которые собственно и описывают всю логику конечного автомата.

Первым делом нам необходимо подстраховать себя от копипаста и просигналить при компиляции, что у нас повторы в списке. Исполняем это с помощью алгоритма NoDuplicates из всем известной библиотеки Loki. Результирующий тип под псевдонимом test_t сравниваем в static_assert-e с исходным списком переходов.

Далее, допуская что static_assert пройден, параметризуем некую структуру type_pack списком переходов и выведенному типу назначаем псевдоним transition_p. Структура type_pack, а также современные алгоритмы и методы по работе со списками типов собраны в файле typelist.h. Данный хедер написан под чутким руководством этого продвинутого парня.

Тип transition_p понадобится нам далее в конструкторе класса StateMachine.

Следом проходим по списку переходов, вытаскиваем, очищаем от повторов и сохраняем в отдельные коллекции состояния и эвенты. Эти коллекции alias-им как state_collection и event_collection соответственно.

К чему эта эквилибристика?

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

Удобным вариантом для этой цели является std::variant (тафтология умышленна).

Последовательно параметризуем std::variant списком переходов (выведенному типу назначим псевдоним transition_v); списком состояний и списком эвентов и назначаем для удобства псевдонимы state_v и event_v соответственно.

Тут нюанс. Чтобы вывести transition_v нам достаточно пробросить в шаблонный параметр std::variant variadic pack (T...) из шаблонного параметра класса TransitionTable.

А вот чтобы вывести state_v и event_v мы используем

вспомогательную constexpr функцию
template<typename... Types>constexpr auto get_var (th::Collection<Types...>){return std::variant<Types...>{};}

Далее мы инстанцируем получившиеся типы в конструкторе класса StateMachine и сохраним их для дальнейшего использования в подходящих контейнерах, о чем совсем скоро.

Оставшихся к этому моменту читателей я не обрадую - начинается основной замес.

Целиком приводить класс StateMachine не буду, он громоздок, прокомментирую его для удобства восприятия по частям.

Контейнер transitions
template<typename Table>class StateMachine{//other stuffprivate:using map_type =std::unordered_map < Key, transition_v, KeyHash, KeyEqual>;Key key;map_type transitions;};

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

Объект типа Key хранит у себя значения индексов состояния и эвента:

Key
struct Key{  base_t state_idx = 0;  base_t event_idx = 0;};

Теперь стало понятно назначение статических членов idx в базовых сущностях. Я просто не знаю, как писать хэшеры для пустых структур. Тащить в строку название самого типа через typeid и _cxa_demangle для нас не вариант, мы же условились, что не пользуем RTTI.

Контейнер events
template<typename Table>class StateMachine{//other stuffprivate:using queue_type =  RingBufferPO2 <EVENT_STACK_SIZE, event_v, Atomic>;    queue_type events;};

events - очередь, в которую будут прилетать эвенты. Так как это чисто рантаймовая история, необходимо избежать динамических аллокаций. Поэтому реализуем ее на базе статического кольцевого буффера RingBufferPO2, который я позаимствовал здесь (отличная для своего времени работа!).

Помимо указанных контейнеров, в объекте типа StateMachine мы будем хранить текущее состояние/state и значение сторожа/guard:

state and guard
template<typename Table>class StateMachine{//other stuffprivate:state_v current_state;Guard guard = Guard::OFF;};

Саспенс уже не за горами.

Конструктор
template<typename Table>class StateMachine{public:using transition_pack = typename Table::transition_p;StateMachine(){  set(transition_pack{});} // other stuff};

В конструкторе метод set принимает аргументом объект с информацией о списке переходов, пробегается по нему, достает инфо о каждом состоянии и эвенте, заполняет контейнер transitions, а также запоминает начальные состояние и значение сторожа:

Метод set
template <class... Ts>void set (type_pack<Ts...>){(set_impl(just_type<Ts>{}), ...);};template <typename T>void set_impl (just_type<T> t){using transition = typename decltype(t)::type;using state_t = typename transition::source_t;using event_t = typename transition::event_t;Guard g = transition::guard;Key k;k.state_idx = state_t::idx;k.event_idx = event_t::idx;transitions.insert( {k, transition{}} );if (0 == key.state_idx) {key.state_idx = k.state_idx;guard = g;current_state = state_t{};}}

Итак, объект StateMachine сконструирован, пора его как-то шевелить.

Но перед этим забудем как страшный сон суммируем что уже рассмотрели к этому моменту:

  • Определили типы компонентов конечного автомата: состояние/state, событие/event, действие/action, сторож/guard

  • Определили тип переход/transition, который должен параметризоваться типами source state, event, target state, guard.

  • Определили тип таблицы переходов. В качестве шаблонных параметров ему передается список переходов/transition-ов, который и определяет алгоритмы работы автомата.

  • При компиляции в классе TransitionTable, на основе std::variant выводятся типы-коллекции переходов, состояний и эвентов, которые впоследствии при конструировании объекта StateMachine инстанцируются и сохраняются в контейнеры, с которыми уже можно работать в рантайме.

Стержневая идея моей имплементации автомата такова (вдохнули): при наступлении события, мы достаем из его типа индекс (idx), объединяем его с индексом текущего состояния в объекте Key, по которому в контейнере transitions находим нужный нам переход, где получаем знания о целевом состоянии, стороже и действии, которое требуется выполнить в этом переходе, а также сверяем значения сторожа с текущим, для подтверждения или отмены перехода/действия(выдохнули).

Теперь рассмотрим методы API нашего автомата, реализующие эту логику.

Переключать состояния мы можем двумя способами: вызывать немедленный переход методом fsm.on_event(event{}) (шаблонная версиия fsm.on_event<Event>() если тип события известен на этапе проектирования), или можем складывать события в очередь методом fsm.push_event(event{}), чтобы потом, например в основном цикле, разобрать ее методом fsm.process(). Также, если в состояние передано какое-то действие, то мы можем вызывать его методом fsm.state_action().

Рассмотрим их детальнее, начиная с последнего

Метод state action
template <typename... Args>void state_action (const Args&... args){state_v temp_v{current_state};    auto l = [&](const auto& arg){      using state_t =  std::decay_t<decltype(arg)>;    using functor_t = typename state_t::action_t;        if constexpr (!std::is_same_v<functor_t, void>){    functor_t{}(args...);      }  };    std::visit(l, temp_v);}  

В методе мы создаем локальную переменную типа std::variant<State...> temp_v и инициализируем ее текущим состоянием. Далее определяем лямбду, которая послужит аргументом в методе std::visit.

"Нырнув" с ее помощью в variant, мы выведем тип текущего состояния, из него в свою очередь вытащим тип переданного функтора, инстанцируем его (проверив, если его тип не void) и вызовем с паком аргументов, захваченных лямбдой по ссылке, и проброшенных с головного вызова.

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

Метод on_event
template <typename Event,class = std::enable_if_t<std::is_base_of_v<EventBase, Event>>>void on_event(const Event& e){Key k;  k.event_idx = e.idx;  k.state_idx = key.state_idx;  on_event_impl(k);}void on_event_impl (Key& k){transition_v tr_var = transitions[k];    Key &ref_k = key;  Guard &ref_g = guard;  state_v &ref_state = current_state;    auto l = [&](const auto& arg){    using tr_t =  std::decay_t<decltype(arg)>;    using functor_t = typename tr_t::action_t;        if ( GuardEqual{}(ref_g, tr_t::guard) ){          using target_t = typename tr_t::target_t;            ref_k.state_idx = target_t::idx;      ref_state = target_t{};            functor_t{}();      }   };      std::visit(l, tr_var);}

Здесь, как я уже описывал, мы достаем индекс из эвента, объединяем его в Key с индексом текущего состояния, и в качестве ключа передаем в приватный метод on_event_impl(Key& k).

Там мы по принятому ключу достаем из контенера transitions объект типа std::variant<Tr...> и инициализируем им локальную переменную tr_var. Ну а далее - логика, схожая с предыдущим примером. Вызываем std::visit c tr_var и лямдой l, в которой из типа Tr получаем сведения о состоянии, в которое нужно перейти (target_t), стороже (tr_t::guard)и типе действия (functor_t) к исполнению.

Сверив значение сторожа перехода с текущим сторожем, мы или оcуществляем переход, инстанцируя и вызывая functor_t, и сохраняя target_t в переменную с текущим состоянием(current_state), или возвращаемся в исходное состояние. Где ждем смены значения сторожа и нового события.

Метод push_event
template <unsigned int N>void push_event (const Event<N>& e){  events.push_back(e);}

Тут все просто.

Метод set_guard
void set_guard (const Guard& g){  guard = g;}

Вызываем, когда в программе сложились условия для перехода в следующее состояние.

Метод process
void process (void){    state_action();    auto it = transitions.begin();    Key k;  k.state_idx = key.state_idx;    for (uint32_t i = 0; i != events.size(); ++i){        auto v = events.front();     auto l = [&](const auto& arg){      using event_t =  std::decay_t<decltype(arg)>;      k.event_idx = event_t::idx;      it = transitions.find(k);    }        std::visit(l, v);        if ( it != transitions.end() ){            events.pop_front();      on_event_impl(k);      return;        } else {      events.push_back(v);      events.pop_front();    }  }}

При вызове метода мы первым делом выполняем некое полезное действие (если не void), переданное в состояние, state_action().

Ну а далее пробегаемся по очереди эвентов и просто воспроизводим логику, уже описанную для метода fsm.on_event(event{}).

Разумеется, работу с событиями можно значительно ускорить, при этом расширив функционал автомата. Тип Event модернизируем

так
template <base_t N, base_t Priority>struct Event : EventBase{  static constexpr base_t idx = N;  static constexpr base_t pri = Priority;};

Теперь мы можем не пушить все события в одну очередь, а завести, скажем, std::array<queue_t, PRIRITY_NUM>, где индексом ячейки будет служить приоритет события. Тогда у нас получится приняв эвент, вытащить его приоритет, по нему, как по индексу за константное время попасть в нужную очередь событий, которая будет гораздо меньше, чем общая и быстрее в обработке.

И, что не менее важно, так мы сможем прыгать между состояниями не по очередности принятых эвентов, но по их приоритету.

На самом деле в следующей версии своего FSM у меня так и реализовано, но здесь я привожу упрощенный вариант.

Хорошо, каков же будет практический результат этой разнузданной шаблонной вакханалии?

Детектор нейтрино(нет)
struct green_a {/*toogle green led every 50ms*/}struct yellow_a {/*toogle yellow led every 50ms*/}struct red_a {/*toogle red led every 50ms*/}struct green_f {/*toogle green led every 150ms*/}struct yellow_f {/*toogle yellow led every 150ms*/}struct red_f {/*toogle red led every 150ms*/}using STATE_A(green_s, green_f);using STATE_A(yellow_s, yellow_f);using STATE_A(red_s, red_f);using EVENT(green_e);using EVENT(yellow_e);using EVENT(red_e);using fsm_table = TransitionTable    <    Tr<green_s, yellow_e, yellow_s, yellow_a, Guard::NO_GUARD>,    Tr<yellow_s, red_e, red_s, red_a, Guard::NO_GUARD>,    Tr<red_s, green_e, green_s, green_a, Guard::NO_GUARD>    >;int main(void){  //some other stuff  StateMachine<fsm_table> fsm;  fsm.push_event(red_e{});  fsm.push_event(yellow_e{});  fsm.push_event(green_e{});  while (1){    fsm.process();  }}

В этом примере структуры типа color_a(ction) - это действия при переходе; color_f(unctor) - функторы, которые будут выполняться каждый раз при заходе в стейт, ну и далее понятно.

Объявляем стейты, эвенты, переходы, таблицу переходов. Конструируем из класса StateMachine<fsm_table> наш конечный автомат fsm. Пушим события, заходим в while и наблюдаем аквасветодискотеку на нашей отладке.

Обращу еще ваше внимание на макросы, через которые организована декларация состояний и событий. Задача была исхитриться и не делать так:

using even_t = Event<1, 15>;

using state_t = State<1, state_functor>;

Очевидно, почему это плохо. Ручная индексация - практически неизбежные ошибки и очепятки.

Что ж, реализуем constexpr функцию, которая будет преобразовывать переданное ей желаемое название типа в число, которым и будем параметризовать шаблон. Ну и спрячем это все в удобную обертку.

Как-то так
#define STATE_A(str, act) str = State<name(#str), act>#define EVENT(str) str = Event<name(#str)>constexpr base_t name (const char* n){    base_t  res = 0;    for (base_t i = 0; n[i] != '\0'; i++){        char data = n[i];        for (base_t j = sizeof (char) * 8; j > 0; j--){            res = ((res ^ data) & 1) ? (res >> 1) ^ 0x8C : (res >> 1);      data >>= 1;    }  }  return res;};

После крайнего проекта на работе у меня на руках осталась отладка NUCLEO-H743ZI2, на ней я и запилил тестовый вариант (забирайте здесь).

С оптимизацией -O3 реализация приведенного примера (только сам FSM) заняла 6,8Кб, с HAL-ом и моргалками - 14,4Кб.

Конечно же, пока это не более чем эксперимент, проверка концепции. Но агрегат завелся, черт его дери.

Будет очень здорово, если сообщество укажет на неизбежные факапы и укажет путь к улучшениям. Также смею надеяться, что кто-то выделит из материала и что-то полезное для себя.

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

Подробнее..

Шаблоны и концепты в С20

15.04.2021 14:13:15 | Автор: admin

Привет, Хабр!

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

Важное уточнение: эта лекция не попытка объять необъятное, а краткий экскурс по полезным возможностям C++ для членов олимпиадного сообщества: от извлечения кода в класс до внутренних механизмов работы лямбда-функций и щепотки ограничений (constraints) из C++20. Если интересно, приглашаем к просмотру.

Подробные таймкоды

00:53 Что нужно знать перед просмотром лекции

02:00 Особенности С++

03:10 Хорошие источники знаний и практик в C++

04:45 Классы. Стек с минимумом

06:21 Создание своей структуры

09:03 Запрещаем прямой доступ

09:53 Упрощаем отладку

10:29 Шаблоны классов

11:24 Статический полиморфизм в разных языках

12:03 Оптимизация

12:27 Ошибки компиляции и инстанцирование

13:40 Ограничения (С++20)

15:01 Шаблоны функций

15:27 Автовывод параметров

16:21 Class Template Argument Deduction (CTAD, С++17)

16:56 Ошибки компиляции и инстанцирование

17:47 Обобщенное программирование

19:12 Вложенные типы

20:10 Продвинутые техники

20:33 Функторы

21:00 Функциональные объекты

21:56 Как параметр шаблона

22:30 Функторы с состоянием

23:26 Функторы с состоянием для контейнеров

24:42 Лямбда-выражения

25:38 Расшифровка лямбды

26:28 Сохранение в переменную

27:27 Рекурсия не поддерживается

27:56 Захваты по значению и ссылке

29:18 Захват с инициализатором

30:29 Комбинированные захваты

31:16 Применение функторов

32:15 IIFE

33:18 Вектор лямбд и стирание типов (type erasure)

34:36 Функтор как параметр функции

35:51 Функтор как поле класса

37:45 Более сложные структуры данных (декартово дерево, дерево отрезков)

38:34 За кадром: лямбды-компараторы

39:48 За кадром: более сложные шаблоны

41:23 Студенческие проекты на C++ (в прошлом году рассказывали о проектах наших первокурсниках)

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

Подробнее..

БЛАБПЛА разрушение восторгов о применении ИИ в БЛА на практике

12.11.2020 20:10:42 | Автор: admin

Последние события выяснения жителей Вилларибо и Виллабаджо, кто самый главный в Нагорном Карабахе, внимательным и любопытствующим телезрителям и читателям сказали довольно много. Даже если они уже забыли какой у них ВУС.


Некоторое время плавала по морям-окиянам сети интернет байка, что БПЛА могут решить практически любой вопрос на планете сбросив несколько десятков килограмм, на тех у кого БПЛА нет. Это что-то вроде байки о всемогуществе АУГ Соединённых Штатов.


Причем решить этот вопрос без участия даже оператора, а только лишь мощью ИИ. Мол, вот чуть-чуть Т-800 стройными рядами пойдут насаждать демократию и справедливость с приданными им в усиление ДРГ в виде Т-1000.


Нет, сомнений нет, турецкий "Байрактар ТБ2" штука полезная. В нужном месте и в нужное время. Но уж точно не "средство два-в-одном вам избавит от страданий". Мой хороший друг полковник ВС РФ в запасе Владимир Трухан детально в своём журнале пояснил почему. И даже вежливо.


А далее передаю микрофон профессиональному военному ПВОшнику:



Если мне вежливо задать вопрос, то на него я могу и ответить. Даже если этот самый вопрос провокационный, на тему применения в Нагорном Карабахе БЛА/БПЛА и массовой угрозы этого для безопасности России.


Ответить-то могу, но не обязательно вежливо. Поскольку вот остолбенело мне в конец: как только появляется какая вундервафля в войне племён, так немедленно военных экспертов набегает, в количестве невероятном. Все про крах ВС РФ в голос голосят.


Если применение беспилотных летательных аппаратов (БЛА/БПЛА) в войсковой операции Азербайджана на территории Нагорного Карабаха, что и доказало, так это то, что в войнушке бедных работает случайно оказавшийся у одного из бедняков девайс, локально приобретающий решающее значение. Например, два миномёта с миномётчиками у племени Матумба в боях с многопротивным племенем Тулумба.


Принципиально с миномётчиками, поелику бедность она не только в вооружении и в военной технике, она ещё в оперативной подготовке штабов и боевой подготовке войск. У Тулумба тоже был злодейский прибамбас (пулемёт системы Максим 3 штуки), но не сложилось с пулемётчиками. А техника (вот девушка с низкой социальной ответственностью такая) самостоятельно ни разу не работает.


Я не оскорбляю сейчас героизм защитников Карабаха (с армянской стороны), я уважаю героев. Просто я в курсе, что героизм появляется после того, когда кто-то где-то вдумчиво накосячил. Например, Генштаб Республики Армения, забивший на разведку и не сумевший обеспечить грамотный количественный и качественный состав сил и средств районов обороны, необходимый и достаточный для ведения войны.


Ладно, к беспилотью перейду; объясняя, почему самостоятельно это не работает. Против сбалансированной по силам и средствам группировки, личный состав которой обучен и готов.


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


Разработчики ИИ не смогут и автомобильчик научить учитывать автодорожное поведение Джамшута на джихад-такси (в погоне за рублём), Элеоноры на насосанном Феррари (в погоне за понтами), а также полупьяного Петровича через Ленинский проспект, на собственных ногах, но поперёк потока (в надежде на бутылку); особенно когда эти три события происходят одновременно и на одном участке полотна.


А теперь мне расскажите про воздушный бой. Или про противовоздушный бой. Или про штурмовку наземных сил. Которые этот самый ИИ проводит самостоятельно, вне запрограммированного естества. А я поржу, причём не в кулачок, а в голос.


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


То есть БЛА работают либо по заблаговременно заданной программе, либо по командам оператора с воздуха, с поверхности, не суть. И используются либо как средство тактической и оперативной разведки, либо как средство доставки боеприпаса в заданный район.


Любая программа это шаблон, пусть даже в него прописаны разнообразные алгоритмы. Пусть даже этих самых алгоритмов большое такое число. Поелику вариантов тактических действий всё одно больше, и пытливый ум бойца постоянно их выдумывает, при наличии опыта. И любой военный лётчик второй класс + порвёт любые шаблоны к половым органам. И любой опытный ПВО-шник.


БЛА-разведчик на программе, для стрелка (хоть с воздуха, хоть с земли) это жирная домашняя утка для охотника на диких крякв. Маршрут просчитываем, к противозенитному манёвру не способен Стреляй не хочу. Нет, со скоростью у него по определению не очень фиг что потом увидишь на средствах объективного контроля. То есть живёт он ровно до тех пор, пока на него не обратили внимания. И, если на маршруте интересное где-нибудь вдали от намеченной трассы, то хрен ты что узнаешь о таком. Думать, куда довернуть, БЛА не умеет. И не научится.


БЛА-доставщик боеприпаса на программе А в чём понт? Есть масса полезных дальнобойных вещей, работающих по заранее заданным координатам. Например, крылатые и оперативно-тактические ракеты. Есть артиллерия и РСЗО. Те же бомбардировщики тоже есть.


Не будем забывать, что полезная нагрузка того распиаренного Байрактара всего 150 килограмм (я в курсе, что он не только автономен, но также управляется с земли). Не будем забывать, что масса боевой части ракеты ОТРК Искандер 480 килограмм. И не будем забывать, что ракету Искандера перехватить куда как более напряжно, чем этот самый Байрактар.


Здесь пытливые умы могут мне чего-то рассказать про американцев и их опыт применения подобных БЛА, но об этом немного ниже. Про другое послушайте, ещё более печальное для онанирующих на решающее значение этого вида средств разведки и поражения.


Так вот, смысл БЛА имеют в основном тогда, когда они управляются оператором с поверхности или с воздуха (из космоса пока что нет; там только спутниковая навигация имеется иногда). Тогда БЛА-доставщик имеет преимущество, поскольку у него более длительное, чем у пилотируемых, время дежурства в назначенном районе, следовательно более быстрая реакция на внезапную цель. И в воздушной засаде, типа, посидеть может.


Типа посидеть это потому, что никаких укрытий в небесах не предусмотрено, от пытливого радиолокационного взгляда. И всякую средне- и маловысотную фигню научились засекать уже давно. А навестись на этот тихоходный агрегат так и вовсе не проблема; даже спьяну перепёлку эту в её районе звезданут.


То есть для безопасного барражирования нашему беспилотному ребёнку нужно свободное небо, которое ему хрен кто даст, при противодействии серьёзных пацанов. Это для джихад-арбы проблема потолок восемь тысяч двести, или для ПЗРК. Эшелонированные средства радиолокационной разведки и огневого поражения ВКС и СВ РФ проблему эту решают влёт; истребителям тем просто поразвлечься стрельбой по неподвижной мишени.


С поражением БЛА другая головная боль расход дорогого боеприпаса. Ну хорошо, Байрактар (70 миллионов баксов за комплект) цель финансово достойная, но есть другие, более дешёвые. А зенитные ракеты большинства комплексов нет. Но над этим вдумчиво работают; может новым вариантам Шилки места найдут.


Не спрашивайте за подробности не ко мне. Пока отметьте, что проблема не стоит, чем их бить, эти БЛА; стоит проблема как бы подешевле.


Вторая и основная печаль апологетов БЛА-всесилия открытые каналы управления. Не, они, конечно, шифрованные шо абзац, однако (вот засада) по тем же самым электромагнитным волнам. И радиообмен между оператором и воздушным сандаликом легко засекается средствами РЭБ и так же легко давится, причём для БЛА насовсем. Он потом куда и прилетит, то, стремительно снижаясь, об асфальт. Или о другую какую поверхность.


Современные средства РЭБ, российского производства сука, неразборчивые, когда им особенно того хочется. И наглухо закрывают весь назначенный район для любого радиообмена. И наглухо гасят все каналы беспроводной связи. И предельно усложняют навигацию БЛА, и делают малореальным их наведение оператором. А ещё можно этой падле активную помеху всадить, в любой из возможных приёмопередатчиков или приёмников.


Третья неприятная подробность а боевая эффективность летательного аппаратика с живым пилотом по-любому выше, чем БЛА. Пофиг какого БЛА (по управлению) пусть тот работает хоть автономно, хоть с оператором. Здесь даже распространяться особо не буду, очевидные ведь вещи; тот же обзор. Та же оценка обстановки; и воздушной, и на поверхности. То же время реакции органов управления хоть пилотированием, хоть применением боеприпаса.


А ещё выше полезная нагрузка этих самых летательных аппаратов, что тоже немаловажно для боевого применения. Су-25 имеет тонну четыреста кг нормальной и четыре тонны четыреста кг максимальной боевой нагрузки, если что. Восемь полутонных бомб. Впечатления от подрыва одной такой в нескольких километрах от меня в КШМ помню до сих пор и буду рассказывать даже внукам.


И да, на память: взрослые военные мальчики имеют сбалансированную по силам, средствам, поставленным целям и решаемым задачам группировку ПВО. Специально для сетевых бойцов сообщаю, что эшелон прорыва ПВО у тех же НАТО включает в себя 100-200 самолётов, из которых 60-70 тактических истребителей и штурмовиков, до 30 истребителей сопровождения и 10-12 самолётов РЭБ. Замените, попробуйте, это всё одними БЛА.


Я не придурок, чтобы утверждать, что БЛА, во всех своих конфигурациях, бесполезны. Отнюдь, это эффективное средство разведки и поражения, но исключительно для решения задач в определённых условиях обстановки, и не более того. Также эффективны, для своих условий, миномёты и гранатомёты, артиллерия и РСЗО, самолёты и вертолёты, разнообразные ракеты всех видов и родов войск. Как те же бомбы любых конфигураций. И в некоторых случаях (я вам по секрету расскажу) эффективен даже пистолет.


Те же америкосы употребляют БЛА для разведки или для поражения там, где им стрёмно употреблять ЛА с живыми лётчиками. Например, сбитый над терводами Ирана RQ-4A Global Hawk это набор обломков различной степени побитости; обидно, но не надо вызволять пилота (как в случае с U-2 в СССР) или его труп. А стоит эта беспилотная леталка 220 миллионов долларов, тащемта.


А ещё америкосы экономные как весь Израиль в одном кибуце сконцентрированный. Посему они полагают, что платить страховку и пенсии родне убитых военных суть офигенная расточительность, а потерянный пепелац суть повод у Конгресса ещё бабосов попросить, для новой купли ровно такой же игрушки. А это, между прочим, ещё и откаты от ВПК; лоббистская деятельность называется. То, с чем у нас борются, как с "коррупцией".


И так же, между прочим, они освежают запасы крылатых ракет, уничтожая сотней Томагавков два сарая и бродячую собаку на территории Сирии. Тогда тоже паника, как помню, была, и ликование поперёк-российских аналитиков. Под бодрое ржание меня и моих коллег.


То есть у пиндосов своя метусня (они и ЧВОК поэтому везде используют тоже проблема экономии выплат), у нас своя, у турок и Азербайджана своя. У турок, например, наличие подготовленных пилотов, особенно в рассуждении неудавшегося турецкого военного переворота и последующих репрессий, что были не так давно. У Азербайджана в принципе проблема с авиацией и необходимым количеством средств поражения.


ВС РФ, буде им необходимо, эти районы обороны, типа бывших в НКР, переработали бы на щебёнку и металлолом (с элементами органики) совершенно без использования БЛА и более эффективно. Колонны бронетехники гавкнули бы тоже только в путь. Потому как у нас различные прибамбасы есть, в достаточном количестве.


И подготовленный личный состав есть, и в штабах, и в частях, а не так, как оказалось у Армении. Посему не надо мне рассказывать про уничтоженные средства ПВО российской и советской разработки, что оказались в их распоряжении. Техника кусок металла в руках любого дикаря любой национальности. БЛА в Сирии бармалеи используют уже давно. Жду трагических и увлекательных историй об уничтоженных образцах ВКС РФ.


Да, БЛА тоже имеются, хотя и есть куда расти. Раньше просто этому не придавали особенного значения опять же, в силу наличия разнообразия других девайсов для удалённого убийства. Теперь будут более активно разрабатывать выгоды и ошибки их использования теперь более понятны.


Зато другой суперский беспилотный аппарат есть. Подводный. Посейдон его зовут. От которого спазмы сфинктера у товарищей американцев, вплоть до полной блокировки кишечной проходимости. Это стратегический беспилотный аппарат, способный перевернуть ход и даже решить исход войны. А тактические БЛА ещё одни военные игрушки. Не спорю, эффективные в умелых руках, но принципиально не решающие ничего.


На ход/исход войны они не повлияют.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru