Pull to refresh

Мини датчик света и удара | nRF52840

Reading time 22 min
Views 14K
В сегодняшней статье хочу расказать о новым датчике освещенности и вибрации. Датчик работает на модуле E73-2G4M08S1C(nRF52840). Поводом к освоению МК стало довольно несложное добавление поддержки в Arduino IDE на основе библиотеки Sandeep Mistry, небольшая стоимость, отличные характеристики и возможность в дальнейшем безболезненно перейти с MySensors например на ZigBee :)



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

Первоначально планировал использовать сенсор BH1750FVI, характеристики данного сенсора устраивали, он не раз ранее применялся в других моих diy проектах. Но в какой-то момент при обсуждении всяких сенсоров кто-то предложил присмотреться к сенсору MAX44009. Я присмотрелся и с этого момента больше никогда не вспоминал про BH1750FVI.



Характеристики MAX44009:

  • Диапазон питающих напряжений 1,7-3,6 В,
  • Cверхнизкий рабочий ток — 0,65 мкА (ниже тока в режиме Power Down у многих аналогичных продуктов),
  • Широкий 22-разрядный динамический диапазон 0,045-188000,
  • Наличие функции прерывания, возможность непрерывно контролировать уровень освещенности, и формировать сигнал прерывания для МК при превышении заданных порогов.

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

В качестве акселерометра был выбран сенсор LIS2DW12 это один из самых экономичных акселерометров на рынке, если не самый экономичный.



Характеристики LIS2DW12:

  • Диапазон питающих напряжений 1.62-3.6 В,
  • 50 нA в режиме ожидания,
  • 1 мкA в режиме пониженного энергопотребления,
  • Наличие функции прерывания, возможность непрерывной работы сенсора и формирование сигнала прерывания для МК при превышении заданных порогов.

Предвидя возможные проблемы у братьев ардуинщиков с запуском этой модели акселерометра из-за полного отсутствие готовых Arduino библиотек для LIS2DW12 позднее была добавлена поддержка другой модели акселерометра LIS2DH12 со схожими характеристиками, но более высоким потреблением в режиме пониженного энергопотребления — 2мкА. Для акселерометров LIS2DH12 есть неплохие Arduino библиотеки.

Питается датчик освещенности от батарейки CR2032.



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

В этом проекте я постарался минимизировать эти недостатки.

Самое основное с чем хотелось разобраться это с прерываниями. Стандартно при активации прерываний в функции сна, МК nRF52 начинает потреблять +10мКА к потреблению самого МК (от 1.4мкА у nRF52810-52811 и до 4.7мкА у nRF52832-52840). Всего доступно 2 прерывания.

int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2, const uint32_t sleepingMS = 0, const bool smartSleep = false);

Ранее в поисках решений оптимизации потребления во сне, была реализована возможность использования прерываний на встроенном низко потребляющем компараторе, потребление во сне было уже не +10мкА, а в районе +1 мкА, но прерывание возможно было использовать только одно. В принципе не считая неудобство в коде(обработка сигналов с дополнительных пинов) и лишних элементов на плате(диоды) это было хорошее решение. Но хотелось еще лучше.

К этому времени я уже был немного знаком:) с nRF5 SDK, поэтому после штудирования форума mysensors.org(подсказки, наводки :)) решение решил поискать в nRF5 SDK… и нашел. Теперь стало доступным неограниченное количество прерываний которые практически ничего дополнительно не потребляют. Основное что надо сделать это внести небольшое изменение в код библиотеки Sandeepmistry nRF5, в файл WInterrupts.с перед функцией GPIOTE_IRQHandler() добавить атрибут «weak» который позволит переопределить эту библиотечную функцию в пользовательском коде — __attribute__ ((weak))



Файл находится по пути — C:\Users\ЮЗЕР_КОМПА\AppData\Local\Arduino15\packages\sandeepmistry\
hardware\nRF5\0.6.0\cores\nRF5\
Файлы которые необходимо добавить в проект находятся на моем Гите в папке EFEKTA-LIS2DW12-MAX44009-E73C/SOURCE CODE/ARDUINO/, пример использования находиться в самом скетче EFEKTA-LIS2DW12-MAX44009-E73C/SOURCE CODE/ARDUINO/vibro_ambi1_2.ino

