...

суббота, 29 февраля 2020 г.

[Из песочницы] Последовательный fetch и 5 способов решения

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


  • факториал
  • числа Фибоначчи
  • уникальность элементов массива
  • проверка на сбалансированность скобок внутри текста
  • сортировки (mergeSort, insertionSort, bubbleSort, quickSort)
  • деревья (обход в глубину / обход в ширину / нахождение кратчайшего пути между узлами)

За последние два года, проведя порядка 70 собеседований по JavaScript, постепенно начал понимать, что они не всегда отражают действительность, так как именно их и ожидает кандидат, именно к ним он и подготовился лучше всего (а если не подготовился, то сам виноват).

Поэтому хотелось задание, которое удовлетворяло бы таким критериям:


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

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

Задача звучала примерно следующим образом:


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

Схематично это выглядело бы примерно так:
fetch(url1) => fetch(url2, resultsUrl1) => fetch(url3, resultsUrl2)

или что-то вроде
compose(res2 => fetch(url3, res2), res1 => fetch(url2, res1), () => fetch(url1))

как бы мы могли решить эту задачу?

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


  • генераторы
  • async/await
  • рекурсия

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

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

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

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


  • асинхронные генераторы
  • метод reduce

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

function fakeFetch (url, params='-') {
    // этот вывод в консоль покажет порядок вызовов с их входящими параметрами
    console.log(`fakeFetch to: ${url} with params: ${params}`);
    return new Promise(resolve => {
        setTimeout(() => resolve(`${url} is DONE`), 1000);
    })
};

Список адресов ограничим тремя элементами (для простоты):

const urls = ['url1', 'url2', 'url3'];

Но наше решение должно не зависеть от их количества (сморим условие 1), т.е цепочки вида then().then().then() и await; await; await; заранее отбраковываются.

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

fetchSeries(result => console.log(`result: ${result}`))

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


Генераторы

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

function generatorWay(callback) {
    function* generateSequence() {
        let results;
        for (let i = 0; i < urls.length; i++) {
            results = yield fakeFetch(urls[i], results);
        }
        return results;
    }
    function execute(generator, yieldValue) {
        let next = generator.next(yieldValue);
        if (!next.done) {  
            return next.value
                .then(result => execute(generator, result));
        } else {
            callback(next.value);
        }
    }
    execute(generateSequence())
}

попробовать можно тут

Общий принцип такой:


  • генератор generateSequence yield'ит не просто значения, а промисы.
  • есть специальная функция execute(generator), которая запускает генератор последовательными вызовами next, получает из него промисы — один за другим, и, когда очередной промис выполнится, возвращает его результат в генератор следующим next.
  • последнее значение генератора execute уже обрабатывает как окончательный результат, вызывая callback.

Асинхронные генераторы

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

async function asyncGeneratorWay(callback) {
    async function* generateSequence() {
        let results;
        for (let i = 0; i < urls.length; i++) {
            results = yield await fakeFetch(urls[i], results);
        }
        return results;
    }
    let generator = generateSequence();
    let result;
    while (!result || !result.done) {
        result = await generator.next(result && result.value);
    }
    callback(result.value);
}

попробовать можно тут

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

Перебирать же с помощью for await of не выйдет, потому что это нарушит дополнительное условие 2.


Async/await

Второй по популярности способ. Он вполне пригоден, но пропадает вся красота использования конструкций async/await. А также, внешнюю функцию тоже приходится объявлять как async, что не всегда удобно и целесообразно.

async function asyncAwaitWay(callback) {
    const series = async () => {
        let results;
        for (let i = 0; i < urls.length; i++) {  
            results = await fakeFetch(urls[i], results);
        }
        return results;
    }
    const result = await series();
    callback(result);
}

попробовать можно тут

тут мы просто в цикле вызываем каждый fakeFetch и ждем его выполнения с помощью await;


Recursion

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

function recursionWay(callback) {  
    const recursion = (arr = [], promise = Promise.resolve()) => {
        if (!arr.length) { 
            return promise;
        }
        const [url, ...restUrls] = arr;
        return promise
            .then(res => recursion(restUrls, fakeFetch(url, res)));
    }
    recursion(urls)
        .then(result => callback(result));
}

попробовать можно тут

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

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


Reduce

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

function reduceWay(callback) {
    urls
        .reduce((accum, item) => {
            return accum
                .then(res => fakeFetch(item, res))
        }, Promise.resolve())
        .then(result => callback(result));
}

попробовать можно тут

тут все просто:


  • итерируемся по массиву
  • по цепочке запускаем следующий fakeFetch из метода then;
  • так же как и в предыдущем способе, Promise.resolve(), в качестве значения по-умолчанию, используем для первой итерации, когда никакого обещания(Promise) у нас еще нет, чтоб избежать постоянных проверок. Это выглядит равноценно такой записи:
function reduceWay(callback) {
    urls
        .reduce((accum, item) => {
            if (!accum) {
                return fakeFetch(item);
            }
            return accum
                .then(res => fakeFetch(item, res));
        })
        .then(result => callback(result));
}

при этом получаем на 2 строки кода меньше.


Выводы

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

И фаворитом в этой "гонке", как видно из таблицы, оказался обычный метод reduce. Разумеется, в реальных условиях этот код будет еще читабельнее и короче (за счет форматирования). И будет выглядеть, например, так:

const reduceWay = callback => urls.reduce(
    (acc, item) => acc.then(res => fakeFetch(item, res)),  
    Promise.resolve())
    .then(result => callback(result));  
}

Послесловие

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

Для сильных кандидатов была возможность проверить знание и умение работы с генераторами, для средних — с рекурсиями. Для евангелистов async/await — показать, что не везде синхронность написания асинхронных вызовов уместна и лаконична. Новичков всегда можно было определить по неумению работы с reduce и/или боязни использования рекурсий.

Это не полноценная задача для оценки уровня кандидата, но начало для беседы, в результате которой рождается истина… но это не точно.


Полезные ссылки

Генераторы

Асинхронные генераторы

Массив: перебирающий метод reduce

Рекурсия

Async/await

Промисы

Цепочка промисов

Let's block ads! (Why?)

SpaceX провела испытание прототипа Starship SN1, которое закончилось взрывом

Согласно информации ютуб-канала NASASpaceflight и издания GeekWire, 29 февраля 2020 года около 07:00 по мск во время испытаний на полигоне в районе Бока-Чика (штат Техас) прототип космического корабля Starship SN1 компании SpaceX был серьезно поврежден.
В ходе испытаний баки прототипа Starship SN1 начали заполнять криогенной жидкостью (жидким азотом), потом произошел взрыв. Корпус прототип Starship SN1 лопнул в нижней части, а его основная конструкция деформировалась и подлетела на высоту в несколько десятков метров, после чего рухнула боком на территорию полигона. А через несколько секунд произошел еще один взрыв, который отбросил верхнюю переборку Starship SN1 на достаточно большое расстояние.

В настоящее время в компании SpaceX не прокомментировали ситуацию с испытанием прототипа Starship SN1 и произошедшим на полигоне взрывом.

Видеозапись февральского испытания прототипа Starship SN1:

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

«Здесь не о чем беспокоиться. Тест, ошибка, исправление, тест, ошибка, исправление — это практика SpaceX. Они извлекут уроки из этого и сделают в следующий раз все правильно. Это был тест с очень низкими ставками, учитывая, что SN1 собирались использовать только для статических огневых испытаний в любом случае во время тестов», — написал в твиттере Майкл Бэйлор.

Это уже второй очень похожий подобный инцидент с прототипами корабля Starship за последние сто дней. Ранее в 20 ноября 2019 года на полигоне в Бока Чика на юге американского штата Техас полноразмерный прототип верхней (второй) ступени космического корабля Starship Mk1 компании SpaceX был разрушен во время проведения испытаний аппарата на максимальное заполнение топливных баков криогенной жидкостью.

Let's block ads! (Why?)

[Перевод] Ansible против Puppet

Ansible и Puppet представляют собой системы управления конфигурациями (SCM), необходимые для построения повторяющихся инфраструктур.

Ansible отличается простотой использования, имеет безагентную архитектуру (не требует установки агента/клиента на целевую систему) и YAML-подобный DSL, написана на Python и легко расширяется за счет модулей. Обычно управляет конфигурацией Linux.

Puppet имеет клиент-серверную архитектуру (периодически опрашивает сервер, чтобы внести в конфигурацию изменения, внесенные администратором сети), написана на Ruby и имеет Ruby-подобный DSL. Это приложение позволяет централизованно управлять конфигурацией ПО, установленного на нескольких компьютерах.

В статье проводится сравнение преимуществ и недостатков этих SCM.

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

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

Ansible против Puppet: краткий обзор


Puppet — один из известнейших брендов на рынке CM. Он существует с 2005 года, что для инструментов CM равносильно существованию со времен зарождения человечества. Множество именитых клиентов, таких как Google, Reddit, Dell, PayPal, Oracle, лаборатории Los Alamos Labs и Стэндфордский университет, управляют своими дата-центрами с помощью Puppet. Наличие таких клиентов на борту всегда придает продукту определенный уровень доверия. Puppet также может похвастаться самым зрелым интерфейсом и работой на всех основных операционных системах -Linux, Windows, Unix и даже Mac OS X. Следуя модели, созданной различными версиями Linux, это приложение с открытым исходным кодом разработано на Ruby. Однако существует солидная, хорошо зарекомендовавшая себя компания поддержки и спонсорства PuppetLabs, предлагающая профессиональную поддержку и коммерческую корпоративную версию программного обеспечения.
Puppet также предлагает простую процедуру установки и несколько инструментов для таких задач, как быстрое развертывание на клиентских серверах. В дополнение к GUI есть CLI на основе Ruby. На самом деле для большинства продвинутых задач вам, скорее всего, придется зависеть от CLI, причем GUI является интерфейсом просмотра, управления и мониторинга. Это означает, что в дополнение к работе системного администратора вам потребуется изучить Ruby.

Вы можете подумать: «Все это звучит здорово! Есть ли какие-то минусы, или стоит пойти и купить Puppet прямо сейчас»?.. Спор вокруг использования этой SCM на специализированных форумах CM заключается в том, что Puppet, как и многие другие гиганты программного обеспечения, стала жертвой собственного успеха и размера. «Ловкий» и «проворный» – это не те слова, которые можно использовать для описания работы Puppet. Пользователи сообщают об ошибках, которые слишком долго исправляются, и игнорировании новых запросов. Существует также некоторое недовольство тем, что PuppetLabs настойчиво подталкивает своих клиентов к принятию коммерческой версии. Наконец, хотя Puppet поддерживает как чистый Ruby, так и его настроенный DSL на CLI, поддержка одного лишь Ruby является устаревшей. Это плохая новость для тех, кто изучал только Ruby, а не DSL.

