Страница 1 из 1

Замудренный выключатель

Добавлено: 10 фев 2018, 20:38
ahelper
Представляю вашему вниманию выключатель, по теме, "Я его слепил из того, что был". Датчики используются в связке с MajorDoMo.
Уважаемый Berk и Mikhail72 подтолкнули для применения данного функционала.
Что может данный выключатель:
1 - Может жить своей автономной жизнью. (Может работать без гейта)
2 - Управляет тремя нагрузками, как с шлюза, так и с кнопки (выключателя).
3 - При старте отправляет последнее состояние выходов и номер канала с ID ноды.
4 - Смена канала дистанционно. По умолчанию 76.
5 - Смена номера ноды (ID node) дистанционно.
6 - Если при первой передаче не было связи с гейтом, пытается 3 раза повторно отправить.
7 - Сохраняет количество недоставленных сообщений как ошибку.
8 - При появлении гейта в сети, после ошибки, отправляет последнее состояние выходов.

Желательно перед первой прошивкой стереть все данные в EEPROM стандартным скетчем "eeprom_clear", прописать 255 (можно и нули).
Смена канала возможна как с самой ноды так и с гейта (общее сообщение для всех нод), не проверял со спящими нодами. Собирал в библиотеке MySensors 2.2.0. Посмотреть количество недоставленных сообщений (ошибок), запросить презентацию.

т.к. У меня прошивка происходит по воздуху MYSBootloader, то мероприятия происходят так. Перевожу ноду на канал 76 (у меня канал основной работы выше 90), она автоматически перезагружается. Перевожу гейт на канал 76, он тоже автоматически перезагружается. Прошиваю новой прошивкой, и так же перевожу на нужный канал ноду и гейт.
Если в сети появляется новая нода (гейт в этот момент должен быть на канале 76), то в настройках модуля MySensors (MajorDoMo) стоит присваивать ей номер 1. Далее меняю на нужный ID, и все.
Скетч для гейта

Код: Выделить всё

int channelid;
byte fflag_ch = 0;
int all_ch = 0;

// Enable debug prints to serial monitor
//#define MY_DEBUG
//#define SERIAL_DEBUG

// Enable and select radio type attached
#define MY_RADIO_NRF24

#define MY_RF24_CHANNEL channelid

// Enable gateway ethernet module type
#define MY_GATEWAY_W5100

// W5100 Ethernet module SPI enable (optional if using a shield/module that manages SPI_EN signal)
//#define MY_W5100_SPI_EN 4

// Enable Soft SPI for NRF radio (note different radio wiring is required)
// The W5100 ethernet module seems to have a hard time co-operate with
// radio on the same spi bus.
#if !defined(MY_W5100_SPI_EN) && !defined(ARDUINO_ARCH_SAMD)
#define MY_SOFTSPI
#define MY_SOFT_SPI_SCK_PIN 14
#define MY_SOFT_SPI_MISO_PIN 16
#define MY_SOFT_SPI_MOSI_PIN 15
#endif

// When W5100 is connected we have to move CE/CSN pins for NRF radio
#ifndef MY_RF24_CE_PIN
#define MY_RF24_CE_PIN 5
#endif
#ifndef MY_RF24_CS_PIN
#define MY_RF24_CS_PIN 6
#endif

// Enable to UDP
//#define MY_USE_UDP

#define MY_IP_ADDRESS 192,168,1,20   // Установите свой желаемый IP
// Renewal period if using DHCP
//#define MY_IP_RENEWAL_INTERVAL 60000
// The port to keep open on node server mode / or port to contact in client mode
#define MY_PORT 5003

// Controller ip address. Enables client mode (default is "server" mode).
// Also enable this if MY_USE_UDP is used and you want sensor data sent somewhere.
//#define MY_CONTROLLER_IP_ADDRESS 192, 168, 178, 254

// The MAC address can be anything you want but should be unique on your network.
// Newer boards have a MAC address printed on the underside of the PCB, which you can (optionally) use.
// Note that most of the Ardunio examples use  "DEAD BEEF FEED" for the MAC address.
#define MY_MAC_ADDRESS 0x00, 0x2A, 0xF5, 0x12, 0x67, 0xFA

/*
// Enable inclusion mode
#define MY_INCLUSION_MODE_FEATURE
// Enable Inclusion mode button on gateway
//#define MY_INCLUSION_BUTTON_FEATURE
// Set inclusion mode duration (in seconds)
#define MY_INCLUSION_MODE_DURATION 60
// Digital pin used for inclusion mode button
//#define MY_INCLUSION_MODE_BUTTON_PIN  3

// Set blinking period
#define MY_DEFAULT_LED_BLINK_PERIOD 300

// Flash leds on rx/tx/err
// Uncomment to override default HW configurations
//#define MY_DEFAULT_ERR_LED_PIN 7  // Error led pin
//#define MY_DEFAULT_RX_LED_PIN  8  // Receive led pin
//#define MY_DEFAULT_TX_LED_PIN  9  // Transmit led pin
*/

