Последовательный 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 6 1
async/await 9 x1.5
recursion 10 x1.67
генераторы (асинхронные) 13 x2.17
генераторы 17 x2.83

И фаворитом в этой "гонке", как видно из таблицы, оказался обычный метод 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

Промисы

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

ссылка на оригинал статьи https://habr.com/ru/post/490524/

DNS-over-HTTPS и риски для персональных данных — обсуждаем мнения экспертов

25 февраля Mozilla сделали DNS-over-HTTPS (DoH) протоколом по умолчанию в своем браузере для всех американских пользователей. В целом ИТ-сообщество встретило это решение положительно, заметив, что шифрование DNS-трафика повысит безопасность в интернете. Но нашлись и те, кто считает иначе, — например, представители интернет-регистратора RIPE.

В сегодняшнем материале разбираем основные мнения.


/ Unsplash / Muukii

Небольшой ликбез

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

Обмен данными между браузером и DNS-сервером происходит в открытом виде. При желании злоумышленник может подслушать этот трафик и проследить, какие ресурсы посещает пользователь. Чтобы решить проблему, протокол DoH инкапсулирует запрос IP-адреса в трафик HTTPS. Затем он поступает специальному серверу, который обрабатывает его при помощи API и генерирует ответ (стр.8):

:status = 200    content-type = application/dns-message    content-length = 61    cache-control = max-age=3709     <61 bytes represented by the following hex encoding>    00 00 81 80 00 01 00 01  00 00 00 00 03 77 77 77    07 65 78 61 6d 70 6c 65  03 63 6f 6d 00 00 1c 00    01 c0 0c 00 1c 00 01 00  00 0e 7d 00 10 20 01 0d    b8 ab cd 00 12 00 01 00  02 00 03 00 04 

Таким образом, DNS-трафик скрыт в трафике HTTPS, и запросы к системе доменных имен остаются анонимными.

Кто поддерживает DoH

В поддержку DoH высказываются западные облачные провайдеры, телекомы и интернет-провайдеры. Многие из них уже предлагают DNS-сервисы на базе нового протокола — полный список есть на GitHub. Например, в British Telecommunications говорят, что сокрытие DNS-запросов в HTTPS увеличит безопасность британских пользователей.

Пара материалов из нашего блога на Хабре:

Год назад DNS-over-HTTPS начали тестировать в Google. Инженеры добавили возможность активировать DoH в Chrome 78. По словам разработчиков, инициатива защитит пользователей от DNS-спуфинга и фарминга, когда хакеры перенаправляют жертву на ложный IP-адрес.

В начале материала мы упомянули другого разработчика браузера — Mozilla. На этой неделе компания подключила DNS-over-HTTPS для всех пользователей из США. Теперь при установке браузера новый протокол активируется по умолчанию. Тех, у кого уже есть Firefox, планируют перевести на DoH в ближайшие недели. Другие страны новая инициатива пока обойдет стороной, но желающие могут включить передачу DNS-запросов по HTTPS самостоятельно.

Аргументы против

Те, кто выступает против внедрения DoH, говорят, что он снизит безопасность сетевых подключений. Например, Пол Викси (Paul Vixie), один из авторов доменной системы имен, утверждает, что системным администраторам станет сложнее блокировать потенциально вредоносные сайты в корпоративных и частных сетях.

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

Сотрудник RIPE Берт Хуберт (Bert Hubert), который участвовал в разработке PowerDNS, говорит, что классический подход DNS-over-UDP предоставляет большую анонимность, так как смешивает все запросы к системе доменных имен из одной сети (домашней или публичной). В этом случае сопоставить отдельные запросы с конкретными компьютерами становится сложнее.


/ Unsplash / chris panas

К недостаткам DoH некоторые эксперты также относят невозможность настроить родительский контроль в браузерах и сложности с оптимизацией трафика в CDN-сетях. В последнем случае может вырасти задержка до начала передачи контента, так как резолвер будет искать адрес хоста, ближайшего к серверу DNS-over-HTTPS. Здесь стоит отметить, что ряд ИТ-компаний уже работает над решением этих сложностей. Например, в той же Mozilla рассказали, что Firefox будет автоматически отключать DoH, если пользователь настроит правила родительского контроля. И компания планирует продолжить работу над более совершенными инструментами в будущем.

О чем мы пишем в корпоративном блоге VAS Experts:

ссылка на оригинал статьи https://habr.com/ru/company/vasexperts/blog/490530/

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

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