Ansible обошел Puppet, захватив самую большую долю рынка в индустрии систем управления конфигурациями — 26,5%. За ним следует Microsoft System Center CM (21,8%) и Puppet (12%).

Это также продукт с открытым исходным кодом, который был выпущен в 2012 году и с тех пор поддерживается компанией-разработчиком AnsibleWorks. Он был разработан на Python, а не на Ruby, что делает его духовно ближе к Salt (еще один новый инструмент CM), чем Puppet. Другим преимуществом Python является то, что он встроен в большинство Unix и Linux – приложений, так что SCM, написанные на этом языке, устанавливаются и работают быстрее.

Уникальный маркетинговый ход Ansible состоит именно в легком и быстром развертывании. Фактически эта система даже не использует развертываемые агенты для связи с мастер-клиеном, вместо этого все функции выполняются через SSH. Для тех конфигураций, которые не поддерживают корневой SSH, Ansible может выполнять «sudo» как root. Ansible можно запускать из CLI без использования файлов конфигурации для простых задач, таких как проверка работы службы или запуск обновлений и перезагрузок. Для более сложных задач конфигурация Ansible обрабатывается с помощью синтаксиса YAML в конфигурационных файлах, называемых «playbooks». Команды Ansible могут быть написаны практически на любом языке программирования и распространены в виде универсальных модулей JSON, что явно является преимуществом по сравнению с выбором одного конкретного языка.

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

Ansible против Puppet: различие в установке


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

Чтобы настроить Ansible, вначале необходимо назначить один узел в качестве узла управления control node. В действительности любой из ваших узлов может быть узлом управления. Вы можете установить Ansible на этом узле с помощью последнего пакета Ansible из репозиториев пакетов вашего дистрибутива, при этом нет никакой необходимости настраивать клиентское программное обеспечение на других узлах. Просто создайте пару ключей SSH на своем узле управления, а затем скопируйте их на остальные узлы. Далее создайте файл инвентаризации для узлов Ansible — как правило, в таких ОС Linux, как Red Hat Linux, Ubuntu и Debian это происходит в /etc/ansible/hosts. Теперь вы готовы использовать Ansible для запуска PlayBook как на вашем узле управления, так и на остальной части сетевой инфраструктуры. Ansible будет использовать SSH-соединения с вашими control node для запуска управления конфигурацией на основе PlayBook.

Настройка Puppet выглядит немного сложнее, но зато в интернете есть много документации, которая поможет в трудном случае. Во-первых, вам нужно будет настроить узлы master и agent так, чтобы они имели одинаковое время и часовой пояс. Затем вам потребуется зайти на мастер-сервер с root-правами и установить серверное программное обеспечение Puppet. Далее необходимо настроить файл Puppet-мастера /etc/hosts для подключения всех управляемых клиентов, запустить службу PuppetServer и перевести ее в режим «enable» для получения клиентских подключений на порту 8140. Поскольку Puppet опирается на программное обеспечение клиентов, вам потребуется установить программные агенты Puppet на каждом из них.

Кроме того, потребуется добавить IP-адрес мастер-сервера в /etc/hosts, чтобы клиент мог к нему подключиться. Для этого вы должны запустить и включить службу Puppet agent на каждом клиенте, после чего сгенерировать SSL-сертификаты, так как эта система CM использует HTTPS для связи мастер-клиент.

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

Ansible против Puppet: масштабируемость и механизм транспортировки конфигураций


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

Ansible по умолчанию использует транспортный механизм «smart», который основан на доверенных сертификатах SSH. Ansible сначала проанализирует PlayBooks и определит затрагиваемые элементы инфраструктуры. Затем она откроет SSH-соединение и создаст временный каталог. После закрытия этого соединения Ansible открывает второе соединение для выполнения копирования поверх кода модуля Ansible и шаблонного кода Ansible. Ansible закроет это соединение перед открытием третьего, окончательного соединения для выполнения кода. Эта настройка будет служить большинству целей, но ее можно изменять по мере масштабирования инфраструктуры.

Первая функция, которую может использовать Ansible, называется ControlPersist, и она полагается на постоянные, сохраняемые сокеты, чтобы сократить время на обмен рукопожатиями, которые необходимы для нескольких соединений. Ansible также поддерживает «конвейеризацию» — конфигурацию, которая сокращает количество требуемых соединений с трех до одного. Наконец, Ansible можно настроить для одновременной обработки большего числа узлов инвентаризации, настроив переменную forks в конфигурации этой системы. По умолчанию это значение равно 5, но для ускорения обработки можно установить более высокое значение.

Транспортный механизм Puppet – это HTTPS, который управляется с помощью SSL-сертификатов. Один сервер Puppet обрабатывает запросы конфигурации целого списка клиентов Puppet. Каждый клиент отправляет Puppet-факты мастер-серверу вместе с запросом на каталог Puppet, после чего мастер-сервер отправляет в ответ этот каталог. Затем клиент обрабатывает каталог, сверяя каждый программный ресурс с требуемым состоянием конфигурации, указанным в каталоге. Если ресурс не находится в нужном состоянии, клиент Puppet обновит ресурс на данном компьютере и после завершения обновления отправит отчет о выполненном изменении конфигурации Puppet-серверу.

Примечательно, что модель обработки Puppet выделяет поток JRuby из пула потоков для обработки каждого входящего клиентского соединения. По мере увеличения числа узлов можно оптимизировать масштабирование Puppet, увеличив количество доступных в пуле потоков JRuby. Это можно проделать, установив значение параметра конфигурации Puppet “max-active-instances” выше, чем значение по умолчанию, которое равно 1. Как правило, это значение принимается равным количеству ядер процессора вашего компьютера, однако имейте в виду, что это потребует большего объема задействованной процессорной памяти CPU RAM. Как правило, вам также будет необходимо установить более высокий «максимальный размер кучи» для JVM вашего Puppet-сервера, чтобы гарантировать, что ваши дополнительные потоки JRuby могут быть выделены без возникновения ошибки виртуальной машины Java «out of memory». При выполнении этой настройки следует соблюдать осторожность, так как можно столкнуться с потерей производительности.

Пример кода CM Ansible


Для повседневной работы с конфигурацией написание кода Ansible удивительно просто благодаря сочетанию двух факторов: использованию формата YAML для PlayBooks и декларативному стилю управления конфигурацией, который отсекает «острые углы». Это важно для быстрого повышения производительности команд DevOps и обеспечения управляемости вашего кода даже для сложных задач конфигурации.

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

Ниже приведен пример кода Ansible PlayBook, который устанавливает на ваши узлы веб-сервер Tomcat. Этот пример взят с официального репозитория Ansible examples, который вам стоит просмотреть, чтобы лучше ознакомиться с идиоматическим стилем Ansible:

---
- name: Install Java 1.7
yum: name=java-1.7.0-openjdk state=present

- name: add group "tomcat"
group: name=tomcat

- name: add user "tomcat"
user: name=tomcat group=tomcat home=/usr/share/tomcat createhome=no
become: True
become_method: sudo

- name: Download Tomcat
get_url: url=http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.61/bin/apache-tomcat-7.0.61.tar.gz dest=/opt/apache-tomcat-7.0.61.tar.gz

- name: Extract archive
command: chdir=/usr/share /bin/tar xvf /opt/apache-tomcat-7.0.61.tar.gz -C /opt/ creates=/opt/apache-tomcat-7.0.61

- name: Symlink install directory
file: src=/opt/apache-tomcat-7.0.61 path=/usr/share/tomcat state=link

- name: Change ownership of Tomcat installation
file: path=/usr/share/tomcat/ owner=tomcat group=tomcat state=directory recurse=yes

- name: Configure Tomcat server
template: src=server.xml dest=/usr/share/tomcat/conf/
notify: restart tomcat

- name: Configure Tomcat users
template: src=tomcat-users.xml dest=/usr/share/tomcat/conf/
notify: restart tomcat

- name: Install Tomcat init script
copy: src=tomcat-initscript.sh dest=/etc/init.d/tomcat mode=0755

- name: Start Tomcat
service: name=tomcat state=started enabled=yes

- name: deploy iptables rules
template: src=iptables-save dest=/etc/sysconfig/iptables
when: "ansible_os_family == 'RedHat' and ansible_distribution_major_version == '6'"
notify: restart iptables

- name: insert firewalld rule for tomcat http port
firewalld: port=/tcp permanent=true state=enabled immediate=yes
when: "ansible_os_family == 'RedHat' and ansible_distribution_major_version == '7'"

- name: insert firewalld rule for tomcat https port
firewalld: port=/tcp permanent=true state=enabled immediate=yes
when: "ansible_os_family == 'RedHat' and ansible_distribution_major_version == '7'"

- name: wait for tomcat to start
wait_for: port=

Эта конкретная задача Ansible загружает и устанавливает Apache Tomcat вместе с зависимым Java JDK 1.7, а затем настраивает, запускает и обеспечивает установку Tomcat. Как видно из данного примера, файлы, управляемые Ansible, могут содержать шаблоны Jinja, что упрощает вычисление значений и делает вашу конфигурацию более гибкой. Код YAML легко читать и писать как системным администраторам, так и разработчикам. Это приводит к тому, что Ansible становится доступной системой управления конфигурацией как для продвинутых, так и для начинающих пользователей.

Пример кода CM Puppet


Предметно-ориентированный язык Puppet основан на Ruby, но его синтаксис гораздо ближе к императивным языкам в стиле C, таким как Perl, Java и C++. Этот подход является промежуточным звеном между разработчиками, знакомыми с Ruby, и теми, кто может быть незнаком с данным языком. В результате вам может вообще не понадобится знать Ruby, чтобы изучить и продуктивно использовать Puppet DSL.
Это пример манифеста Puppet из репозитория PuppetLabs MySQL Puppet Module, который устанавливает и настраивает клиентский пакет MySQL:
# @summary
#     Installs and configures the MySQL client.
#
# @example Install the MySQL client
#     class {'::mysql::client':
#         package_name => 'mysql-client',
#         package_ensure => 'present',
#         bindings_enable => true,
#     }
#
# @param bindings_enable
#     Whether to automatically install all bindings. Valid values are `true`, `false`. Default to `false`.
# @param install_options
#     Array of install options for managed package resources. You must pass the appropriate options for the package manager.
# @param package_ensure
#     Whether the MySQL package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'.
# @param package_manage
#     Whether to manage the MySQL client package. Defaults to `true`.
# @param package_name
#     The name of the MySQL client package to install.
#
class mysql::client (
    $bindings_enable = $mysql::params::bindings_enable,
    $install_options = undef,
    $package_ensure = $mysql::params::client_package_ensure,
    $package_manage = $mysql::params::client_package_manage,
    $package_name = $mysql::params::client_package_name,
)  inherits mysql::params {
    
    include '::mysql::client::install'

    if $bindings_enable {
        class { 'mysql::bindings':
            java_enable => true,
            perl_enable => true,
            php_enable => true,
            python_enable => true,
            ruby_enable => true,
        }
    }

# Anchor pattern workaround to avoid resources of mysql::client::install to
# "float off" outside mysql::client
anchor { 'mysql::client::start': }
-> Class['mysql::client::install']
-> anchor { 'mysql::client::end': }
}

