Wireless sensor for opening and closing with advanced functionality

I welcome all readers of Habr and especially readers of the section "DIY or Do it yourself"! And if I can’t come up with something like that, I’m an arduino, I can ... I don’t touch the main topic of managing elevator cabs :). After some thought, for some reason I wanted to make an opening and closing sensor. This sensor, like the rest of my crafts that I do recently, is based on chips from Nordic Semiconductor. The sensor decided to do in two versions, one on the nRF52840 chip, and the second on the nRF52811 chip.



For versions nRF52840 chip module was used E73_2G4M08S1C company EBYTE , for verciya chip module nRF52811 MC50SFA company MiNEW . Frankly speaking, the search for affordable nRF52811 chips was still an adventure. But as a result of this adventure in the device, the module on the nRF52811 chip from MINEW and the buns in the form of several versions of the chips soldered to these modules are nRF52810 and nRF52832.



The main functionality of the device is the detection of opening and closing based on a reed switch. The circuit of the reed switch is re-phaseized with anti-bounce.

Circuit diagram:


Arduino scheme :)



Thinking over what it would be appropriate to dilute the main functionality of this sensor of opening and closing, I decided to see what is on the market about this. As it turned out almost nothing, the opening and closing sensor is also in Africa the opening and closing sensor. The most “advanced” solution was found at REDMOND . In their BLE sensor (by the way, also on a chip from Nordic), in addition to the reed switch, there is a temperature sensor and a capacitive button implemented on the TTP223 chip. But for some reason this seemed to me to be not quite a good solution, how useful the temperature readings near the door or window (and what prevented it from being measured by a chip) and in what situations it is appropriate to use the button on the sensor hanging on the window or door (well, except maybe the input :)). As a result, I decided to expand the security functions of my sensor.



The main selection criterion was the consumption of additional sensors, since it was decided to use a CR2032 battery in this sensor. The winners among the candidates were two sensors, the LIS2DW12 accelerometer and the DRV5032FB magnetic field sensor.

LIS2DW12 is currently probably the most economical accelerometer. In low consumption mode, this accelerometer consumes 1 μA ( datasheet ). The DRV5032FB magnetic field sensor also showed excellent consumption characteristics. Its consumption is around 500nA ( datasheet ).

The accelerometer was decided to be used in shock sensor mode, and the magnetic field sensor for its intended purpose. If I was calm about the functionality of the shock sensor, then using a magnetic field sensor is still a highly experimental solution, but it's better than a temperature sensor.



The software part of the project was made to operate the sensor in the Maysensors network. At least for now. Meisensors in the variant of working on Nordic chips (nRF24 (+ atmega 328, stm32f1), nRF51 and nRF52) at the lower level uses the Nordic proprietary protocol - Enhanced ShockBurst (ESB), thereby ensuring the compatibility of devices on nRF24 and nRF51-52. Maysensors is an open Arduino project around which a rather large community has already formed in many countries of the world. But the good solutions on nRF52 chips are that it is not necessary to use Maysensors (ESB). It’s enough to simply replace the software based on the Zigbee or BLE protocol, since the chips are multi-protocol. ... Regarding BLE, I’ll digress a little, look at what a wonderful Arduino NANO 33 Ble can be made from the E73_2G4M08S1C module,the cost of my NANO 33 is $ 4.



The sketch for the sensor was made in Arduino. IDE of additional libraries was used the library for the accelerometer LIS2DW12, a little changed by me in the part of the default settings of the registers, in my version it works immediately with the settings of the lowest version of power consumption ( available on my git ).

I will describe the logic of the program. In the main mode of operation, the sensor is in a dream with configured external interrupts, only 4 interrupts. There are two configurations of interrupts; configs reconfigure interrupts during the program operation, depending on the state of the reed switch. If the door is open, interrupts for the shock sensor and magnetic field sensor are disabled. As soon as the door closes, interrupts for two of these sensors are activated. I also encountered the fact that during the opening there were situations when the shock sensor was triggered before the reed switch, it came from vibrations during the opening of the lock. This trouble was recorded only with the configured high sensitivity of the accelerometer.

To eliminate this problem, a wait of 2 seconds was introduced when the accelerometer was triggered, during which the pin of the reed switch is monitored. If during waiting a level change occurs on the pin of the reed switch, then further processing of the event by interruption from the accelerometer stops and the processing of the event from the reed switch begins.

The sensor has a configuration mode. When the service button is pressed, the sensor wakes up upon interruption, the radio module enters listening mode and waits for incoming commands from the UD controller. If a command is received, the sensor writes a new value to the memory and switches to operating mode immediately going to sleep. To send the next command, the activation of the configuration mode must be repeated. If in the configuration mode the sensor does not receive anything within 30 seconds, then after this time it also switches to the operating mode and goes to sleep. In addition to the configuration mode, from the service button you can start the presentation of the sensor sensors and factory reset (the sensor forgets the network to which it has been added, registration of the sensor after resetting must be done again).