Актуальность

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

Эффективность, полезность, качество базы знаний как инструмента коррелируют с ресурсоемкостью ее ведения и результативностью извлечения знаний. Чем проще и быстрее сбор и фиксация знаний в базе и чем более пертинентны результаты запросов к ней, тем лучше и надежнее сам инструмент [1, 2]. Тем не менее, дискретные методы и средства структуризации, которые применимы для систем управления базами данных, в том числе нормализация отношений реляционных баз данных, не позволяют описывать или моделировать смысловые компоненты, интерпретации, интервальные и непрерывные семантические множества [4, 7, 10]. Для этого нужен методологический подход, обобщающий частные случаи конечных онтологий и приближающий модель знаний к непрерывности описания предметной области информационной системы.

Таким подходом может быть объединение положений теории нечеткой математики и понятия фрактальной размерности [3, 6]. Оптимизируя описание знаний по критерию степени непрерывности (величине шага дискретизации описания) в условиях ограничения по принципу неполноты Гёделя (в информационной системе – принципиальной неполноты рассуждений, знаний, выводимых из этой системы при условии ее непротиворечивости), выполняя последовательную фаззификацию (приведение к нечеткости), получаем формализованное описание, которое максимально полно и связно отображает некоторый массив знаний и с которым при этом можно выполнять любые операции информационных процессов – сбор, хранение, обработку и передачу [5, 8, 9].

Определение рекурсии нечеткого множества

Пусть X – множество значений некоторой характеристики моделируемой системы:

(1)

где n = [N ≥ 3] – количество значений такой характеристики (больше, чем элементарный набор (0; 1) – (ложь; истина)).
Пусть X = B, где B = {a,b,c,…,z} – множество эквивалентов, поэлементно соответствующее множеству значений характеристики X.
Тогда нечеткое множество , которое соответствует нечеткому (в общем случае) понятию, описывающему характеристику X, может быть представлено в виде:

(2)

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

(3)

где – множество, соответствующее нечеткому понятию, в общем случае более полно описывающему характеристику X, чем множество , по критерию мягкости; Re – степень рекурсии описания.
Следует учесть, что (сводимо к четкому множеству) в частном случае при необходимости.

Введение дробной размерности

При Re = 1 множество представляет собой обычное нечеткое множество 2-й степени, включающее в качестве элементов нечеткие множества (либо их четкие отображения), описывающие все значения характеристики X [1, 2]:

(4)

Однако это – вырожденный случай, и в наиболее полном представлении часть элементов могут быть множествами, в то время как остальные – тривиальными (предельно простыми) объектами. Поэтому для определения такого множества необходимо ввести дробную рекурсию – аналог дробной размерности пространства (в данном контексте – пространства онтологии некоторой предметной области) [3, 9].

При Re дробной получаем следующую запись :

(5)

где – нечеткое множество для значения X1, – нечеткое множество для значения X2 и т. д.

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

Определение множества функциональных возможностей модуля

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

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

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

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

(6)

где X1 – создание, X2 – редактирование, X3 – удаление,

(7)

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

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

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

(8)

Такое множество в общем случае имеет степень рекурсии равную 1,6(6) и является фрактальным и нечетким одновременно.

Подготовка сценариев использования и тестирования модуля

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

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

Для модуля формируется нечеткое множество :

(9)

где
– нечеткое множество для операции создания данных по функциональной возможности X;
– нечеткое множество для операции редактирования данных по функциональной возможности X, при этом степень рекурсии a (вложения функции) является натуральным числом и в тривиальном случае равна 1;
– нечеткое множество для операции удаления данных по функциональной возможности X, при этом степень рекурсии b (вложения функции) является натуральным числом и в тривиальном случае равна 1.

Такое множество описывает, что именно (какие объекты данных) создают, редактируют и/или удаляют при любом варианте использования модуля.

Затем составляется набор сценариев использования Ux по функциональной возможности X для рассматриваемого модуля, в каждом из которых описывается, для чего (для какой бизнес-задачи) создают, редактируют и/или удаляют объекты данных, описанные множеством , и в каком порядке:

(10)

где n – количество сценариев использования для X.

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

(11)

где [D] – массив тестовых данных, n – количество сценариев тестирования для X.
В описанном подходе количество сценариев тестирования равно числу соответствующих сценариев использования, что позволяет упростить работу над их описанием и актуализацией по мере развития системы. Кроме того, такой алгоритм может быть использован для автоматизации тестирования программных модулей информационной системы.