Важным преимуществом Puppet является то, что, в отличие от перечисленных выше императивных языков программирования, Puppet DSL является декларативным, в некотором смысле похожим на XML, а также на код YAML из приведенного выше примера кода Ansible. Декларативный подход как Puppet, так и Ansible действительно прослеживается в обоих примерах кода. С точки зрения кодирования, они похожи и ближе друг другу, чем такие инструменты, как Chef. Тем не менее, декларативный язык Puppet также предоставляет Ruby-подобные конструкции, такие как условные выражения и итерации, что опять же является компромиссным решением для пользователей Ruby и тех, кто не владеет данным языком.

Ansible против Puppet: простота использования


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

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

С точки зрения обучаемости пользователей обе платформы просты в использовании, но Ansible имеет небольшое преимущество. Легко добиться декларативного стиля YAML, так что код Ansible никогда не бывает слишком сложным. Тем временем Puppet пришла к осознанию некоторых проблем, связанных с объединением данных и кода в одних и тех же исходных файлах. Это привело к появлению Puppet Hiera, решения для хранения данных, которое использует формат YAML для хранения пар «ключ-значение» конфигурационных данных. Hiera проходит долгий путь к упрощению и оптимизации опыта Puppet DevOps. Формат YAML оказался довольно популярным для управления конфигурациями, причем Salt от SaltStack также использует этот формат.
Обе SCM имеют возможности для проверки и тестирования вашего СM, от проверки синтаксиса до интеграции кода «infrastructure-as-code».

Ansible против Puppet: расходы на приобретение лицензий и внедрение

Как инструменты с открытым исходным кодом, Ansible и Puppet имеют много общего в своей лицензионной политике.

Релиз Ansible с открытым исходным кодом доступен совершенно бесплатно. Компаниям, которым требуется больше гарантий безопасности, стабильности и надежности, предлагается перейти на Ansible Engine, продукт корпоративного класса, который предоставляется в Red Hat Linux. Стоимость лицензии Ansible Engine обычно составляет от $ 47,50 до $ 70 в год на узел и зависит от предпочтительной настройки.

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

Как и Ansible, релиз Puppet с открытым исходным кодом доступен бесплатно. Для получения дополнительных корпоративных функций и технической поддержки, организации могут перейти на Puppet Enterprise, стоимость которого составляет от $112 до $199 в год на 1 узел. Puppet Enterprise предлагает пакет из нескольких инструментов, включая отчетность и мониторинга состояния инфраструктуры предприятия.

Ansible против Puppet: сообщество


Puppet, выпущенный в 2005 году, является более старым инструментом DevOps, поэтому у него было больше времени для создания собственного сообщества и базы пользователей. Тем не менее, Ansible, запущенный в 2012 году, благодаря своему новому подходу, смог привлечь еще большую аудиторию и создать очень динамичное сообщество разработчиков-энтузиастов и пользователей. В число пользователей Puppet входят такие компании, как Uber, Salesforce и Paypal, а сообщество Ansible включает в себя такие компании, как Digital Ocean, 9GAG и TypeForm.

Если сравнить такой важный показатель развития продуктов open source, как число вкладчиков – участников разработки на GitHub, то Ansible с более чем 4800 вкладчиками намного превосходит Puppet с его 527 участниками разработки продукта. Развитие развития Ansible за последние годы происходило настолько стремительными темпами, что породило создание отдельного «галактического» репозитория Ansible Galaxy. Это означает, что сообщество Ansible стремиться делиться своим кодом, тем самым внося свой вклад в дальнейшее развитие продукта.

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

Обе системы имеют первоклассные инструменты для контроля жизненного цикла проекта управления конфигурацией через командную строку. И Puppet, и Ansible хорошо интегрируются с другими широко используемыми системами DevOps, такими как Docker, Kubernetes и Jenkins и облачными вычислительными платформами AWS и Azure.

Ansible против Puppet: что лучше?


В этом вопросе выбор остается только за вами. Декларативный стиль как Ansible, так и Puppet означает, что эти инструменты имеют намного больше общего, чем остальные системы управления конфигурацией. Однако, чтобы сделать оптимальный выбор, нужно обратить особое внимание на то, как потребности вашей команды сочетаются с дизайном и преимуществами конкретной SCM.
Ansible лучше подходит тем, кто интересуется конфигурацией в стиле YAML и разделяет философию Ansible – быть максимально простой, при этом достаточно быстро и параллельно управлять большим пулом машин. Кроме того, эта философия направлена на использование существующих функций SSH вместо создания пользовательских агентов.

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

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

Выводы


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

Немного рекламы :)


Спасибо, что остаётесь с нами. Вам нравятся наши статьи? Хотите видеть больше интересных материалов? Поддержите нас, оформив заказ или порекомендовав знакомым, облачные VPS для разработчиков от $4.99, уникальный аналог entry-level серверов, который был придуман нами для Вас:Вся правда о VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps от $19 или как правильно делить сервер? (доступны варианты с RAID1 и RAID10, до 24 ядер и до 40GB DDR4).

Dell R730xd в 2 раза дешевле в дата-центре Equinix Tier IV в Амстердаме? Только у нас 2 х Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 ТВ от $199 в Нидерландах! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — от $99! Читайте о том Как построить инфраструктуру корп. класса c применением серверов Dell R730xd Е5-2650 v4 стоимостью 9000 евро за копейки?

Let's block ads! (Why?)

Маленькая паяльная станция своими руками v2

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


Основные функции:
1. Паяльник. В коде заданы несколько температурных режимов (100, 250 и 350 градусов), между которыми осуществляется переключение кнопкой Solder. Плавная регулировка мне тут не нужна, паяю я в основном на 250 градусах. Мне лично это очень удобно. Для точного поддержания температуры используется PID регулятор.
Заданные режимы, пины, параметры PID можно поменять в файле 3_Solder:
struct {
  static const byte   termistor   =  A2;  // пин термистора
  static const byte   pwm         =  10;  // пин нагревателя
  static const byte   use         =  15;  // A1 пин датчика движения паяльника
  int                 mode[4]     =  {0, 150, 250, 300}; // режимы паяльника
  byte                set_solder  =  0; // режим паяльника (по сути главная функция)
  static const double PID_k[3]    =  {50, 5, 5};    // KP KI KD
  static const byte   PID_cycle   =  air.PID_cycle; // Цикл для ПИД. Участвует в расчетах, а также управляет частотой расчетов ПИД
  double PID_in;  // входящее значение
  double PID_set; // требуемое значение
  double PID_out; // выходное значения для управляемого элемента
  //unsigned long time;
  unsigned long srednee;
} sol;

2. Фен. Также заданы несколько температурных режимов (переключение кнопкой Heat), PID регулятор, выключение вентилятора только после остывания фена до заданной температуры 70 градусов.
Заданные режимы, пины, параметры PID можно поменять в файле 2_Air:
struct {
  static const byte   termistor     =  A3; // пин термистора
  static const byte   heat          =  A0; // пин нагревателя
  static const byte   fan           =  11; // пин вентилятора
  int                 mode_heat[5]  =  {0, 300, 450, 600, 700}; // быстрые режимы нагревателя
  byte                set_air       =  0; // режимы фена (нагреватель + вентилятор) по сути главная функция
  static const double PID_k[3]      =  {10, 2, 10}; // KP KI KD
  static const byte   PID_cycle     =  200; // Цикл для ПИД. Участвует в расчетах, а также управляет частотой расчетов ПИД
  double PID_in;  // входящее значение
  double PID_set; // требуемое значение
  double PID_out; // выходное значения для управляемого элемента
  unsigned long time;
  unsigned long srednee;
  boolean OFF = 0;
} air;

Нюансы:
1. Паяльник применил от своей старой станции Lukey 936A, но с замененным нагревательным элементом на китайскую копию Hakko A1321/
2. Кнопка отключения отключает сразу все что было включено.
3. Можно одновременно включать и паяльник и фен.
4. На разъеме фена присутствует напряжение 220В, будьте осторожны.
5. Нельзя отключать паяльную станцию от сети 220В пока не остынет фен.
6. При отключенном кабеле паяльника или фена, на дисплее будут максимальные значения напряжения с ОУ, пересчитанные в градусы (не ноль). Поясню: если например просто подключить кабель холодного паяльника должен показывать комнатную температуру, при отключении покажет например 426. Какой в этом плюс: если случайно оборвется провод термопары или терморезистора, на выходе ОУ будет максимальное значение и контроллер просто перестанет подавать напряжение на нагреватель, так как будет думать что наш паяльник раскален и его нужно охладить.
7. Защиты от КЗ нет, поэтому рекомендую установить предохранители.
8. Стабилизатор на 5В для питания Arduino используйте любой доступный с учетом напряжения питания вашего БП и нагрева в случае линейного стабилизатор. Так как у меня напряжение 20В установил 7805.
9. Паяльник прекрасно работает и при 30В питания, как в моей основной паяльной станции. Но при использовании повышенного напряжения учитывайте все элементы: стабилизатор 5В и то что напряжение вентилятора 24В.

Основные узлы и состав:
1. Основная плата:
— Arduino Pro mini,
— сенсорные кнопки,
— дисплей от телефона Nokia 1202.
2. Плата усилителей:
— усилитель терморезистора паяльника,
— полевой транзистор нагрева паяльника,
— усилитель термопары фена,
— полевой транзистор включения вентилятора фена.
3. Плата симисторного модуля
— оптосимистор MOC3063,
— симистор со снабберной цепочкой.
4. Блок питания:
— блок питания от ноутбука 19В 3.5А,
— выключатель,
— стабилизатор для питания Arduino.
5. Корпус.
А теперь подробнее по узлам.
1. Основная плата.