Следующее что захотелось доработать это сама библиотека Sandeepmistry nRF5, что бы удобно было работать с новыми МК nRF52840, nRF52811 и старыми, но почему-то незаслуженно не добавленными nRF52810 (все-таки 1.4 мкА во сне это вам не это :)). Конечно с nRF52840 можно работать в Arduino IDE и из под nRF52832, но… захотелось поудобнее. Выше я писал что есть интерес что-то делать на SDK Нордика, тем более Segger Embedded Studio бесплатна при работе с МК nRF5. Поддержка всех интересных мне плат была взята из СДК и перенесена в Arduino (пока без softdevice, да оно и не надо с mysensors). Так же были добавлены новые платы в библиотеку MySensors.



github.com/smartboxchannel/arduino-nRF5
github.com/smartboxchannel/MySensors

Если кто-то из читателей уже знаком с предыдущими моими статьями, то наверное не удивится что корпус для датчика света я, как и прежде делал на SLA принтере из жидкого полимера. Плюсами печати на этой технологии является высокая точность. Но есть конечно и минусы, полимеры с которыми способны работать бытовые SLA принтеры все же уступают по прочности пластикам для FDM 3D модель корпуса состоит из 2 частей, каждая половинка печаталась 40 минут( толщина слоя 50 микрон). Особо тут наверное останавливаться незачем, просто пара фоток процесса разработки в 3D редакторе.







В программе датчика реализовал перезагрузку устройства без отправки презентаций. Работает это так: пока устройство новое и не добавленное в вашу сеть, оно при первом добавлении в сеть сделает регистрацию и полную презентацию, при успешной регистрации устройство получившее идентификатор больше не будет слать повторно презентации, но отправить презентацию можно нажав на кнопку на датчике(это необходимо например если не все сенсоры успешно презентовали сразу). Так же в программе отключается возможность стандартного автоматического восстановления работы в сети(при потере датчиком сети), придуман свой нестандартный вариант :). Работает это так: если датчик фиксирует более 5(это настраиваемо) неудачных подряд попыток отправок сообщений, то устройство перестает отправлять данные с сенсоров и начинает отправлять сообщение о поиске сети с интервалом вначале равном интервалу отправки с сенсоров датчика / 2 и периодически увеличивая этот интервал c каждой отправкой, между отправками здоровый сон. Все это позволяет существенно экономить заряд батарейки.

Код тестовой программы
// SDK PORT
extern "C" {
#include "app_gpiote.h"
#include "nrf_gpio.h"
}
#define APP_GPIOTE_MAX_USERS 1
#include <LIS2DW12Sensor.h>
#include <MAX44009.h>
//#define MY_DEBUG
#define MY_RADIO_NRF5_ESB
#define MY_DISABLED_SERIAL
int16_t mtwr;
#define MY_TRANSPORT_WAIT_READY_MS (mtwr)
#define MY_NRF5_ESB_PA_LEVEL (NRF5_PA_MAX)
#include <MySensors.h>
#define SN "LUX & VIBRO SENS"
#define SV "1.0"
#define V_SENS_CHILD_ID 1
#define LUX_SENS_CHILD_ID 2
#define WPM_SENS_CHILD_ID 3
#define INTERVAL_R_LUX_CHILD_ID 220
#define LEVEL_SENSIV_V_SENS_CHILD_ID 230
#define ENABLE_WPM_SENS_CHILD_ID 240
#define SIGNAL_Q_ID 253
#define TEMP_CHILD_ID 254 //for any tests
#include <MySensors.h>
MyMessage vibroMsg(V_SENS_CHILD_ID, V_TRIPPED);
MyMessage brightMsg(LUX_SENS_CHILD_ID, V_LEVEL);
MyMessage wpmMsg(WPM_SENS_CHILD_ID, V_LEVEL);
MyMessage conf_wpmMsg(ENABLE_WPM_SENS_CHILD_ID, V_VAR1);
MyMessage conf_vsensMsg(LEVEL_SENSIV_V_SENS_CHILD_ID, V_VAR1);
MyMessage conf_interv_rluxMsg(INTERVAL_R_LUX_CHILD_ID, V_VAR1);
MyMessage tempMsg(TEMP_CHILD_ID, V_VAR1); //for any tests
bool nosleep = 0;
bool button_flag = 0;
bool configMode = 0;
bool wpm_enable = 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;
byte conf_vibro_set = 1;
byte interval_reading_lux = 10;
byte err_delivery_beat;
byte problem_mode_count;
uint8_t  countbatt = 0;
uint8_t batt_cap;
uint8_t old_batt_cap = 100;
//unsigned long BATT_TIME = 43200000; //12 hours
uint32_t BATT_TIME = 7200000; //12 hours
uint32_t SLEEP_TIME_TEMP = 60000; //1 minute
uint32_t SLEEP_TIME;
uint32_t C_BATT_TIME;
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;
int16_t result;
int16_t brightness;
int16_t lastbrightness;
int16_t brightThreshold = 25;
int16_t myid;
int16_t mypar;
int16_t old_mypar = -1;
int16_t master_id;
float Wpm;
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;
bool vibro = 1;
static app_gpiote_user_id_t m_gpiote_user_id;
uint32_t PIN_BUTTON1_MASK;
uint32_t AXEL_INT1_MASK;
volatile byte axelInt1Status = 0;
volatile byte buttInt1Status = 0;
uint16_t batteryVoltage;
int16_t linkQuality;
int16_t old_linkQuality;
LIS2DW12Sensor *lis2;
MAX44009 light;