#if defined(MY_USE_UDP)
#include <EthernetUdp.h>
#endif

#include <Ethernet.h>
#include <SPI.h>
#include <MySensors.h>

#define SKETCH_NAME "Gateway_W5100"
#define SKETCH_MAJOR_VER "16.02"
#define SKETCH_MINOR_VER "18"

#define CHANNEL_ID 200
#define PING_ID 222
#define ALL_CH_ID 250

MyMessage msgCH(CHANNEL_ID, V_STATUS);
MyMessage msgALL(ALL_CH_ID, V_VAR1);
MyMessage msgPing(PING_ID, V_VAR5);

const unsigned long tPing=180000; //Ping interval
unsigned long t;

//*********************************************************************

void before()
{
  channelid = loadState(200);
  all_ch = loadState(250);
  if ((channelid > 125) || (all_ch > 125))
  {
    channelid = 76;
    all_ch = 76;
    saveState(200, channelid);
    saveState(250, all_ch);
  }
}

//*********************************************************************

void presentation() {
  // Отправить версию для шлюза и контроллера
  sendSketchInfo(SKETCH_NAME, SKETCH_MAJOR_VER"."SKETCH_MINOR_VER);

  present(CHANNEL_ID, S_BINARY, "Gate channel ID");
  present(ALL_CH_ID, S_BINARY, "All channel ID");
}

//*********************************************************************

void setup() {
  #ifdef SERIAL_DEBUG
  Serial.println("####################################");
  Serial.print("################### --- >  CHANNEL = ");
  Serial.println(channelid);
  Serial.println("####################################");
  Serial.println("####################################");
  Serial.print("################### --- >  ALL CHANNEL = ");
  Serial.println(all_ch);
  Serial.println("####################################");
  Serial.println("Init done...");
  #endif
}

//*********************************************************************

void loop()
{
  if (fflag_ch == 0) {
    wait(10000);
    send(msgCH.set(channelid));
    wait(200);
    send(msgALL.set(all_ch));
    wait(200);
    fflag_ch = 1;
  }
  if ((millis()-t) > tPing) {
    send(msgPing.setDestination(255).set(PING_ID));
    t = millis();
  }
}

//*********************************************************************

void receive(const MyMessage &message)
{
    if (message.type == V_STATUS) {
    if (message.sensor == CHANNEL_ID)
    {
      channelid = message.getInt();
      if (channelid > 125) {
        channelid = 76;
        saveState(200, channelid);
        wait(500);
        send(msgCH.set(channelid));
        wait(3000);
        softReset();
      }
      else
      {
        saveState(200, channelid);
        wait(500);
        send(msgCH.set(channelid), 1);
        wait(3000);
        softReset();
      }
    }
  }

  if (message.type == V_VAR1) {
    if (message.sensor == ALL_CH_ID)
    {
      all_ch = message.getInt();
      send(msgALL.set(all_ch));
      wait(500);
      send(msgALL.setDestination(255).set(all_ch));
      wait(5000);
      send(msgALL.setDestination(255).set(all_ch));
      wait(5000);
      send(msgALL.setDestination(255).set(all_ch));
      wait(500);
      saveState(250, all_ch);
    }
  }
}

//*********************************************************************

void softReset() {
  asm volatile ("  jmp 0");
}
Скетч для ноды

Код: Выделить всё

int nodeid;
int channelid;
int error;
byte fflag_ch;
byte count;
bool gate = true;

// Включаем отладочные сообщения в сериал порт ноды
//#define MY_DEBUG
//#define SERIAL_DEBUG

#define MY_RADIO_NRF24

#define MY_RF24_CHANNEL channelid

#define MY_TRANSPORT_WAIT_READY_MS 10000

#define MY_PARENT_NODE_ID 0

#define MY_PARENT_NODE_IS_STATIC

#include <MySensors.h>
#include <Bounce2.h>
#include <avr/wdt.h>

#define SKETCH_NAME "Zall_new"
#define SKETCH_MAJOR_VER "16.02"
#define SKETCH_MINOR_VER "18"

#define noRelays 3
#define ERR_ID 252
#define NCHANNEL_ID 253
#define NODE_ID 254

const int relayPin[] = {4,5,6}; // Выход Реле
const int buttonPin[] = {14,15,16}; // Вход выключателя (кнопка)