Обратите внимание наименование сенсорных площадок отличается от фото. Дело в том, что в связи с отказом от регулировки оборотов вентилятора, в коде я переназначил кнопку включения фена. В самом начале регулировка оборотов была реализована, но так как напряжение моего БП 20В (увеличил на 1В добавлением переменного резистора), а вентилятор на 24В, решил отказаться.
Сигнал с сенсорных кнопок TTP223 (включены в режиме переключателя Switch, на пин TOG подан 3.3В) считывается Arduino. Дисплей подключен через ограничительные резисторы для согласования 5В и 3.3В логики. Такое решение не совсем правильное, но уже работает несколько лет в разных устройствах.
Основная плата двухстороннего печатного монтажа. Металлизацию оставлял по максимуму, чтобы уменьшить влияние помех, а также для упрощения схемы сенсорных кнопок (для TTP223 требуется конденсатор по входу на землю для уменьшения чувствительности. Без него кнопка будет срабатывать просто при приближении пальца. Но так как у меня сделана сплошная металлизация этот конденсатор не требуется). Сделан вырез под дисплей.

Фото платы без деталей



На верхней стороне находятся площадки сенсорных кнопок, наклеена лицевая панель, припаивается дисплей. Площадки сенсорных кнопок и дисплей подключены к нижней стороне через перемычки тонким проводом. Типоразмер резисторов и конденсатора 0603.
Изготовление лицевой панели
Лицевую панель, по размерам из 3Д модели, я сначала нарисовал в программе FrontDesigner-3.0_rus, в файлах проекта лежит исходник.

Распечатал, вырезал по контуру, а также окно для дисплея.
Далее заламинировал самоклеящейся пленкой для ламинирования и приклеил к плате. Дисплей за также приклеен к этой пленке. За счет выреза в плате дисплей получился вровень с основной платой.

На нижней стороне находится Arduino Pro mini и микросхемы сенсорных кнопок TTP223.
2. Плата усилителей.

Схема паяльника состоит из дифференциального усилителя с резистивным мостом и полевого транзистора с обвязкой.
1. Для увеличения «полезного» диапазона выходного сигнала при низкоомном терморезисторе (в моем случае в китайской копии Hakko A1321 56 Ом при 25 градусах, для сравнения в 3д принтерах обычно стоит терморезистор сопротивлением 100 кОм при 25 градусах) применен резистивный мост и дифференциальный усилитель. Для уменьшения наводок параллельно терморезистору и в цепи обратной связи стоят конденсаторы. Данная схема нужна только для терморезистора, если в вашем паяльнике стоит термопара, то нужна схема усилителя аналогичной в схеме фена. Настройка не требуется. Только измерить сопротивление вашего терморезистора при 25 градусах и поменять при необходимости резистор 56Ом на измеренный.
2. Полевой транзистор был выпаян из материнской платы. Резистор 100 кОм нужен чтобы паяльник сам не включился от наводок если ардуина например отключится, заземляет затвор полевого транзистора. Резисторы по 220 Ом для ограничения тока заряда затвора.
Схема фена состоит из неинвертирующего усилителя и полевого транзистора.
1. Усилитель: типовая схема. Для уменьшения наводок параллельно термопаре и в цепи обратной связи стоят конденсаторы.
2. Обвязки у полевого транзистора ME9926 нет, это не случайно. Включение ничем не грозит, просто будет крутится вентилятор. Ограничения тока заряда затвора тоже нет, так как емкость затвора небольшая.
Типоразмер резисторов и конденсаторов 0603, за исключением резистора 56 Ом — 1206.,
Настройка не требуется.
Нюансы: применение операционного усилителя LM321 (одноканальный аналог LM358) для дифферециального усилителя не является оптимальным, так как это не Rail-to-Rail операционный усилитель, и максимальная амплитуда на выходе будет ограничена 3.5-4 В и максимальная температура (при указанных на схеме номиналах) будет ограничена в районе 426 градусов. Рекомендую использовать например MCP6001. Но нужно обратить внимание что в зависимости от букв в конце отличается распиновка:

3. Плата симисторного модуля.

Схема стандартная с оптосимистором MOC3063. Так как MOC3063 сама определяет переход через ноль напряжения сети 220В, а нагрузка — нагреватель инерционный элемент, использовать фазовое управление нет смысла, как и дополнительных цепей контроля ноля.
4. Блок питания.
Выбор был сделан по габаритным размерам и выходной мощности в первую очередь. Также я немного увеличил выходное напряжение до 20В. Можно было и 22В сделать, но при включении паяльника срабатывала защита БП.
5. Корпус.
Корпус проектировался под мой БП, с учетом размеров плат и последующей печати на 3Д принтере. Металлический даже не планировался, приличный алюминиевый анодированный корпус дороговато и царапается, и куча других нюансов. А гнуть самому красиво не получится.
Разъемы:
1. Фен — «авиационный» GX16-8.
2. Паяльник — «авиационный» GX12-6.
Немного фото

Исходники лежат тут.
На этом все.
P.S. Первую версию я сохранил в черновиках на память.

Let's block ads! (Why?)

Беспроводной датчик открытия и закрытия с расширенным функционалом

Приветствую всех читателей Хабра и особенно читателей раздела «DIY или Сделай сам»! А не придумать ли чего нибудь такого-растакого, я же ардуиншик, мне можно,… главное тему управления лифтовыми кабинами не трогать :). После недолгих размышлений почему то захотелось сделать датчик открытия и закрытия. Данный датчик как и остальные мои поделки которые я делаю в последнее время базируется на чипах компании Nordic Semiconductor. Датчик решил делать в двух версиях, одну на чипе nRF52840, а вторую на чипе nRF52811.

Для версии на чипе nRF52840 был использован модуль E73_2G4M08S1C компании EBYTE, для верcии на чипе nRF52811 модуль MC50SFA компании MINEW. Чесно говоря поиски доступных чипов nRF52811 это было то еще приключение. Но в итоге этого приключения в устройстве модуль на чипе nRF52811 от компании MINEW и плюшки в виде нескольких вариантов чипов напаиваемых на эти модули — nRF52810 и nRF52832.

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

Ардуино схема:

Обдумывая чем бы было уместно разбавить основной функционал данного датчика открытия и закрытия, решил посмотреть а что же есть по этому поводу на рынке. Как оказалось практически ничего, датчик открытия и закрытия он и в Африке датчик открытия и закрытия. Наиболее «продвинутое» решение нашлось у компании REDMOND. В их BLE датчике (кстати тоже на чипе от компании Nordic) в дополнение к герконовому сенсору присутствует датчик температуры и емкосная кнопка реализованная на микросхеме TTP223. Но мне почему то это показалось не совсем удачным решением, чем полезны показания температуры около двери или окна(и что мешало ее мерять чипом) и в каких ситуация уместно использовать кнопку на датчике висящем на окне или двери(ну разве что входной :)). В итоге решил расширить охранные функции у моего датчика.

Основным критерием отбора было потребление дополнительных сенсоров, так как в данном датчике решено было использовать батарейку CR2032. Победителями среди кандидатов стали два датчика, акселерометр LIS2DW12 и датчик магнитного поля DRV5032FB.
LIS2DW12 на данный момент является наверное самым экономичным акселерометром. В режиме низкого потребления данный акселерометр потребляет 1 мкА (даташит). Так же просто отличные характеристики по потреблению показал датчик магнитного поля DRV5032FB. Его потребление составляет в районе 500нА (даташит).

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

Програмная часть проекта была сделана для работы датчика в сети Майсенсорс. По крайней мере пока это так. Майсенсорс в варианте работы на чипах компании Nordic(nRF24(+atmega 328, stm32f1), nRF51 и nRF52) на нижнем уровне использует проприетарный протокол компании Nordic — Enhanced ShockBurst (ESB), тем самым обеспечивается совместимость устройств на nRF24 и nRF51-52. Майсенсорс это открытый Ардуино проект вокруг которого уже образовалось довольно большое сообщество во многих странах мира. Но чем хороши решения на чипах nRF52 так это тем что использвать Майсенсорс(ESB) совсем не обязательно. Достаточно просто заменит ПО в основе которого работа на протоколе Zigbee или BLE, так как чипы мультипротокольные.… По поводу BLE, немного отвлекусь, посмотрите какое замечательное Arduino NANO 33 Ble можно сделать из модуля E73_2G4M08S1C, стоимость моей NANO 33 — $4.

Скетч к датчику делал в Ардуино ИДЕ из дополнительных библиотек была использована библиотека для акселерометра LIS2DW12, немного измененная мною в части дефолтных настроек регистров, в моем варианте она работает сразу с настройками самого низкого варианта энергопотребления(доступна на моем гите).
Немного опишу логику работы программы. В основном режиме работы датчик находится во сне с настроенными внешними прерываниями, всего 4 прерывания. Есть две конфигурации прерываний, конфиги перенастраивают прерывания в процессе работы программы в зависимости в каком состоянии находится герконовый датчик. Если дверь открыта то прерывания для датчика удара и датчика магитного поля отключаются. Как только дверь закрывется прерывания для двух данных сенсоров активируются. Так же столкнулся с тем что во время открытия возникали ситуации когда датчик удара срабатывал раньше герконового датчика, это происходило от вибраций во время открытия замка. Данная неприятность фиксировалась только при настроеной высокой чувствительности акселерометра.
Для устранения этой проблемы было введено ожидание в 2 секунды при срабатывании акселерометра, во время которого мониторится пин герконового датчика. Если во время ожидания происходит изменение уровня на пине герконового датчика, то дальнейшая обработка события по прерыванию от акселерометра останавливается и начинается обработка события от геркона.
Датчик имеет режим конфигурирования. При нажатии на сервисную кнопку датчик просыпается по прерыванию, радиомодуль включается в режим прослушивания и ждет входящих команд с контроллера УД. Если команда получена, датчик записывает в память новое значение и переключается в рабочий режим сразу уходя в сон. Для отправки следующей команды процедуру активирования режима конфигурирования необходимо повторить. Если находясь в режиме конфигурирования датчик в течении 30 секунд ничего не получает то также по истечению этого времени переключается в рабочий режим и уходит в сон. Помимо режима конфигурации с сервисной кнопки можно запустить презентацию сенсоров датчика и заводской сброс настроек(датчик забывает сеть в которую добавлен, регистрацию датчика после сброса настроек необходимо пройти заново).

Для программирования датчика в Ардуино ИДЕ необходимо добавить поддержку следующих плат:

sandeepmistry/arduino-nRF5
mysensors/ArduinoBoards
Библиотеки:
Mysensor
LIS2DW12

Программатор: st-link, j-link.