void preHwInit() {
  board_Init();
}



void before() {
  blinky(1, 1, GREEN_LED);
  wait(1000);
  nRF_Init();
  device_Conf();
  happy_init();
}



void setup() {
  interrupt_Init();
  sensors_Init();
  config_Happy_node();
}



void presentation()
{
  if (!sendSketchInfo(SN, SV)) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1000);
    wait(50);
    if (!sendSketchInfo(SN, SV)) {
      _transportSM.failedUplinkTransmissions = 0;
    }
  }

  present(V_SENS_CHILD_ID, S_VIBRATION, "STATUS VIBRO", 1);
  wait(2500, C_PRESENTATION, S_VIBRATION);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
  if (PRESENT_ACK == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    present(V_SENS_CHILD_ID, S_VIBRATION, "STATUS VIBRO", 1);
    wait(2500, C_PRESENTATION, S_VIBRATION);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    PRESENT_ACK = 0;
  }

  present(LUX_SENS_CHILD_ID, S_LIGHT_LEVEL, "LUX", 1);
  wait(2500, C_PRESENTATION, S_LIGHT_LEVEL);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
  if (PRESENT_ACK == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    present(LUX_SENS_CHILD_ID, S_LIGHT_LEVEL, "LUX", 1);
    wait(2500, C_PRESENTATION, S_LIGHT_LEVEL);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    PRESENT_ACK = 0;
  }

  present(WPM_SENS_CHILD_ID, S_LIGHT_LEVEL, "W/M^2", 1);
  wait(2500, C_PRESENTATION, S_LIGHT_LEVEL);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
  if (PRESENT_ACK == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    present(WPM_SENS_CHILD_ID, S_LIGHT_LEVEL, "W/M^2", 1);
    wait(2500, C_PRESENTATION, S_LIGHT_LEVEL);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    PRESENT_ACK = 0;
  }

  present(SIGNAL_Q_ID, S_CUSTOM, "SIGNAL QUALITY", 1);
  wait(2500, C_PRESENTATION, S_CUSTOM);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
  if (PRESENT_ACK == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    present(SIGNAL_Q_ID, S_CUSTOM, "SIGNAL QUALITY", 1);
    wait(2500, C_PRESENTATION, S_CUSTOM);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    PRESENT_ACK = 0;
  }

  present(ENABLE_WPM_SENS_CHILD_ID, S_CUSTOM, "ON|OFF WPM", 1);
  wait(2500, C_PRESENTATION, S_CUSTOM);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
  if (PRESENT_ACK == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    present(ENABLE_WPM_SENS_CHILD_ID, S_CUSTOM, "ON|OFF WPM", 1);
    wait(2500, C_PRESENTATION, S_CUSTOM);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    PRESENT_ACK = 0;
  }

  present(LEVEL_SENSIV_V_SENS_CHILD_ID, S_CUSTOM, "SENS LEVEL VIBRO", 1);
  wait(2500, C_PRESENTATION, S_CUSTOM);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
  if (PRESENT_ACK == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    present(LEVEL_SENSIV_V_SENS_CHILD_ID, S_CUSTOM, "SENS LEVEL VIBRO", 1);
    wait(2500, C_PRESENTATION, S_CUSTOM);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    PRESENT_ACK = 0;
  }

  present(INTERVAL_R_LUX_CHILD_ID, S_CUSTOM, "INTERVAL RLUX|MIN", 1);
  wait(2500, C_PRESENTATION, S_CUSTOM);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
  if (PRESENT_ACK == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    present(INTERVAL_R_LUX_CHILD_ID, S_CUSTOM, "INTERVAL RLUX|MIN", 1);
    wait(2500, C_PRESENTATION, S_CUSTOM);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER PRESENT SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    PRESENT_ACK = 0;
  }

  send(conf_wpmMsg.set(wpm_enable), 1);
  wait(2500, C_SET, V_VAR1);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER SEND CONF SENSOR\n"));
  if (Ack_TL == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    send(conf_wpmMsg.set(wpm_enable), 1);
    wait(2500, C_SET, V_VAR1);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER SEND CONF SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    Ack_TL = 0;
  }

  send(conf_vsensMsg.set(conf_vibro_set), 1);
  wait(2500, C_SET, V_VAR1);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER SEND CONF SENSOR\n"));
  if (Ack_TL == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    send(conf_vsensMsg.set(conf_vibro_set), 1);
    wait(2500, C_SET, V_VAR1);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER SEND CONF SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    Ack_TL = 0;
  }

  send(conf_interv_rluxMsg.set(interval_reading_lux), 1);
  wait(2500, C_SET, V_VAR1);
  CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER SEND CONF SENSOR\n"));
  if (Ack_TL == 0) {
    _transportSM.failedUplinkTransmissions = 0;
    sleep(1500);
    wait(50);
    send(conf_interv_rluxMsg.set(interval_reading_lux), 1);
    wait(2500, C_SET, V_VAR1);
    CORE_DEBUG(PSTR("MyS: TEST WAIT AFTER SEND CONF SENSOR\n"));
    _transportSM.failedUplinkTransmissions = 0;
  } else {
    Ack_TL = 0;
  }
}



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 ((axelInt1Status == AXEL_INT1) || (buttInt1Status == PIN_BUTTON1)) {
          if (axelInt1Status == AXEL_INT1) {
            nosleep = 1;
            send_Axel();
            axelInt1Status = 0;
            newmillis = millis();
            interrupt_time = newmillis - oldmillis;
            SLEEP_TIME_W = SLEEP_TIME_W - interrupt_time;
            if (SLEEP_TIME_W < 5000) {
              SLEEP_TIME_W = SLEEP_TIME;
              send_Brigh(1);
              countbatt++;
              if (countbatt == C_BATT_TIME) {
                sendBatteryStatus(1);
                countbatt = 0;
              }
            }
            nosleep = 0;
          }
          if (buttInt1Status == PIN_BUTTON1) {
            if (digitalRead(PIN_BUTTON1) == 0 && button_flag == 0) {
              button_flag = 1;
              nosleep = 1;
              previousMillis = millis();
              ledsOff();
            }
            if (digitalRead(PIN_BUTTON1) == 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(BLUE_LED, onoff);
                }
              }
              if ((millis() - previousMillis > 4000) && (millis() - previousMillis <= 4250)) {
                ledsOff();
              }
              if ((millis() - previousMillis > 4250) && (millis() - previousMillis <= 6250)) {
                if (millis() - lightMillisR > 25)    {
                  lightMillisR = millis();
                  onoff = !onoff;
                  digitalWrite(RED_LED, onoff);
                }
              }
              if ((millis() - previousMillis > 6250) && (millis() - previousMillis <= 6500)) {
                ledsOff();
              }
              if ((millis() - previousMillis > 6500) && (millis() - previousMillis <= 8500)) {
                if (millis() - lightMillisR > 50)    {
                  lightMillisR = millis();
                  onoff = !onoff;
                  digitalWrite(RED_LED, onoff);
                }
              }
              if (millis() - previousMillis > 8500) {
                ledsOff();
              }
            }
            if (digitalRead(PIN_BUTTON1) == 1 && button_flag == 1) {
              if (millis() - previousMillis <= 2000)
              {
                ledsOff();
                send_Brigh(0);
                nosleep = 0;
                button_flag = 0;
                buttInt1Status = 0;
              }
              if ((millis() - previousMillis > 2000) && (millis() - previousMillis <= 4000))
              {
                ledsOff();
                configMode = 1;
                button_flag = 0;
                configMillis = millis();
              }
              if ((millis() - previousMillis > 4250) && (millis() - previousMillis <= 6250))
              {
                ledsOff();
                blinky(2, 2, RED_LED);
                button_flag = 0;
                buttInt1Status = 0;
                presentation();
                nosleep = 0;
              }
              if ((millis() - previousMillis > 6500) && (millis() - previousMillis <= 8500))
              {
                ledsOff();
                blinky(3, 3, RED_LED);
                new_device();
              }
              if (((millis() - previousMillis > 1750) && (millis() - previousMillis <= 2000)) || ((millis() - previousMillis > 4000) && (millis() - previousMillis <= 4250)) || ((millis() - previousMillis > 6250) && (millis() - previousMillis <= 6500)) || ((millis() - previousMillis > 8500)))
              {
                ledsOff();
                blinky(1, 2, GREEN_LED);
                nosleep = 0;
                button_flag = 0;
                buttInt1Status = 0;
              }
            }
          }
        } else {
          SLEEP_TIME_W = SLEEP_TIME;
          send_Brigh(1);
          countbatt++;
          if (countbatt == C_BATT_TIME) {
            sendBatteryStatus(1);
            countbatt = 0;
          }
          nosleep = 0;
        }
      } else {
        if (millis() - configMillis > 30000) {
          blinky(3, 3, GREEN_LED);
          configMode = 0;
          nosleep = 0;
          button_flag = 0;
          buttInt1Status = 0;
        }
      }
    } else {
      if (buttInt1Status == PIN_BUTTON1) {

        if (digitalRead(PIN_BUTTON1) == 0 && button_flag == 0) {
          button_flag = 1;
          nosleep = 1;
          previousMillis = millis();
          ledsOff();
        }

        if (digitalRead(PIN_BUTTON1) == 0 && button_flag == 1) {
          if ((millis() - previousMillis > 0) && (millis() - previousMillis <= 500))
          {
            ledsOff();
          }
          if ((millis() - previousMillis > 500) && (millis() - previousMillis <= 2500))
          {
            lightMillisR = millis();
            onoff = !onoff;
            digitalWrite(BLUE_LED, onoff);
          }
          if ((millis() - previousMillis > 2500) && (millis() - previousMillis <= 2750))
          {
            ledsOff();
          }
          if ((millis() - previousMillis > 2750) && (millis() - previousMillis <= 4750)) {
            if (millis() - lightMillisR > 50) {
              lightMillisR = millis();
              onoff = !onoff;
              digitalWrite(RED_LED, onoff);
            }
          }

          if (millis() - previousMillis > 4750) {
            ledsOff();
            blinky(3, 1, GREEN_LED);
            button_flag = 0;
            nosleep = 0;
            buttInt1Status = 0;
          }
        }

        if (digitalRead(PIN_BUTTON1) == 1 && button_flag == 1) {
          if (millis() - previousMillis <= 500)
          {
            ledsOff();
            button_flag = 0;
            nosleep = 0;
            buttInt1Status = 0;
          }
          if ((millis() - previousMillis > 500) && (millis() - previousMillis <= 2500))
          {
           ledsOff();
            blinky(1, 1, BLUE_LED);
            check_parent();
            button_flag = 0;
            nosleep = 0;
            buttInt1Status = 0; 
          }

          if ((millis() - previousMillis > 2500) && (millis() - previousMillis <= 2750))
          {
           ledsOff();
            button_flag = 0;
            nosleep = 0;
            buttInt1Status = 0; 
          }

          if ((millis() - previousMillis > 2750) && (millis() - previousMillis <= 4750))
          {
            ledsOff();
            blinky(3, 3, RED_LED);
            new_device();
          }

          if (millis() - previousMillis > 4750)
          {
            ledsOff();
            button_flag = 0;
            nosleep = 0;
            buttInt1Status = 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 = 5;
    happy_node_mode();
    gateway_fail();
  }

  if (configMode == 0) {
    if (nosleep == 0) {
      oldmillis = millis();
      axelInt1Status = 0;
      buttInt1Status = 0;
      wait(100);
      sleep(SLEEP_TIME_W, false);
      wait(50);
      nosleep = 1;
    }
  }
}



float GetWpm()
{
  float SunLuxCoef = 0.0079;
  float Wpm_temp = 0;
  Wpm_temp = (float)brightness;
  Wpm_temp *= SunLuxCoef;
  return Wpm_temp;
}



void blinky(uint8_t pulses, uint8_t repit, uint8_t ledColor) {
  for (int x = 0; x < repit; x++) {
    if (x > 0) {
      sleep(300);
    }
    for (int i = 0; i < pulses; i++) {
      if (i > 0) {
        sleep(80);
      }
      digitalWrite(ledColor, LOW);
      sleep(20);
      digitalWrite(ledColor, HIGH);
    }
  }
}



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



void nRF_Init() {
  NRF_POWER->DCDCEN = 1;
  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;
}



void sensors_Init() {
  Wire.begin();
  wait(100);
  light.begin();
  wait(100);
  lis2 = new LIS2DW12Sensor (&Wire);
  vibro_Init();
  if (isTransportReady() == true) {
    blinky(3, 1, BLUE_LED);
    wait(200);
    blinky(3, 1, GREEN_LED);
    wait(200);
    blinky(3, 1, RED_LED);
    SLEEP_TIME_W = SLEEP_TIME;
    send_Brigh(0);
    wait(50);
    sendBatteryStatus(0);
    axel_time = millis();
  } else {
    blinky(5, 3, RED_LED);
  }
}



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(50);
  lis2->Enable_Wake_Up_Detection();
  wait(50);
}



void board_Init() {
  pinMode(PIN_BUTTON1, INPUT);
  pinMode(AXEL_INT1, INPUT);
  pinMode(AXEL_INT2, INPUT);
  pinMode(AMBI_INT, INPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);
  ledsOff();
}



void send_Axel() {
  if (millis() - axel_time >= 5000) {
    blinky(6, 1, RED_LED);
    lis2->Disable_Wake_Up_Detection();
    wait(100);
    if (_transportConfig.parentNodeId == 0) {
      if (send(vibroMsg.set(vibro))) {
        wait(100);
        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 < 5) {
          err_delivery_beat++;
        }
        if (err_delivery_beat == 4) {
          if (flag_nogateway_mode == 0) {
            gateway_fail();
            CORE_DEBUG(PSTR("MyS: LOST GATEWAY MODE\n"));
          }
        }
      }
      lis2->Enable_Wake_Up_Detection();
      wait(100);
      axel_time = millis();
      nosleep = 0;
    }
    if (_transportConfig.parentNodeId > 0) {
      send(vibroMsg.set(vibro), 1);
      wait(2500, C_SET, V_TRIPPED);
      if (Ack_TL == 1) {
        Ack_TL = 0;
        err_delivery_beat = 0;
        //sleep_flag = 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 < 5) {
          err_delivery_beat++;
        }
        if (err_delivery_beat == 4) {
          if (flag_nogateway_mode == 0) {
            gateway_fail();
            CORE_DEBUG(PSTR("MyS: LOST GATEWAY MODE\n"));
          }
        }
      }
      lis2->Enable_Wake_Up_Detection();
      wait(100);
      axel_time = millis();
      nosleep = 0;
    }
  } else {
    nosleep = 0;
  }
}