Заключение

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

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

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

Список литературы

  1. Борисов В.В., Федулов А.С., Зернов М.М., «Основы теории нечетких множеств». М.: Горячая линия – Телеком, 2014. – 88 с.
  2. Борисов В.В., Федулов А.С., Зернов М.М., «Основы теории нечеткого логического вывода». М.: Горячая линия – Телеком, 2014. – 122 с.
  3. Деменок С.Л., «Фрактал: между мифом и ремеслом». Спб: Академия исследования культуры, 2011. – 296 с.
  4. Заде Л., «Основы нового подхода к анализу сложных систем и процессов принятия решений» / «Математика сегодня». М.: «Знание», 1974. – С. 5 – 49.
  5. Кранц С., «Изменчивая природа математического доказательства». М.: Лаборатория знаний, 2016. – 320 с.
  6. Маврикиди Ф.И., «Фрактальная математика и природа перемен» / «Дельфис», №54 (2/2008), http://www.delphis.ru/journal/article/fraktalnaya-matematika-i-priroda-peremen.
  7. Мандельброт Б., «Фрактальная геометрия природы». М.: Институт компьютерных исследований, 2002. – 656 с.
  8. «Основы теории нечетких множеств: Методические указания», сост. Коробова И.Л., Дьяков И.А. Тамбов: Изд-во Тамб. гос. тех. Ун-та, 2003. – 24 с.
  9. Успенский В.А., «Апология математики». М.: Альпина Нон-фикшн, 2017. – 622 с.
  10. Zimmerman H. J. «Fuzzy Set Theory – and its Applications», 4th edition. Springer Seience + Business Media, New York, 2001. – 514 p.

ссылка на оригинал статьи https://habr.com/ru/post/490534/

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

Приветствую всех читателей Хабра и особенно читателей раздела «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.

Всем добра!

ссылка на оригинал статьи https://habr.com/ru/post/490476/

O Tilling-wm в 2-х словах

Немного расскажу о моем опыте использования тиллинговых менеджеров окон, а также рассмотрим кратко плюсы и минусы, которые я нашёл за 3 месяца использования

Вкратце

Я использовал bspwm, awesome и i3 из тиллинговых менеджеров окон. Самым удобным оказался bspwm. Его буквально можно поставить на ПК и он уже настроен.
Опыт перехождения с мышки на клавиатуру и только был болезненный. Во всех трех менеджерах можно использовать мышь, однако делать этого незачем, ибо с мышью прекрасно справляются KDE, Gnome, XFCE, Cinnamon и многие другие DE.

Плюсы и минусы:

i3

+ Гибконастраиваемый
+ Легковесный
+ Вся конфигурация в одном файле (хотя, этот плюс, конечно же субъективный)
+ Правила расположения окон можно настроить очень гибко
+ Очень хорошая документация

— Оригинальный i3 не такой уж и гибкий, как его форк i3-gaps
— Изначально поставляется просто как пустой экран с dmenu (пакет из suckless-tools)

Скриншоты:

Моя конфигурация i3

bspwm

+ Конфигурация может быть написана на любом скриптовом языке. По сути конфигурация — просто набор последовательных правил. Сам же bspwm может быть настроен с помощью bspc
+ Bspc содержит мануал с краткими выкладками и примерами
+ Отступы между окнами присутствуют по умолчанию в отличии от i3
+ Настройка горячих клавиш производится с помощью отдельной программы sxhkd. Все горячие клавиши могут быть перенесены на другой WM

— Нет нормальной документации (только мануал)
— Нужно докачивать программы по типу rofi, dmenu, alacritty (или любого другого терминала) и так далее, т.к. без них bspwm вообще ничего не показывает
— Может чуть больше нагружать систему, чем i3 из-за дополнительных утилит

Моя конфигурация bspwm

awesome

+ Хорошо задокументирован
+ Вся конфигурация на языке Lua
+ Очень гибкий
+ Все утилиты нужные для работы идут в комплекте. Не нужно ничего докачивать
+ Нормально обращается с мышью
+ Отступы между окнами присутствуют (привет, i3)

— Обязательное знание Lua
— Немного больше нагружает систему, чем bspwm

Личное мнение

Мне лично по душе bspwm. Он лёгкий в конфигурации, над ним не нужно много работать, всё уже готово.
Скриншоты:



ссылка на оригинал статьи https://habr.com/ru/post/490480/