Скетч программы
bool configMode = 0;
int8_t int_status = 0;
bool door_status = 1;
bool check;
bool magnet_status = 1;
bool nosleep = 0;
bool button_flag = 0;
bool onoff = 1;
bool flag_update_transport_param;
bool flag_sendRoute_parent;
bool flag_no_present;
bool flag_nogateway_mode;
bool flag_find_parent_process;
bool flag_fcount;
bool Ack_TL;
bool Ack_FP;
bool PRESENT_ACK;
bool send_a;
bool batt_flag;
byte conf_vibro_set = 2;
byte err_delivery_beat;
byte problem_mode_count;
uint8_t  countbatt = 0;
uint8_t batt_cap;
uint8_t old_batt_cap = 100;
uint32_t BATT_TIME;
uint32_t SLEEP_TIME = 10800000;
uint32_t SLEEP_NOGW = 60000;
uint32_t oldmillis;
uint32_t newmillis;
uint32_t previousMillis;
uint32_t lightMillisR;
uint32_t configMillis;
uint32_t interrupt_time;
uint32_t SLEEP_TIME_W;
uint32_t axel_time;
uint32_t axel_time0;
int16_t myid;
int16_t mypar;
int16_t old_mypar = -1;
bool vibro = 1;
uint32_t PIN_BUTTON_MASK;
uint32_t AXEL_INT_MASK;
uint32_t GERKON_INT_MASK;
uint32_t MAGNET_INT_MASK;
float ODR_1Hz6_LP_ONLY = 1.6f;
float ODR_12Hz5 = 12.5f;
float ODR_25Hz = 25.0f;
float ODR_50Hz = 50.0f;
float ODR_100Hz = 100.0f;
float ODR_200Hz = 200.0f;
volatile byte axelIntStatus = 0;
volatile byte gerkIntStatus = 0;
volatile byte magIntStatus = 0;
volatile byte buttIntStatus = 0;
uint16_t batteryVoltage;
int16_t linkQuality;
int16_t old_linkQuality;

//#define MY_DEBUG
#ifndef MY_DEBUG
#define MY_DISABLED_SERIAL
#endif
#define MY_RADIO_NRF5_ESB
int16_t mtwr;
#define MY_TRANSPORT_WAIT_READY_MS (mtwr)
#define MY_NRF5_ESB_PA_LEVEL (NRF5_PA_MAX)

#include <MySensors.h>

extern "C" {
#include "app_gpiote.h"
#include "nrf_gpio.h"
}
#define APP_GPIOTE_MAX_USERS 1
static app_gpiote_user_id_t m_gpiote_user_id;

#include <LIS2DW12Sensor.h>
LIS2DW12Sensor *lis2;

#define DWS_CHILD_ID 0
#define V_SENS_CHILD_ID 1
#define M_CHILD_ID 2
#define LEVEL_SENSIV_V_SENS_CHILD_ID 230
#define SIGNAL_Q_ID 250

MyMessage dwsMsg(DWS_CHILD_ID, V_TRIPPED);
MyMessage mMsg(M_CHILD_ID, V_TRIPPED);
MyMessage vibroMsg(V_SENS_CHILD_ID, V_TRIPPED);
MyMessage conf_vsensMsg(LEVEL_SENSIV_V_SENS_CHILD_ID, V_VAR1);
#define SN "DOOR & WINDOW SENS"
#define SV "1.12"


void before() {
  board_Init();
  happy_init();
  delay(500);
  batteryVoltage = hwCPUVoltage();
  digitalWrite(BLUE_LED, LOW);
}


void presentation()
{
  NRF_POWER->DCDCEN = 0;
  wait(10);

  check = sendSketchInfo(SN, SV);
  wait(30);
  if (!check) {
    _transportSM.failedUplinkTransmissions = 0;
    wait(30);
    check = sendSketchInfo(SN, SV);
    wait(30);
    _transportSM.failedUplinkTransmissions = 0;
  }
  if (check) {
    blinky(1, 1, BLUE_LED);
  } else {
    blinky(1, 1, RED_LED);
  }

  check = present(DWS_CHILD_ID, S_DOOR, "STATUS RS SENS");
  wait(40);
  if (!check) {
    _transportSM.failedUplinkTransmissions = 0;
    wait(40);
    check = present(DWS_CHILD_ID, S_DOOR, "STATUS RS SENS");
    wait(40);
    _transportSM.failedUplinkTransmissions = 0;
  }
  if (check) {
    blinky(1, 1, BLUE_LED);
  } else {
    blinky(1, 1, RED_LED);
  }

  check = present(V_SENS_CHILD_ID, S_VIBRATION, "STATUS SHOCK SENS");
  wait(50);
  if (!check) {
    _transportSM.failedUplinkTransmissions = 0;
    wait(50);
    check = present(V_SENS_CHILD_ID, S_VIBRATION, "STATUS SHOCK SENS");
    wait(50);
    _transportSM.failedUplinkTransmissions = 0;
  }
  if (check) {
    blinky(1, 1, BLUE_LED);
  } else {
    blinky(1, 1, RED_LED);
  }

  check = present(M_CHILD_ID, S_DOOR, "ANTI-MAGNET ALARM");
  wait(60);
  if (!check) {
    _transportSM.failedUplinkTransmissions = 0;
    wait(60);
    check = present(M_CHILD_ID, S_DOOR, "ANTI-MAGNET ALARM");
    wait(60);
    _transportSM.failedUplinkTransmissions = 0;
  }
  if (check) {
    blinky(1, 1, BLUE_LED);
  } else {
    blinky(1, 1, RED_LED);
  }

  check = present(SIGNAL_Q_ID, S_CUSTOM, "SIGNAL %");
  wait(70);
  if (!check) {
    _transportSM.failedUplinkTransmissions = 0;
    wait(70);
    check = present(SIGNAL_Q_ID, S_CUSTOM, "SIGNAL %");
    wait(70);
    _transportSM.failedUplinkTransmissions = 0;
  }
  if (check) {
    blinky(1, 1, BLUE_LED);
  } else {
    blinky(1, 1, RED_LED);
  }

  check = present(LEVEL_SENSIV_V_SENS_CHILD_ID, S_CUSTOM, "SENS LEVEL VIBRO");
  wait(80);
  if (!check) {
    _transportSM.failedUplinkTransmissions = 0;
    wait(80);
    check = present(LEVEL_SENSIV_V_SENS_CHILD_ID, S_CUSTOM, "SENS LEVEL VIBRO");
    wait(80);
    _transportSM.failedUplinkTransmissions = 0;
  }
  if (check) {
    blinky(1, 1, BLUE_LED);
  } else {
    blinky(1, 1, RED_LED);
  }

  check = send(conf_vsensMsg.set(conf_vibro_set));
  wait(90);
  if (!check) {
    _transportSM.failedUplinkTransmissions = 0;
    wait(90);
    check = send(conf_vsensMsg.set(conf_vibro_set));
    wait(90);
    _transportSM.failedUplinkTransmissions = 0;
  }
  if (check) {
    blinky(1, 1, BLUE_LED);
  } else {
    blinky(1, 1, RED_LED);
  }
  NRF_POWER->DCDCEN = 0;
  wait(10);
}


void setup() {
  digitalWrite(BLUE_LED, HIGH);
  config_Happy_node();
  sensors_Init();
}


void loop() {
  if (flag_update_transport_param == 1) {
    update_Happy_transport();
  }
  if (flag_sendRoute_parent == 1) {
    present_only_parent();
  }
  if (isTransportReady() == true) {
    if (flag_nogateway_mode == 0) {
      if (flag_find_parent_process == 1) {
        find_parent_process();
      }
      if (configMode == 0) {
        if ((axelIntStatus == AXEL_INT) || (buttIntStatus == PIN_BUTTON) || (gerkIntStatus == GERKON_INT) || (magIntStatus == MAGNET_INT)) {
          nosleep = 1;
          newmillis = millis();
          interrupt_time = newmillis - oldmillis;
          BATT_TIME = BATT_TIME - interrupt_time;
          if (BATT_TIME < 60000) {
            BATT_TIME = SLEEP_TIME;
            batteryVoltage = hwCPUVoltage();
            batt_flag = 1;
          }

          if (gerkIntStatus == GERKON_INT) {
            send_Gerkon();
            axel_time = millis();
            nosleep = 0;
          }

          if (magIntStatus == MAGNET_INT) {
            send_Magnet();
            nosleep = 0;
          }

          if (axelIntStatus == AXEL_INT) {
            if (millis() - axel_time0 >= 2000) {
              send_Axel();
              nosleep = 0;
            } else {
              if (digitalRead(GERKON_INT) == LOW) {
                send_Gerkon();
                axel_time = millis();
                nosleep = 0;
              }
            }
          }

          if (buttIntStatus == PIN_BUTTON) {
            if (digitalRead(PIN_BUTTON) == 0 && button_flag == 0) {
              button_flag = 1;
              previousMillis = millis();
              ledsOff();
            }
            if (digitalRead(PIN_BUTTON) == 0 && button_flag == 1) {
              if ((millis() - previousMillis > 0) && (millis() - previousMillis <= 1750)) {
                if (millis() - lightMillisR > 70) {
                  lightMillisR = millis();
                  onoff = !onoff;
                  digitalWrite(BLUE_LED, onoff);
                }
              }
              if ((millis() - previousMillis > 1750) && (millis() - previousMillis <= 2000)) {
                ledsOff();
              }
              if ((millis() - previousMillis > 2000) && (millis() - previousMillis <= 3750)) {
                if (millis() - lightMillisR > 50) {
                  lightMillisR = millis();
                  onoff = !onoff;
                  digitalWrite(GREEN_LED, onoff);
                }
              }
              if ((millis() - previousMillis > 3750) && (millis() - previousMillis <= 4000)) {
                ledsOff();
              }
              if ((millis() - previousMillis > 4000) && (millis() - previousMillis <= 5750)) {
                if (millis() - lightMillisR > 30) {
                  lightMillisR = millis();
                  onoff = !onoff;
                  digitalWrite(RED_LED, onoff);
                }
              }
              if (millis() - previousMillis > 5750) {
                ledsOff();
              }
            }

            if (digitalRead(PIN_BUTTON) == 1 && button_flag == 1) {
              if ((millis() - previousMillis <= 1750) && (button_flag == 1))
              {
                ledsOff();
                blinky(2, 2, BLUE_LED);
                button_flag = 0;
                buttIntStatus = 0;
                presentation();
                nosleep = 0;
              }
              if ((millis() - previousMillis > 2000) && (millis() - previousMillis <= 3750) && (button_flag == 1))
              {
                ledsOff();
                blinky(2, 2, GREEN_LED);
                configMode = 1;
                button_flag = 0;
                configMillis = millis();
                interrupt_Init(1);
                NRF_POWER->DCDCEN = 0;
                buttIntStatus = 0;
                NRF5_ESB_startListening();
                wait(50);
              }

              if ((millis() - previousMillis > 4000) && (millis() - previousMillis <= 5750) && (button_flag == 1))
              {
                ledsOff();
                blinky(3, 3, RED_LED);
                //new_device();
              }

              if ((((millis() - previousMillis > 1750) && (millis() - previousMillis <= 2000)) || ((millis() - previousMillis > 3750) && (millis() - previousMillis <= 4000)) || ((millis() - previousMillis > 5750))) && (button_flag == 1))
              {
                ledsOff();
                nosleep = 0;
                button_flag = 0;
                buttIntStatus = 0;
              }
            }
          }
        } else {
          batteryVoltage = hwCPUVoltage();
          BATT_TIME = SLEEP_TIME;
          sendBatteryStatus(1);
          nosleep = 0;
        }
      } else {
        if (millis() - configMillis > 30000) {
          blinky(3, 3, GREEN_LED);
          configMode = 0;
          nosleep = 0;
          interrupt_Init(0);
          NRF_POWER->DCDCEN = 1;
          wait(50);
        }
      }
    } else {
      if (buttIntStatus == PIN_BUTTON) {
        if (digitalRead(PIN_BUTTON) == 0 && button_flag == 0) {
          button_flag = 1;
          nosleep = 1;
          previousMillis = millis();
          ledsOff();
        }
        if (digitalRead(PIN_BUTTON) == 0 && button_flag == 1) {
          if ((millis() - previousMillis > 0) && (millis() - previousMillis <= 1750)) {
            if (millis() - lightMillisR > 25) {
              lightMillisR = millis();
              onoff = !onoff;
              digitalWrite(GREEN_LED, onoff);
            }
          }
          if ((millis() - previousMillis > 1750) && (millis() - previousMillis <= 2000)) {
            ledsOff();
          }
          if ((millis() - previousMillis > 2000) && (millis() - previousMillis <= 4000)) {
            if (millis() - lightMillisR > 25) {
              lightMillisR = millis();
              onoff = !onoff;
              digitalWrite(RED_LED, onoff);
            }
          }
          if (millis() - previousMillis > 4000) {
            ledsOff();
          }
        }

        if (digitalRead(PIN_BUTTON) == 1 && button_flag == 1) {
          if ((millis() - previousMillis <= 1750) && (button_flag == 1))
          {
            ledsOff();
            blinky(2, 2, BLUE_LED);
            button_flag = 0;
            buttIntStatus = 0;
            check_parent();
            nosleep = 0;
          }
          if ((millis() - previousMillis > 2000) && (millis() - previousMillis <= 4000) && (button_flag == 1))
          {
            ledsOff();
            blinky(3, 3, RED_LED);
            //new_device();
          }

          if ((((millis() - previousMillis > 1750) && (millis() - previousMillis <= 2000)) || ((millis() - previousMillis > 4000))) && (button_flag == 1))
          {
            ledsOff();
            nosleep = 0;
            button_flag = 0;
            buttIntStatus = 0;
          }
        }
      } else {
        check_parent();
      }
    }
  }

  if (_transportSM.failureCounter > 0)
  {
    _transportConfig.parentNodeId = loadState(101);
    _transportConfig.nodeId = myid;
    _transportConfig.distanceGW = loadState(103);
    mypar = _transportConfig.parentNodeId;
    nosleep = 0;
    flag_fcount = 1;
    err_delivery_beat = 6;
    happy_node_mode();
    gateway_fail();
  }

  if (nosleep == 0) {
    oldmillis = millis();
    axelIntStatus = 0;
    buttIntStatus = 0;
    gerkIntStatus = 0;
    magIntStatus = 0;
    sleep(SLEEP_TIME_W, false);
    nosleep = 1;
  }
}