void send_Brigh(bool start) {
  brightness = light.get_lux() * 2;
  wait(50);
  if (start == 1) {
    if (abs(brightness - lastbrightness) >= brightThreshold) {
      if (_transportConfig.parentNodeId == 0) {
        if (send(brightMsg.set(brightness, 0))) {
          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;
          }
          lastbrightness = brightness;
          if (wpm_enable == 1) {
            Wpm = GetWpm();
            wait(100);
            send(wpmMsg.set(Wpm, 0));
          }
          wait(50);
          blinky(2, 2, BLUE_LED);
        } else {
          _transportSM.failedUplinkTransmissions = 0;
          if (err_delivery_beat < 5) {
            err_delivery_beat++;
          }
          if (err_delivery_beat == 4) {
            if (flag_nogateway_mode == 0) {
              gateway_fail();
              CORE_DEBUG(PSTR("MyS: LOST GATEWAY MODE\n"));
            }
          }
        }
      }
      if (_transportConfig.parentNodeId > 0) {
        send(brightMsg.set(brightness, 0), 1);
        wait(2500, C_SET, V_LEVEL);
        if (Ack_TL == 1) {
          Ack_TL = 0;
          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;
          }
          lastbrightness = brightness;
          if (wpm_enable == 1) {
            Wpm = GetWpm();
            wait(100);
            send(wpmMsg.set(Wpm, 0));
          }
          wait(50);
          blinky(2, 2, BLUE_LED);
        } else {
          _transportSM.failedUplinkTransmissions = 0;
          if (err_delivery_beat < 5) {
            err_delivery_beat++;
          }
          if (err_delivery_beat == 4) {
            if (flag_nogateway_mode == 0) {
              gateway_fail();
              CORE_DEBUG(PSTR("MyS: LOST GATEWAY MODE\n"));
            }
          }
        }
      }
    }
  } else {
    send(brightMsg.set(brightness, 0));
    lastbrightness = brightness;
    if (wpm_enable == 1) {
      Wpm = GetWpm();
      wait(100);
      send(wpmMsg.set(Wpm, 0));
    }
    wait(50);
    blinky(2, 2, BLUE_LED);
  }
}