class Relay // Класс реле, хранить все релевантные данные (эквивалентные структуре)
{
public: 
  int buttonPin;                    // physical pin number of button
  int relayPin;                     // physical pin number of relay
  byte oldValue;                    // last Values for key (debounce)
  boolean state;                // Состояние реле (также сохраняется в EEPROM)
};

Relay Relays[noRelays];
Bounce debouncer[noRelays];
MyMessage msg[noRelays];
MyMessage msgCH(NCHANNEL_ID, V_STATUS);
MyMessage msgID(NODE_ID, V_STATUS);
MyMessage msgERR(ERR_ID, V_VAR3);

//*********************************************************************

void before()
{
  if (hwReadConfig(EEPROM_NODE_ID_ADDRESS) == 0) {
    hwWriteConfig(EEPROM_NODE_ID_ADDRESS, 255);
  }
  channelid = loadState(200);
  fflag_ch = loadState(201);
  error = loadState(202);
  
  delay(20);
  if (fflag_ch == 255)
  {
    fflag_ch = 0;
    saveState(201, fflag_ch);
  }

  if (fflag_ch == 1)
  {
    if (channelid > 125)
    {
      channelid = 76;
      error = 0;
      saveState(200, channelid);
      saveState(202, error);
    }
  }

  if (fflag_ch == 0) {
      channelid = 76;
      saveState(200, channelid);
      #ifdef SERIAL_DEBUG
      Serial.print("#################### -------------------------------->>> ");
      Serial.println(channelid);
      Serial.print(" <<<-------------------------------- ####################");
      #endif
  }
  if (error >= 255)
  {
    error = 0;
    saveState(202, error);
    saveState(203, error);
  }
}
  
//*********************************************************************

void presentation()
{
  sendSketchInfo(SKETCH_NAME, SKETCH_MAJOR_VER"."SKETCH_MINOR_VER);
  present(0, S_LIGHT, "Relay 1");
  present(1, S_LIGHT, "Relay 2");
  present(2, S_LIGHT, "Relay 3");
  present(ERR_ID, S_CUSTOM, "Error send");
  present(NCHANNEL_ID, S_BINARY, "Chanel NRF24");
  present(NODE_ID, S_BINARY, "ID Node");
  
  send(msgERR.set(error));
}

//*********************************************************************

void setup()
{
  wdt_disable();
  
  transportSwitchSM(stReady); // Нужен для автономной работы Ноды

  if (fflag_ch == 1) {
    nodeid = hwReadConfig(EEPROM_NODE_ID_ADDRESS);
    send(msgCH.set(channelid));
    wait(200);
    send(msgID.set(nodeid));
    wait(200);
  #ifdef SERIAL_DEBUG
  Serial.println("####################################");
  Serial.print("################### --- >  CHANNEL = ");
  Serial.println(channelid);
  Serial.print("################### --- >  NODE ID = ");
  Serial.println(nodeid);
  Serial.println("####################################");
  #endif
  
  // Инициализировать реле и соответствующие кнопки
  for (int i = 0; i < noRelays; i++)
  {
    Relays[i].buttonPin = buttonPin[i]; // Назначить физические контакты
    Relays[i].relayPin = relayPin[i];
    msg[i].sensor = i;  // Инициализировать сообщения
    msg[i].type = V_STATUS;
    debouncer[i] = Bounce();  // Инициализировать debouncer
    debouncer[i].attach(buttonPin[i]);
    debouncer[i].interval(5);
    pinMode(Relays[i].buttonPin, INPUT_PULLUP);
    pinMode(Relays[i].relayPin, OUTPUT);
    Relays[i].state = loadState(i); // Извлечь последние значения из EEPROM
    digitalWrite(Relays[i].relayPin, Relays[i].state); // И соответственно установить реле
    send(msg[i].set(Relays[i].state)); // Информировать шлюз о последнем статусе
    wait(200);
  }

  #ifdef SERIAL_DEBUG
  Serial.begin(115200);
  Serial.println("Ready!");
  #endif
  
  wdt_enable(WDTO_8S);
  }
}

//*********************************************************************