void blinky(uint8_t pulses, uint8_t repit, uint8_t ledColor) {
  for (int x = 0; x < repit; x++) {
    if (x > 0) {
      wait(150);
    }
    for (int i = 0; i < pulses; i++) {
      if (i > 0) {
        wait(40);
      }
      digitalWrite(ledColor, LOW);
      wait(10);
      digitalWrite(ledColor, HIGH);
    }
  }
}


void board_Init() {
  pinMode(PIN_BUTTON, INPUT_PULLUP);
  pinMode(MAGNET_INT, INPUT);
  pinMode(GERKON_INT, INPUT);
  pinMode(AXEL_INT, INPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);
  ledsOff();
  NRF_POWER->DCDCEN = 1;
  wait(5);
#ifndef MY_DEBUG
  NRF_UART0->ENABLE = 0;
  wait(5);
#endif
  //NRF_NFCT->TASKS_DISABLE = 1;
  // NRF_NVMC->CONFIG = 1;
  // NRF_UICR->NFCPINS = 0;
  // NRF_NVMC->CONFIG = 0;
  // NRF_SAADC ->ENABLE = 0;
  // NRF_PWM0  ->ENABLE = 0;
  // NRF_PWM1  ->ENABLE = 0;
  // NRF_PWM2  ->ENABLE = 0;
  // NRF_TWIM1 ->ENABLE = 0;
  // NRF_TWIS1 ->ENABLE = 0;
  NRF_RADIO->TXPOWER = 8;
  wait(5);

  conf_vibro_set = loadState(230);
  if ((conf_vibro_set > 5) || (conf_vibro_set == 0)) {
    conf_vibro_set = 2;
    saveState(230, conf_vibro_set);
  }

  blinky(1, 1, BLUE_LED);
}


void ledsOff() {
  digitalWrite(RED_LED, HIGH);
  digitalWrite(GREEN_LED, HIGH);
  digitalWrite(BLUE_LED, HIGH);
}


void happy_init() {
  //hwWriteConfig(EEPROM_NODE_ID_ADDRESS, 255); // ******************** checking the node config reset *************************

  if (hwReadConfig(EEPROM_NODE_ID_ADDRESS) == 0) {
    hwWriteConfig(EEPROM_NODE_ID_ADDRESS, 255);
  }
  if (loadState(100) == 0) {
    saveState(100, 255);
  }
  CORE_DEBUG(PSTR("EEPROM NODE ID: %d\n"), hwReadConfig(EEPROM_NODE_ID_ADDRESS));
  CORE_DEBUG(PSTR("USER MEMORY SECTOR NODE ID: %d\n"), loadState(100));

  if (hwReadConfig(EEPROM_NODE_ID_ADDRESS) == 255) {
    mtwr = 0;
  } else {
    mtwr = 11000;
    no_present();
  }
  CORE_DEBUG(PSTR("MY_TRANSPORT_WAIT_MS: %d\n"), mtwr);
}

void no_present() {
  _coreConfig.presentationSent = true;
  _coreConfig.nodeRegistered = true;
}


void interrupt_Init(bool start) {
  //***
  //SET
  //NRF_GPIO_PIN_NOPULL
  //NRF_GPIO_PIN_PULLUP
  //NRF_GPIO_PIN_PULLDOWN
  //***
  nrf_gpio_cfg_input(PIN_BUTTON, NRF_GPIO_PIN_PULLUP);
  nrf_gpio_cfg_input(AXEL_INT, NRF_GPIO_PIN_NOPULL);
  nrf_gpio_cfg_input(GERKON_INT, NRF_GPIO_PIN_NOPULL);
  nrf_gpio_cfg_input(MAGNET_INT, NRF_GPIO_PIN_NOPULL);
  APP_GPIOTE_INIT(APP_GPIOTE_MAX_USERS);
  PIN_BUTTON_MASK = 1 << PIN_BUTTON;
  AXEL_INT_MASK = 1 << AXEL_INT;
  GERKON_INT_MASK = 1 << GERKON_INT;
  MAGNET_INT_MASK = 1 << MAGNET_INT;
  //  app_gpiote_user_register(p_user_id, pins_low_to_high_mask, pins_high_to_low_mask, event_handler)
  if (start == 0) {
    app_gpiote_user_register(&m_gpiote_user_id, AXEL_INT_MASK | GERKON_INT_MASK, GERKON_INT_MASK | MAGNET_INT_MASK | PIN_BUTTON_MASK, gpiote_event_handler);
    wait(5);
  } else if (start == 1) {
    app_gpiote_user_register(&m_gpiote_user_id, GERKON_INT_MASK, GERKON_INT_MASK | MAGNET_INT_MASK | PIN_BUTTON_MASK, gpiote_event_handler);
    wait(5);
  }
  app_gpiote_user_enable(m_gpiote_user_id);
  wait(5);
  axelIntStatus = 0;
  buttIntStatus = 0;
  gerkIntStatus = 0;
  magIntStatus = 0;
}


void gpiote_event_handler(uint32_t event_pins_low_to_high, uint32_t event_pins_high_to_low)
{
  MY_HW_RTC->CC[0] = (MY_HW_RTC->COUNTER + 2); // Taken from d0016 example code, ends the sleep delay

  if (PIN_BUTTON_MASK & event_pins_high_to_low) {
    if ((buttIntStatus == 0) && (axelIntStatus == 0) && (gerkIntStatus == 0) && (magIntStatus == 0)) {
      buttIntStatus = PIN_BUTTON;
    }
  }
  if (flag_nogateway_mode == 0) {
    if (AXEL_INT_MASK & event_pins_low_to_high) {
      if ((axelIntStatus == 0) && (buttIntStatus == 0) && (gerkIntStatus == 0) && (magIntStatus == 0) && (door_status == 1)) {
        axelIntStatus = AXEL_INT;
        axel_time0 = millis();
      }
    }
    if ((GERKON_INT_MASK & event_pins_low_to_high) || (GERKON_INT_MASK & event_pins_high_to_low)) {
      if ((axelIntStatus == 0) && (buttIntStatus == 0) && (gerkIntStatus == 0) && (magIntStatus == 0)) {
        gerkIntStatus = GERKON_INT;
      }
    }
    if (MAGNET_INT_MASK & event_pins_high_to_low) {
      if ((axelIntStatus == 0) && (buttIntStatus == 0) && (gerkIntStatus == 0) && (magIntStatus == 0) && (door_status == 1)) {
        magIntStatus = MAGNET_INT;
      }
    }
  }
}


void sensors_Init() {
  Wire.begin();
  wait(100);
  lis2 = new LIS2DW12Sensor (&Wire);
  vibro_Init();
  if (flag_nogateway_mode == 0) {
    if (digitalRead(GERKON_INT) == HIGH) {
      door_status = 1;
      interrupt_Init(0);
    } else {
      door_status = 0;
      interrupt_Init(1);
    }
    send(dwsMsg.set(door_status));
    wait(50);

    SLEEP_TIME_W = SLEEP_TIME;
    axelIntStatus = 0;
    buttIntStatus = 0;
    gerkIntStatus = 0;
    magIntStatus = 0;
    sendBatteryStatus(0);
    wait(100);
    blinky(2, 1, BLUE_LED);
    wait(100);
    blinky(2, 1, GREEN_LED);
    wait(100);
    blinky(2, 1, RED_LED);
    axel_time = millis();
  } else {
    interrupt_Init(0);
    blinky(5, 3, RED_LED);
  }
}