void interrupt_Init() {
  //***
  //SET
  //NRF_GPIO_PIN_NOPULL
  //NRF_GPIO_PIN_PULLUP
  //NRF_GPIO_PIN_PULLDOWN
  //***
  nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP);
  nrf_gpio_cfg_input(AXEL_INT1, NRF_GPIO_PIN_NOPULL);
  APP_GPIOTE_INIT(APP_GPIOTE_MAX_USERS);
  PIN_BUTTON1_MASK = 1 << PIN_BUTTON1;
  AXEL_INT1_MASK = 1 << AXEL_INT1;
  //  app_gpiote_user_register(p_user_id, pins_low_to_high_mask, pins_high_to_low_mask, event_handler)
  app_gpiote_user_register(&m_gpiote_user_id, AXEL_INT1_MASK, PIN_BUTTON1_MASK, gpiote_event_handler);
  app_gpiote_user_enable(m_gpiote_user_id);
  axelInt1Status = 0;
  buttInt1Status = 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);

  if (PIN_BUTTON1_MASK & event_pins_high_to_low) {
    if ((buttInt1Status == 0) && (axelInt1Status == 0)) {
      buttInt1Status = PIN_BUTTON1;
    }
  }
  if (flag_nogateway_mode == 0) {
    if (configMode == 0) {
      if (AXEL_INT1_MASK & event_pins_low_to_high) {
        if ((axelInt1Status == 0) && (buttInt1Status == 0)) {
          axelInt1Status = AXEL_INT1;
        }
      }
    }
  }

  /***
    if ((PIN_BUTTON_MASK & event_pins_low_to_high) || (PIN_BUTTON1_MASK & event_pins_high_to_low))
  ***/
}



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

  wpm_enable = loadState(240);
  if (wpm_enable > 1) {
    wpm_enable = 0;
    saveState(240, wpm_enable);
  }

  interval_reading_lux  = loadState(220);
  if (interval_reading_lux > 60) {
    interval_reading_lux = 60;
    saveState(230, interval_reading_lux);
  } else if (interval_reading_lux < 1) {
    interval_reading_lux = 1;
    saveState(230, interval_reading_lux);
  }

  SLEEP_TIME = SLEEP_TIME_TEMP * interval_reading_lux;
  C_BATT_TIME = BATT_TIME / SLEEP_TIME;
}