void loop()
{
  wdt_reset();

  if (fflag_ch == 0) {
    int hd = transportGetDistanceGW();
    wait(100);
    if ((hd == 255) || (hd == 0))
    {
      softReset();
    }
    else
    {
      fflag_ch = 1;
      saveState(201, fflag_ch);
      wait(500);
      softReset();
    }
  }

  if (fflag_ch == 1) {
  //Обработка нажатия кнопок
  for (byte i = 0; i < noRelays; i++)  {
    debouncer[i].update();
    byte value = debouncer[i].read();
    if (value != Relays[i].oldValue && value == 0)
    {
      Relays[i].state = !Relays[i].state;
      digitalWrite(Relays[i].relayPin, Relays[i].state);
      saveState( i, Relays[i].state );
      bool send_data = send(msg[i].set(Relays[i].state));
      wait(100);
      if (send_data == 1){                             // Если сообщение отправлено
        #ifdef SERIAL_DEBUG
        Serial.println("Message sent OK");                // Вывести в сериал "Сообщение отправлено"
        #endif
        } else {
  while (send_data == 0) {                             // Пока статус отправки ложь
      count++;                                         // Включаем счётчик
      #ifdef SERIAL_DEBUG
      Serial.print("Sending a message, try No.");      // Выводим в монитор порта попытку отправки
      Serial.println(count);                           // и её номер
      #endif
      send_data = send(msg[i].set(Relays[i].state));   // Отправляем сообщение
      wait(100, 1, V_STATUS);                          // Ждём подтверждения отправки
      if (send_data == 1){                             // Если сообщение отправлено
        count = 0;
        #ifdef SERIAL_DEBUG
        Serial.println("Message sent, OK");                // Вывести в сериал "Сообщение отправлено"
        #endif
      }
      if ((count == 2 )&&(send_data == 0)){            // Если сделано 3 попытки и нет подтверждения отправки
        count = 0;                                     // Обнуляем счётчик
        send_data = 1;                                 // Выходим из цикла
        gate = false;
        error ++;
        saveState(202, error);
        #ifdef SERIAL_DEBUG
        Serial.println("Send failed, NG");                 // Выводим сообщение "Отправка не удалась"
        Serial.print("ERROR - ");
        Serial.println(error);
        #endif
        }
      }
    }
  }
    Relays[i].oldValue = value;
    }
  }
}

//*********************************************************************

// Обрабатывать входящие сообщения
void receive(const MyMessage &message)
{
  if (message.type == V_STATUS) {
    if (message.sensor < noRelays)  // Проверить, действительно ли сообщение для реле ..... предыдущая строка  [[[ if (message.sensor <=noRelays){ ]]]
    {
      Relays[message.sensor].state = message.getBool();
      digitalWrite(Relays[message.sensor].relayPin, Relays[message.sensor].state); // И соответственно настройте реле
      saveState( message.sensor, Relays[message.sensor].state ); // Сохранить состояние датчика в EEPROM (положение == номер датчика)
      // Обработка ошибок через com порт
   #ifdef SERIAL_DEBUG
     Serial.print("Incoming change for sensor:");
     Serial.print(message.sensor);
     Serial.print(", New status: ");
     Serial.println(message.getBool());
   #endif
    }
    if (message.sensor == NCHANNEL_ID)
    {
      channelid = message.getInt();
      if (channelid > 125) {
        channelid = 76;
        saveState(200, channelid);
        wait(500);
        send(msgCH.set(channelid));
        wait(3000);
        softReset();
      }
      else
      {
        saveState(200, channelid);
        wait(500);
        send(msgCH.set(channelid));
        wait(3000);
        softReset();
      }
    }
    if (message.sensor == NODE_ID)
    {
      nodeid = message.getInt();
      hwWriteConfig(EEPROM_NODE_ID_ADDRESS, nodeid);
      send(msgID.set(nodeid));
      wait(500);
      softReset();
    }
  }
  if (message.type == V_VAR1) {
    if (message.sensor == 250)
    {
      channelid = message.getInt();
      if (channelid > 125) {
        channelid = 76;
        saveState(200, channelid);
        wait(500);
        send(msgCH.set(channelid));
        wait(3000);
        softReset();
      }
      else
      {
        saveState(200, channelid);
        wait(500);
        send(msgCH.set(channelid));
        wait(3000);
        softReset();
      }
    }
  }
  if (gate == false) {
    if (message.type == V_VAR5) {
      if (message.sensor == 222)
      {
        for (int i = 0; i < noRelays; i++)
  {
        send(msg[i].set(Relays[i].state));
        wait(50);
  }
        gate = true;
      }
    }
  }
}

//*********************************************************************

void softReset() {
  asm volatile ("  jmp 0");
}
Перезалил оба скетча, что изменилось:
Так как нет еще информации о том, шлет ли что то в эфир гейт или нет. Решил сам добавить маленький функционал.
Теперь гейт каждые 3 минуты спамит (сообщает) в общий эфир, что он в сети. А нода выключатель, если были изменения ее состояния, но гейт не ответил. Включает флаг нет гейта и при первом сообщении, что гейт в сети, отсылает состояния своих выходов.
Если есть другая реализация данного функционала, буду признателен за информацию.