void config_Happy_node() {
  if (mtwr == 0) {
    myid = getNodeId();
    saveState(100, myid);
    mypar = _transportConfig.parentNodeId;
    old_mypar = mypar;
    saveState(101, mypar);
    saveState(102, _transportConfig.distanceGW);
  }
  if (mtwr != 0) {
    myid = getNodeId();
    if (myid != loadState(100)) {
      saveState(100, myid);
    }
    if (isTransportReady() == true) {
      mypar = _transportConfig.parentNodeId;
      if (mypar != loadState(101)) {
        saveState(101, mypar);
      }
      if (_transportConfig.distanceGW != loadState(102)) {
        saveState(102, _transportConfig.distanceGW);
      }
      present_only_parent();
    }
    if (isTransportReady() == false)
    {
      no_present();
      flag_fcount = 1;
      err_delivery_beat = 6;
      _transportConfig.nodeId = myid;
      _transportConfig.parentNodeId = loadState(101);
      _transportConfig.distanceGW = loadState(102);
      mypar = _transportConfig.parentNodeId;
      happy_node_mode();
      gateway_fail();
    }
  }
}


void send_Axel() {
  if (millis() - axel_time >= 5000) {
    blinky(2, 1, GREEN_LED);
    blinky(2, 1, RED_LED);
    blinky(2, 1, GREEN_LED);
    blinky(2, 1, RED_LED);
    blinky(2, 1, GREEN_LED);
    blinky(2, 1, RED_LED);

    send_a = send(vibroMsg.set(vibro));
    wait(50);
    if (send_a == false) {
      send_a = send(vibroMsg.set(vibro));
      wait(100);
    }
    if (send_a == true) {
      err_delivery_beat = 0;
      if (flag_nogateway_mode == 1) {
        flag_nogateway_mode = 0;
        CORE_DEBUG(PSTR("MyS: NORMAL GATEWAY MODE\n"));
        err_delivery_beat = 0;
      }
    } else {
      _transportSM.failedUplinkTransmissions = 0;
      if (err_delivery_beat < 6) {
        err_delivery_beat++;
      }
      if (err_delivery_beat == 5) {
        if (flag_nogateway_mode == 0) {
          gateway_fail();
          CORE_DEBUG(PSTR("MyS: LOST GATEWAY MODE\n"));
        }
      }
    }
    axel_time = millis();
    axelIntStatus = 0;
    nosleep = 0;
  } else {
    axelIntStatus = 0;
    nosleep = 0;
  }
}


void send_Gerkon() {
  if (digitalRead(GERKON_INT) == HIGH) {
    door_status = 1;
    interrupt_Init(0);
  } else {
    door_status = 0;
    interrupt_Init(1);
  }
  if (door_status == 1) {
    blinky(1, 1, GREEN_LED);
  } else {
    blinky(1, 1, RED_LED);
  }
  send_a = send(dwsMsg.set(door_status));
  wait(50);
  if (send_a == false) {
    send_a = send(dwsMsg.set(door_status));
    wait(100);
    if (send_a == false) {
      send_a = send(dwsMsg.set(door_status));
      wait(150);
    }
  }
  if (send_a == true) {
    err_delivery_beat = 0;
    if (flag_nogateway_mode == 1) {
      flag_nogateway_mode = 0;
      CORE_DEBUG(PSTR("MyS: NORMAL GATEWAY MODE\n"));
      err_delivery_beat = 0;
    }
  } else {
    _transportSM.failedUplinkTransmissions = 0;
    if (err_delivery_beat < 6) {
      err_delivery_beat++;
    }
    if (err_delivery_beat == 5) {
      if (flag_nogateway_mode == 0) {
        gateway_fail();
        CORE_DEBUG(PSTR("MyS: LOST GATEWAY MODE\n"));
      }
    }
  }
  gerkIntStatus = 0;
  nosleep = 0;
}


void send_Magnet() {
  blinky(2, 1, BLUE_LED);
  blinky(2, 1, RED_LED);
  blinky(2, 1, BLUE_LED);
  blinky(2, 1, RED_LED);
  blinky(2, 1, BLUE_LED);
  blinky(2, 1, RED_LED);
  send_a = send(mMsg.set(magnet_status));
  wait(50);
  if (send_a == false) {
    send_a = send(mMsg.set(magnet_status));
    wait(100);
  }
  if (send_a == true) {
    err_delivery_beat = 0;
    if (flag_nogateway_mode == 1) {
      flag_nogateway_mode = 0;
      CORE_DEBUG(PSTR("MyS: NORMAL GATEWAY MODE\n"));
      err_delivery_beat = 0;
    }
  } else {
    _transportSM.failedUplinkTransmissions = 0;
    if (err_delivery_beat < 6) {
      err_delivery_beat++;
    }
    if (err_delivery_beat == 5) {
      if (flag_nogateway_mode == 0) {
        gateway_fail();
        CORE_DEBUG(PSTR("MyS: LOST GATEWAY MODE\n"));
      }
    }
  }
  magIntStatus = 0;
  nosleep = 0;
}


void new_device() {
  hwWriteConfig(EEPROM_NODE_ID_ADDRESS, 255);
  saveState(100, 255);
  wdt_enable(WDTO_15MS);
}


void update_Happy_transport() {
  CORE_DEBUG(PSTR("MyS: UPDATE TRANSPORT CONFIGURATION\n"));
  mypar = _transportConfig.parentNodeId;
  if (mypar != loadState(101))
  {
    saveState(101, mypar);
  }
  if (_transportConfig.distanceGW != loadState(102))
  {
    saveState(102, _transportConfig.distanceGW);
  }
  present_only_parent();
  wait(50);
  nosleep = 0;
  flag_update_transport_param = 0;
}


void present_only_parent() {
  if (old_mypar != mypar) {
    CORE_DEBUG(PSTR("MyS: SEND LITTLE PRESENT:) WITH PARENT ID\n"));
    if (_sendRoute(build(_msgTmp, 0, NODE_SENSOR_ID, C_INTERNAL, 6).set(mypar))) {
      flag_sendRoute_parent = 0;
      old_mypar = mypar;
    } else {
      flag_sendRoute_parent = 1;
    }
  }
}


void happy_node_mode() {
  _transportSM.findingParentNode = false;
  _transportSM.transportActive = true;
  _transportSM.uplinkOk = true;
  _transportSM.pingActive = false;
  _transportSM.failureCounter = 0;
  _transportSM.uplinkOk = true;
  _transportSM.failureCounter = 0u;
  _transportSM.failedUplinkTransmissions = 0u;
  transportSwitchSM(stReady);
  CORE_DEBUG(PSTR("TRANSPORT: %d\n"), isTransportReady());
}


void gateway_fail() {
  flag_nogateway_mode = 1;
  flag_update_transport_param = 0;
  SLEEP_TIME_W = SLEEP_NOGW;
}


void check_parent() {
  _transportSM.findingParentNode = true;
  CORE_DEBUG(PSTR("MyS: SEND FIND PARENT REQUEST, WAIT RESPONSE\n"));
  _sendRoute(build(_msg, 255, NODE_SENSOR_ID, C_INTERNAL, 7).set(""));
  wait(1500, C_INTERNAL, 8);
  if (_msg.sensor == 255) {
    if (mGetCommand(_msg) == 3) {
      if (_msg.type == 8) {
        Ack_FP = 1;
        CORE_DEBUG(PSTR("MyS: PARENT RESPONSE FOUND\n"));
      }
    }
  }
  if (Ack_FP == 1) {
    CORE_DEBUG(PSTR("MyS: FIND PARENT PROCESS\n"));
    Ack_FP = 0;
    transportSwitchSM(stParent);
    flag_nogateway_mode = 0;
    flag_find_parent_process = 1;
    problem_mode_count = 0;
  } else {
    _transportSM.findingParentNode = false;
    CORE_DEBUG(PSTR("MyS: PARENT RESPONSE NOT FOUND\n"));
    _transportSM.failedUplinkTransmissions = 0;
    CORE_DEBUG(PSTR("TRANSPORT: %d\n"), isTransportReady());
    nosleep = 0;
    if (problem_mode_count < 9) {
      CORE_DEBUG(PSTR("PROBLEM MODE COUNTER: %d\n"), problem_mode_count);
      problem_mode_count++;
      SLEEP_TIME_W = SLEEP_TIME_W + SLEEP_TIME_W;
    }
  }
}


void find_parent_process() {
  flag_update_transport_param = 1;
  flag_find_parent_process = 0;
  CORE_DEBUG(PSTR("MyS: STANDART TRANSPORT MODE IS RESTORED\n"));
  err_delivery_beat = 0;
  SLEEP_TIME_W = SLEEP_TIME;
  nosleep = 0;
}


void sendBatteryStatus(bool start) {
  batt_cap = battery_level_in_percent(batteryVoltage);
  if (start == 1) {
    //if (batt_cap < old_batt_cap) {
    sendBatteryLevel(battery_level_in_percent(batteryVoltage), 1);
    wait(1500, C_INTERNAL, I_BATTERY_LEVEL);
    old_batt_cap = batt_cap;
    // }
  } else {
    sendBatteryLevel(battery_level_in_percent(batteryVoltage), 1);
    wait(1500, C_INTERNAL, I_BATTERY_LEVEL);
  }

  linkQuality = calculationRxQuality();
  if (linkQuality != old_linkQuality) {
    wait(10);
    sendSignalStrength(linkQuality);
    wait(50);
    old_linkQuality = linkQuality;
  }
}


bool sendSignalStrength(const int16_t level, const bool ack)
{
  return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, SIGNAL_Q_ID, C_SET, V_VAR1,
                          ack).set(level));
}
int16_t calculationRxQuality() {
  int16_t nRFRSSI_temp = transportGetReceivingRSSI();
  int16_t nRFRSSI = map(nRFRSSI_temp, -85, -40, 0, 100);
  if (nRFRSSI < 0) {
    nRFRSSI = 0;
  }
  if (nRFRSSI > 100) {
    nRFRSSI = 100;
  }
  return nRFRSSI;
}


void receive(const MyMessage & message)
{
  if (message.sensor == LEVEL_SENSIV_V_SENS_CHILD_ID) {
    if (message.type == V_VAR1) {
      conf_vibro_set = message.getByte();
      vibro_Init();
      saveState(230, conf_vibro_set);
      wait(200);
      send(conf_vsensMsg.set(conf_vibro_set));
      wait(200);
      blinky(3, 3, GREEN_LED);
      configMode = 0;
      nosleep = 0;
    }
  }
}