void sendBatteryStatus(bool start) {
  sleep(5000);
  wait(200);
  batteryVoltage = hwCPUVoltage();
  wait(10);
  batt_cap = battery_level_in_percent(batteryVoltage);
  if (start == 1) {
    if (batt_cap < old_batt_cap) {
      sendBatteryLevel(battery_level_in_percent(batteryVoltage), 1);
      wait(2500, C_INTERNAL, I_BATTERY_LEVEL);
      old_batt_cap = batt_cap;
    }
  } else {
    sendBatteryLevel(battery_level_in_percent(batteryVoltage), 1);
    wait(2500, 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 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 = 10000;
    no_present();
  }
  CORE_DEBUG(PSTR("MY_TRANSPORT_WAIT_MS: %d\n"), mtwr);

}



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



void config_Happy_node() {
  if (mtwr == 0) {
    myid = getNodeId();
    saveState(100, myid);
    mypar = _transportConfig.parentNodeId;
    old_mypar = mypar;
    master_id = 0; // *************************** master slave mode is not initialized in this example, ..stub *******************************
    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;
      master_id = 0; // *************************** master slave mode is not initialized in this example, ..stub *******************************
      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 = 5;
      _transportConfig.nodeId = myid;
      _transportConfig.parentNodeId = loadState(101);
      _transportConfig.distanceGW = loadState(102);
      mypar = _transportConfig.parentNodeId;
      happy_node_mode();
      gateway_fail();
    }
  }
}



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