To program the sensor in Arduino IDE, you need to add support for the following boards:

sandeepmistry / arduino-nRF5
mysensors / ArduinoBoards
Libraries:

Mysensor
LIS2DW12

Programmer: st-link, j-link.

Sketch program
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);
}


A complete list of project files is available on the git .

As a UD system, I have been using Majordomo for a long time . In this article I will describe an example of how the sensor works in the Maysensors network through the UD controller. In this embodiment, data from the sensor is sent through the Maysensors gateway to the UD system. Majordomo implements support for the Mysensors protocol in a separate module . The module for downloading and installation is available in the add-ons market of the UD system in the "equipment" section.


At the moment, the implementation for the Majordomo UD is the most complete, supported:

  • all mesensors data types,
  • work with OTA,
  • work with several networks at once in one module (multi-gate),
  • SmartSleep device support,
  • request data from sensors in the network when the module starts,
  • message delivery confirmation request,
  • support for service requests, such as data collection, heartbeat, presentation, reboot,
  • work with NodeManager

There are of course flaws, the previously added support for serial gateways, in the process of the natural development of the system, Majordomo ordered a long life and is not currently supported. I didn’t even have a chance to test this type of gates in the Majordomo, as this feature became unavailable before I found out about Mysensors. The module developer promised to add this feature again by September 2019, but the fall of the 19th has passed, and there is still no support for the series of gateways :(.

You can also use Mysensors mqtt gateways with Majordomo, but not through the Mysensors module, but through the MQTT module.

In my sensor, shock and magnetic field sensors only transmit a unit when triggered, and this turned out to be a small problem. The Simple Devices module does not support these types of sensors, of course there is a common sensor, but its customization of settings is very limited. When adding a sensor, an uncomfortable problem was that when the next unit came from the sensor, I had to start a reverse timer, so that after a time interval specified in the timer, zero is written to the object property. But since everything works through the “status update” method, then writing the zero, the meysensors module receiving a new state sent a message to the network with this data to my device, and the point is zero.The simplest solution seemed to me to add a new method in which the state from property1 to property2 will be transferred and a timer will be started to write zero to property2. An object created in simple devices will work with property2, and in the Maysensors module with property1.



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

Next, in the status update method of the desired object, you need to add a timer start:

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

Video with the operation of the sensor in the Majordomo system and the Majordroid application. I recommend that you look at it, wherever possible, to show the operation of the main functionality, and of course your likes and subscriptions will be especially invaluable for my little home channel, but by clicking on the bell you will not miss a video with my new sensors;).


The device board was made using the DeepTrace program. The development of this editor for the development of electronics once allowed me to greatly expand my capabilities. I note that I am not a professional electronics engineer, my experience in home development of motherboards is one and a half or two years. To everyone who makes their devices on breadboard, I recommend trying to learn some kind of editor, YouTube is full of video manuals.





Support for nRF5 chips in Maysensors is based on the Sandeep Mistry library - arduino-nRF5. But this library lacks support for the nRF52840, nRF52810 chips, and the completely new nRF52811 chips. I had to fork and add support for these chips, a transfer and adaptation was made from the Nordic SDK. There was no support for soft device since there is no particular need for using Mysensors, and there was no support for Port1 for nRF52840 chips. More recently, my research on this topic and the research of another member of the Maysensors community were combined and as a result, support for nRF52840 was already obtained with port1, the pins became just a sea.

The case for the sensor was developed in the SolidWorks program, it was also mastered independently from the lessons on YouTube about a year ago. The case was printed on an ANYCUBIC FOTON SLA printer. The quality and accuracy of the print I was very happy with. The only negative is a rather poor choice of UV resins with which such household printers can work. Dimensions of the device in the case: Long 43 mm, Width 26 mm, Height 12.5 mm. Case dimensions with magnet: Length 37 mm, Width 11 mm, Height 12.5 mm.









The sensor consumption in a dream ranged from 4 μA to 7 μA, depending on the chip selected. Consumption in data transfer mode was 8 mA.

The sensor uses a CR2032 battery. All measurements were made by the Chinese "multifiler" :) in view of the lack of a profiler due to its rather rather big cost :(.

The device can be repeated, use the written sketch or write your own. To repeat the sensor, everything you need is laid out on my github ( gerberas, code, case models ).

If someone is ready to assist in writing software under the ZIGBEE protocol, I will gladly cooperate.

If you are interested in this project, go to the telegram group, they will always be provided with assistance in mastering not only the Maysensors protocol , but also Zigbee and BLE on nRF5, they will promptly advise you on all issues of nRF52 programming in Arduino IDE and not only in it.

Carts chat where I live and people like me - @MYSENSORS_RUS .

Good to all!

All Articles