void vibro_Init() {
  if (conf_vibro_set == 1) {
    lis2->ODRTEMP = ODR_1Hz6_LP_ONLY;
  }
  if (conf_vibro_set == 2) {
    lis2->ODRTEMP = ODR_12Hz5;
  }
  if (conf_vibro_set == 3) {
    lis2->ODRTEMP = ODR_25Hz;
  }
  if (conf_vibro_set == 4) {
    lis2->ODRTEMP = ODR_100Hz;
  }
  if (conf_vibro_set == 5) {
    lis2->ODRTEMP = ODR_200Hz;
  }
  lis2->Enable_X();
  wait(100);
  lis2->Enable_Wake_Up_Detection();
  wait(100);
}

Полный список файлов проекта доступен на гите

В качестве системы УД я уже давно использую Мажордомо. В данной статье буду описывать пример работы датчика в сети Майсенсорс через контроллер УД. В таком варианте данные с датчика отправляются через шлюз Майсенсорс в систему УД. В Мажордомо реализованна поддержка протокола Майсенсорс в отдельном модуле. Модуль для скачивания и установки доступен в маркете дополнений системы УД в разделе «оборудование».

На данный момент реализация для УД Мажордомо наиболее полная, поддерживается:

  • все типы данных майсенсорс,
  • работа с OTA,
  • работа сразу с несколькими сетями в одном модуле(мультгейтовость),
  • поддержка SmartSleep девайсов,
  • запрос данных у датчиков в сети при старте модуля,
  • запрос подтверждений доставки сообщений,
  • поддержка сервисных запросов, таких как сбор данных, heartbeat, презентация, перезагрузка,
  • работа с NodeManager

Есть конечно и недостатки, добавленная ранее поддержка serial шлюзов, в процессе естественного развития системы Мажордомо приказала долго жить и в данный момент не поддерживается. Мне этот тип гейтов даже не довелось потестировать в Мажордомо так как эта возможность стала недоступна раньше чем я узнал о Майсенсорс. Разработчик модуля обещал добавить снова эту возможность к сентябрю 2019 года, но осень 19-го прошла, а поддержки сериал шлюзов все еще нет :(.

Так же с Мажордомо можно использовать mqtt шлюзы Майсенсорс но уже не через модуль Майсенсорс, а через MQTT модуль.

В моем датчике сенсоры удара и магнитного поля передают только единицу при срабатывании и это оказалось небольшой проблемой. Модуль «Простые устройсва» не поддерживает такие типы датчиков, есть конечно — общий датчик, но его кастомизация настроек сильно ограничена. При добавлении датчика неудобная проблемка заключалась в том что когда приходила очередная единичка с датчика мне нужно было запускать обратный таймер, что бы через интервал времени указанном в таймере в свойство объекта записывался ноль. Но так как всё работает через метод — «статус апдейт», то записывая ноль модуль майсенсорс получая новое состояние отправлял сообщение в сеть с этими данными на мой девайс, а смысла в этом ноль. Самым простым решением мне показалось добавить новый метод в котором будет передаватся сосотояние из свойства1 в свойство2 и запускатся таймер для записи в свойство2 ноля. Объект созданный в простых устройствах будет работать с свойством2, а в модуле Майсенсорс с свойством1.

if($this->getProperty('value2') == '1'){
$this->setProperty('status','1');
}

Далее в метод статус апдейт нужного объекта необходимо добавить запуск таймера:

if (gg("MysensorsSmoke03.status") == "1") {
SetTimeOut('AlarmShock','sg("MysensorsSmoke03.status","0");',10);
}

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

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

Поддержка чипов nRF5 в Майсенсорс реализована на базе билиотеки Sandeep Mistry — arduino-nRF5. Но в этой бибилтотеке отсутствует поддержка чипов nRF52840, nRF52810 и совсем новых чипов nRF52811. Пришлось сделать форк и добавить поддержку для этих чипов, был сделан перенос и адаптация из SDK Nordic. Отсутствовала поддержка софтдевайс так как особой необходимости используя Майсенсорс в этом нет, и небыло поддержки Порта1 для чипов nRF52840. Совсем недавно были объеденены мои изыскания на эту тему и изыскания еще одного участника сообщества Майсенсорс и в итоге получилась поддержка nRF52840 уже с портом1, доступных пинов стало просто море.

Корпус для датчика разрабатывался в программе СолидВоркс, его тоже осваивал самостоятельно по урокам на Ютуб примерно год назад. Корпус был напечатан на SLA принтере ANYCUBIC FOTON. Качество и точность печати меня очень устраивает. Единственный минус это довольно бедный выбор УФ смол с которыми такие бытовые принтеры могут работать. Размеры устройства в корпусе: Длинна 43мм, Ширина 26мм, Высота 12.5мм. Размеры корпуса с магнитом: Длинна 37мм, Ширина 11мм, Высота 12,5мм.

Потребление датчика во сне составило от 4мкА до 7мкА, в зависимости от выбранного чипа. Потребление в режиме передачи данных составило 8мА.
В датчике используется батарейка CR2032. Все замеры производились китайским «мультифайлером» :) в виду отсутствия профайлера из за его довольно немаленькой стоимости :(.

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

Если кто то готов оказать помощь в написании ПО под протокол ZIGBEE, с радостью посотрудничаю.

Если вас заинтересовал данный проект, заходите в группу телеграмм, там всегда будет оказана помощь в освоении уже не только протокола Майсенсорс, но и Zigbee и BLE на nRF5, оперативно проконсультируют по всем вопросам по программированию nRF52 в Ардуино ИДЕ и не только в ней.

ТЕЛЕГРАМ ЧАТ ГДЕ ОБИТАЮ Я И ТАКИЕ КАК Я — @MYSENSORS_RUS.

Всем добра!

Let's block ads! (Why?)

Как я писал полудецентрализованную криптовалюту на PHP. (Часть 1 — Сбор библиотек)

[Из песочницы] STM32 с чистого листа

Нельзя доверять коду, который вы не написали полностью сами. — Кен Томпсон
Пожалуй, моя самая любимая цитата. Именно она и стала причиной по которой я решил нырнуть в самую глубь кроличьей норы. Свой путь в мир программирования я начал совсем недавно, прошло всего около месяца и я решил писать статьи для закрепления материала. Все началось с простой задачи, синхронизировать лампы в своей фото студии с помощью Ардуины. Задача была решена, но в фото студию я больше не заходил, времени нет. С того момента я решил основательно заняться программированием микроконтроллеров. Ардуина, хоть и привлекательна в своей простоте, как платформа мне не понравилась. Выбор пал на компанию ST и их популярную продукцию. В тот момент я еще не представлял в чем особо разница, но как типичный потребитель я сравнил скорость «процессора» и количество памяти, купил себе внушительную плату с дисплеем STM32F746NG — Discovery. Я пропущу моменты отчаяния и сразу перейду к делу.
Погрузившись в образ программиста я очень много читал, изучал, экспериментировал. И как я уже описал выше я хотел изучить ну вот прям все. И для этого я поставил цель, ни каких готовых решений, только свое. И если получилось все у меня то и у вас получиться.

Список всего того что понадобиться:

  1. Виртуальная машина Ubuntu 16+ ну или что под рукой есть
  2. arm компилятор — скачать по ссылке developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
  3. openocd отладчик и программатор — скачивать по ссылке не получится, собираем из исходников, инструкция прилагается
    git clone https://git.code.sf.net/p/openocd/code openocd
  4. Текстовый редактор любой по вкусу

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

Что нам понадобится:

  1. Makefile
  2. Linker.ld
  3. Init.c

Начнем с последнего пункта Init.c. Первым делом наш мк должен загрузить адрес «stack pointer» это указатель на адрес памяти который используется для инструкций PSUH и POP. Настоятельно рекомендую досконально изучить эти две инструкции, так как я не буду в деталях объяснять все инструкции. Как это реализовать смотрим ниже
extern void *_estack;
void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack
}

Теперь разберем данный пример. Extern означает, что символ внешний, и этот символ мы объявили в файле Linker.ld, к нему мы еще вернемся чуть позже.
 __attribute__((section(".isr_vector"), used))

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

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

Приведу пример. дано что стэк начинается с 0x20010000 а код программы находиться 0x0800008. то массив можно написать как

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    0x20010000,
    0x08000008
}

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

Теперь прервемся и обратим внимание на то как именно положить этот массив в нужную нам секцию. Этим занимается «ld» или линкер. Это программа собирает всю нашу программу воедино и для этого используется скрипт Linker.ld. Привожу пример и дальше разберем его.

MEMORY{
        ROM_AXIM (rx) : ORIGIN = 0x08000000, LENGTH = 1M
        RAM_DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 64K
}

_estack = LENGTH(RAM_DTCM) + ORIGIN(RAM_DTCM);

SECTIONS{
        .isr_vector : {
        KEEP(*(.isr_vector))
        } >ROM_AXIM</code>
}

Разберемся, что тут происходит. MEMORY определяет секции памяти а SECTIONS определяет секции. тут же мы видим и наш _estack и то что он равен сумме начала памяти и ее длины, то есть конец нашей памяти. Также определена секция .isr_vector в которую мы положили наш массив. >ROM_AXIM в конце нашей секции означает что эта секция должна быть помещена в секцию памяти которая начинается с 0x08000000 как того требовал наш мк.

Мы поместили массив куда надо теперь надо какую нибудь инструкцию нашему мк для работы. Вот дополнены Init.c

extern void *_estack;

void Reset_Handler();

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}

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

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

arm-none-eabi-gcc -c init.c -o init.o -mthumb
arm-none-eabi-gcc -TLinker.ld -o prog.elf init.o -Wl,--gc-sections -nostartfiles -nodefaultlibs -nostdlib

И тут мы видим много непонятного. по порядку:
  1. -mthumb компилируем для armv7, который использует только thumb инструкции
  2. -TLinker.ld указываем скрипт линкера. по дефолту он скомпилирует для исполнения в среде линукс
  3. -Wl,--gc-sections -nostartfiles -nodefaultlibs -nostdlib убираем все стандартные библиотеки, файлы инициализации среды С и все другие вспомогательные библиотеки типа математики.

Загружать такое в мк конечно толку нет. Зато есть толк в просмотре и изучении бинарника.
objcopy -O ihex prog.elf prog.bin
hexdump prog.bin

И тут мы увидим вывод. «00000000: 00 01 00 02 09 00 00 08» что будет символизировать нам об успехе. Это моя первая статья и я не вполне смог раскрыть весь материал и суть, поэтому в следующей я более подробно опишу механизмы и мы друзья сможем перейти к программе которая не будет мигать лампочкой, но выставит скорость процессора.

Let's block ads! (Why?)