void happy_node_mode() {
  _transportSM.findingParentNode = false;
  _transportSM.transportActive = true;
  _transportSM.uplinkOk = true;
  _transportSM.pingActive = false;
  transportSwitchSM(stReady);
  _transportSM.failureCounter = 0;
}



void gateway_fail() {
  flag_nogateway_mode = 1;
  flag_update_transport_param = 0;
  SLEEP_TIME_W = SLEEP_TIME / 2;
  lis2->Disable_Wake_Up_Detection();
}



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;
  lis2->Enable_Wake_Up_Detection();
}



void update_Happy_transport() {
  CORE_DEBUG(PSTR("MyS: UPDATE TRANSPORT CONFIGURATION\n"));
  mypar = _transportConfig.parentNodeId;
  master_id = 0; // *************************** master slave mode is not initialized in this example, ..stub *******************************
  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 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;
    SLEEP_TIME_W = SLEEP_TIME;
    problem_mode_count = 0;
  } else {
    _transportSM.findingParentNode = false;
    CORE_DEBUG(PSTR("MyS: PARENT RESPONSE NOT FOUND\n"));
    _transportSM.failedUplinkTransmissions = 0;
    nosleep = 0;
    if (problem_mode_count < 24) {
      CORE_DEBUG(PSTR("PROBLEM MODE COUNTER: %d\n"), problem_mode_count);
      problem_mode_count++;
      SLEEP_TIME_W = SLEEP_TIME / 100 * 120;
    } else if (problem_mode_count == 24) {
      SLEEP_TIME_W = SLEEP_TIME * 30;
      CORE_DEBUG(PSTR("PROBLEM MODE COUNTER: %d\n"), problem_mode_count);
    }
  }
}



void receive(const MyMessage & message)
{
  if (message.sensor == ENABLE_WPM_SENS_CHILD_ID) {
    if (message.type == V_VAR1) {
      if (mGetCommand(message) == C_SET) {
        if (message.isEcho()) {
          Ack_TL = 1;
        } else {
          wpm_enable = message.getBool();
          saveState(240, wpm_enable);
          wait(10);
          send(conf_wpmMsg.set(wpm_enable));
          wait(50);
          blinky(3, 3, GREEN_LED);
          configMode = 0;
          nosleep = 0;
          button_flag = 0;
          buttInt1Status = 0;
        }
      }
    }
  }

  if (message.sensor == LEVEL_SENSIV_V_SENS_CHILD_ID) {
    if (message.type == V_VAR1) {
      if (mGetCommand(message) == C_SET) {
        if (message.isEcho()) {
          Ack_TL = 1;
        } else {
          conf_vibro_set = message.getByte();
          vibro_Init();
          saveState(230, conf_vibro_set);
          wait(10);
          send(conf_vsensMsg.set(conf_vibro_set));
          wait(50);
          blinky(3, 3, GREEN_LED);
          configMode = 0;
          nosleep = 0;
          button_flag = 0;
          buttInt1Status = 0;
        }
      }
    }
  }

  if (message.sensor == INTERVAL_R_LUX_CHILD_ID) {
    if (message.type == V_VAR1) {
      if (mGetCommand(message) == C_SET) {
        if (message.isEcho()) {
          Ack_TL = 1;
        } else {
          interval_reading_lux = message.getByte();
          SLEEP_TIME = SLEEP_TIME_TEMP * interval_reading_lux;
          C_BATT_TIME = BATT_TIME / SLEEP_TIME;
          saveState(220, interval_reading_lux);
          wait(10);
          send(conf_interv_rluxMsg.set(interval_reading_lux));
          wait(50);
          blinky(3, 3, GREEN_LED);
          configMode = 0;
          nosleep = 0;
          button_flag = 0;
          buttInt1Status = 0;
        }
      }
    }
  }

  if (message.sensor == LUX_SENS_CHILD_ID) {
    if (message.type == V_LEVEL) {
      if (mGetCommand(message) == C_SET) {
        if (message.isEcho()) {
          Ack_TL = 1;
        }
      }
    }
  }

  if (message.sensor == V_SENS_CHILD_ID) {
    if (message.type == V_TRIPPED) {
      if (mGetCommand(message) == C_SET) {
        if (message.isEcho()) {
          Ack_TL = 1;
        }
      }
    }
  }

  if (mGetCommand(message) == 0) {
    PRESENT_ACK = 1;
    CORE_DEBUG(PSTR("MyS: !!!ACK OF THE PRESENTATION IN THE FUNCTION RECEIVE RECEIVED!!!\n"));
  }
}


Гитхаб проекта(дополнительные библиотеки, исходники проекта, схемы, гербер, BOM):
github.com/smartboxchannel/EFEKTA-LIS2DW12-MAX44009-E73C

Характеристики датчика:

  • Диапазон рабочего напряжения 2-3 В,
  • 6.7мкА в режиме сна,
  • 8мА в режиме работы в сети,
  • E73-2G4M08S1C nRF52840,
  • MAX44009 Ambient Light Sensor,
  • LIS2DW12\LIS2DH12 3-axis MEMS accelerometer,
  • RGB Led,
  • User botton,
  • Порт программирования SWD + Serial.

Ниже пример работы датчика в системе УД Majordomo, естественно работать будет в любом УД в которoм добавлена поддержка протокола MySensors, а это практически все известные.





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


Фото датчика






На этом наверное буду закруглиться.

Место где всегда с радостью помогут всем кто хочется познакомиться с MYSENSORS (установка плат, работа с микроконтроллерами nRF5 в среде Arduino IDE, советы по работе с протоколом mysensors, обсуждение проектов — телеграмм чат @mysensors_rus

Всем Добра и удачи в своих проектах!

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

Спойлер




















Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+10
Comments 5
Comments Comments 5

Articles