Автоматическая перезагрузка и удаленное включение/выключение/перезагрузка ПК/фермы с уведомлениями в Телеграм с помощью NodeMCU под Espruino v1.87

NodeMCU ESP8266 Relay 5V
Осуществлен переход на Espruino v1.9x, что позволило использовать штатную функцию wifi.ping() и отказаться от установки веб-сервера на ПК. Доработана параметризация через массивы в форме объектов.

Код:

var debug = 1;
var autostart = 0;
var autostart_timeOut = 15000;
var counter_limit = 5;
var reboot_limit = 3;

var wifi = require("Wifi");
  wifi.stopAP();
  var ssid = "YOUR_SSID";
  var password = "YOUR_PASSWORD";
  wifi_timeOut = 5000;

var TOKEN = "YOUR_TOKEN";

var pin = {};
  pin['A'] = "D12";
  pin['B'] = "D14";
  pin['C'] = "D4";
  pin['D'] = "D5";

var ip = {};
  ip['A'] = "10.0.0.40";
  ip['B'] = "10.0.0.20";
  ip['C'] = "10.0.0.39";

var counter = {};

var state = {};
  state['WiFi'] = 0;
  state['stop_reboot'] = 0;
  state['stop_loops'] = 0;

var reboot_counter = {};

var timeOut = {};

ip.forEach(function(item, key){ // set default values
  counter[key] = 0;
  state[key] = 1;
  reboot_counter[key] = 0;
  timeOut[key] = 60000;
});

function pingR(index) {
  setTimeout(function(){
    if (counter[index] > counter_limit-1) {
      if (reboot_counter[index] < reboot_limit-1) {
        if (debug) console.log("reboot");
        sendMessage("Ferma%20"+index+"%20reboot");
        if (state['stop_reboot'] == 0) {
          ferma_switch(index);
        } else {
          if (debug) console.log("reboot stopped");
          sendMessage("Ferma%20"+index+"%20reboot sopped");
        };
        counter[index]=0;
        reboot_counter[index]++;
      } else {
        if (debug) console.log("reboot limit");
        if (debug) console.log("set 2x timeout");
        sendMessage("Ferma%20"+index+"%20reboot%20limit");
        sendMessage("Set 2x timeout for Ferma%20"+index);
        timeOut[index] = 2 * timeOut[index];
        reboot_counter[index] = 0;
      }
    } else {
      ping(index);
    }
    if (debug) console.log(counter[index]);
    pingR(index);
  }, timeOut[index]);
  if (state['stop_loops'] == 1) {
    if (debug) console.log("stop all loops");
    clearInterval();
  };
  return 1;
}

function ping(index){
  var subcounter = 0;
  var ping_count = 0;
  if (debug) console.log(index);
  wifi.getStatus(function(data) {
    if (data.station == "connected"){
      if (debug) console.log("Wi-Fi ok");
      wifi.ping(ip[index], function(time) {
        ping_count++;
        if (time.error == -1) {
          subcounter++;
          if (ping_count == 5 && subcounter == 5) {
            if (debug) console.log("Ferma "+index+" ping fail");
            sendMessage("Ferma%20"+index+"%20ping%20fail");
            counter[index]++;
            state[index] = 0;
          } else if (ping_count == 5)  {
            counter[index] = 0;
            reboot_counter[index] = 0;
            timeOut[index] = 60000;
            if ( state[index] === 0 ) sendMessage("Ferma%20"+index+"%20ping%20ok");
            state[index] = 1;
          }
        }
      });
    } else {
     if (debug) console.log("Wi-Fi error"); 
    }
  });
  return 0;
}

function status() { //foreach ip
  var message = "";
  ip.forEach(function(item, key){
    if (state[key] == 1) {
      message += " Ferma " + key + " online";
    } else {
      message += " Ferma " + key + " offline";
    }
  });
  return message;
}

function ferma_on(index){
  pinMode(pin[index], "output");
  setTimeout(function(){
    pinMode(pin[index], "input");
  }, 2000);
}

function ferma_off(index){
  pinMode(pin[index], "output");
  setTimeout(function(){
    pinMode(pin[index], "input");
  }, 10000);
}

function ferma_switch(index){
  pinMode(pin[index], "output");
  setTimeout(function(){
    pinMode(pin[index], "input");
    setTimeout(function(){
      pinMode(pin[index], "output");
      setTimeout(function(){
        pinMode(pin[index], "input");
      }, 2000);
    }, 5000);
  }, 5000);
}

function self_reboot() {
  require("ESP8266").reboot();
}

function sendMessage(message) {
  require("http").get("http://crierbot.appspot.com/" + TOKEN + "/send?message="+message);
}

function help() {
  print ("Ferma switch PIN A-D12 B-D14 C-D4 D-D5");
}

function connect() {
  wifi.connect(ssid, {password: password}, function(err){
    if (debug) console.log("connected? err=", err, "info=", wifi.getIP());});
    setTimeout(function(){
      wifi.getIP(function(data){
        if (debug) console.log(data);
        if (data.ip == "0.0.0.0") {
          if (debug) console.log(data.ip);
          state['WiFi'] = 0;
          connect();
        } else {
          state['WiFi'] = 1;
        }
      });
    }, 15000);
}

function onInit() {
  setTimeout(function(){
    connect();
  }, wifi_timeOut);
  if (autostart == 1) {
    ip.forEach(function(item, key){
      setTimeout(function(){
          pingR(key);
          autostart_timeOut += 5000;
        }, autostart_timeOut);
    });
  }
}					

Решена проблема с подключенем к Wi-Fi при перезагрузке. Подключение вынесено в отдельную фунцкцию connect(), которая проверяет получение IP-адреса и если он равен 0.0.0.0 вызывает себя вновь.
Для того чтобы ограничить бесконечную автоматическую перезагрузку добавлена переменная counter_limit. Функция проверки доступности проверяет текущее значение счетчика counter с counter_limit, если достигнут предел перезгрузка не будет выполнена. После перезагрузки счетчик сбрасывается на 0, как и вслучае восстановления доступности до перезагрузки.
Чтобы иметь возможность принудительно отключить автоматическую перезагрузку добавлена переменная stop. Если в консоли задать значение равное 1, перезагрзка не будет выполнена.

Код:

var debug = 1;
var autostart = 0;
var counter_limit = 5;
var stop = 0;
var wifi = require("Wifi");
wifi.stopAP();
var p = require("Ping");
var TOKEN = "YOUR_TOKEN";
var ssid = "YOUR_SSID";
var password = "YOUR_PASSWORD";
var ipA = "10.0.0.40";
var ipB = "10.0.0.20";
var ipC = "10.0.0.39";
var port_8000 = 8000;
var port_4200 = 4200;
var counter = [];
counter.A = 0;
counter.B = 0;
counter.C = 0;
var state = [];
state.A = 1;
state.B = 1;
state.C = 1;

function status() {
  var message = "";
  if (state.A == 1) {
    message = "Ferma A online";
  } else {
    message = "Ferma A offline";
  }
  if (state.B == 1) {
    message += " Ferma B online";
  } else {
    message += " Ferma B offline";
  }
  if (state.C == 1) {
    message += " Ferma C online";
  } else {
    message += " Ferma C offline";
  }
  return message;
}

function sendMessage(message) {
  require("http").get("http://crierbot.appspot.com/"+TOKEN+"/send?message="+message);
}

function help() {
  print ("Ferma switch PIN B-D14 A-D12 C-D4");
}

function pingA() {
  setTimeout(function(){
    if (counter.A > counter_limit-1) {
      if (debug == 1) console.log("reboot");
      sendMessage("Ferma%20A%20reboot");
      if (stop == 0)ferma_switch(D12);
      counter.A=0;
    } else {
      ping(ipA, "A", port_4200);
    }
    if (debug == 1) console.log(counter.A);
    pingA();
  }, 60000);
  return 1;
}
function pingB() {
  setTimeout(function(){
    if (counter.B > counter_limit-1) {
      if (debug == 1) console.log("reboot");
      sendMessage("Ferma%20B%20reboot");
      if (stop == 0) ferma_switch(D14);
      counter.B=0;
    } else {
      ping(ipB, "B", port_4200);
    }
    if (debug == 1) console.log(counter.B);
    pingB();
  }, 60000);
  return 1;
}
function pingC() {
  setTimeout(function(){
    if (counter.C > counter_limit-1) {
      if (debug == 1) console.log("reboot");
      sendMessage("Ferma%20C%20reboot");
      if (stop == 0) ferma_switch(D4);
      counter.C=0;
    } else {
      ping(ipC, "C", port_4200);
    }
    if (debug == 1) console.log(counter.B);
    pingC();
  }, 60000);
  return 1;
}
function stop_on() {
  stop=1;
}
function stop_off() {
  stop=0;
}
function ping(ip, index, ping_port){
  if (debug == 1) console.log(index);
  wifi.getStatus(function(data) {
    if (data.station == "connected"){
      if (debug == 1) console.log("Wi-Fi ok");
      p.ping({ address: ip, port: ping_port }, function(err1, data1) {
      if (debug == 1) console.log(err1);
      if (debug == 1) console.log(data1);
      if (err1) {
          sendMessage("Ferma%20"+index+"%20ping%20fail");
          switch (index) {
            case "A":
              counter.A++;
              state.A = 0;
              break;
            case "B":
              counter.B++;
              state.B = 0;
              break;
           case "C":
              counter.C++;
              state.C = 0;
              break;
          }
        } else {
          switch (index) {
            case "A":
              counter.A = 0;
              if ( state.A === 0 ) sendMessage("Ferma%20A%20ping%20ok");
              state.A = 1;
              break;
            case "B":
              counter.B = 0;
              if ( state.B === 0 ) sendMessage("Ferma%20B%20ping%20ok");
              state.B = 1;
              break;
            case "C":
              counter.C = 0;
              if ( state.C === 0 ) sendMessage("Ferma%20C%20ping%20ok");
              state.C = 1;
              break;
          }
        }
  });
    } else {
     if (debug == 1) console.log("Wi-Fi error"); 
    }
  });
  return 0;
}
function ferma_on(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
  }, 2000);
}
function ferma_off(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
  }, 10000);
}

function ferma_switch(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
    setTimeout(function(){
      pinMode(pin, "output");
      setTimeout(function(){
        pinMode(pin, "input");
      }, 2000);
    }, 5000);   
  }, 5000);
}

function connect() {
  wifi.connect(ssid, {password: password}, function(err){
    if (debug) console.log("connected? err=", err, "info=", wifi.getIP());});
    setTimeout(function(){
      wifi.getIP(function(data){
        if (debug) console.log(data);
        if (data.ip == "0.0.0.0") {
          if (debug) console.log(data.ip);
          connect();
        }
      });
    }, 15000);
}

function onInit() {
  setTimeout(function(){
    connect();
  }, 5000);
  if (autostart == 1) {
    pingA();
    setTimeout(function(){
      pingB();
      setTimeout(function(){
        pingC();
      }, 15000);
    }, 15000);
  }
}

Реализация watchdog c уведомлениями в Telegram и удаленным контролем:

Для работы требуется плата NodeMCU предварительно перепрошитая Espruino и модуль реле.

Скетч каждую минуту "пингует" заданный порт ПК, на котором висит miniweb сервер (так как не удалось задейстовать встроенные в Windows простые службы TCP для целей данной разработки), если в течении 5 итерраций порт не доступен, дергает реле и сбрасывает счетчик. Подключиться к NodeMCU можно по сети telnet'ом или через Espruino IDE и вызвать функции включения, отключения или перезагрузки ПК, вывести текущее состояние наблюдаемого ПК.

Перед "пингом" проверяется состояние подключения Wi-Fi.

Если "пинг" возобновляется до достижения порогового числа срабатываний, счетчик сбрасывается на 0.

Фиксируются текущее состояние и сравнивывается с предыдущим наблюдаемого ПК.

Уведомления в Телеграм отправляются через стороннего бота Crierbot (см. crierbot.appspot.com), токен которого необходимо предварительно получить. Так как текст сообщения отправляется в составе URL, пробелы необходимо заменять на %20.
Использование собственного Телеграм-бота является, безусловно, более предпочтительным вариантом + позволит интерактивно управлять устройством, но в текущей реализации при использовании API Телеграм через функцию http.request() не смотря на явное указание protocol: https судя по ответу сервера Телеграм отправка запроса идет по HTTP (без шифрования на 443 порту) и отвергается сервером.

Автоматический запуск мониторинга запускает функции для мониторинга двух ПК последовательно с задержкой в 30 секунд, так как на практике неблюдается нестабильная работа функции создания сетевых подключений/сокетов, запущенных в одном интервале времени, что отражается как на "пинге" так и на отправке уведомлений. Насколько удалось понять по сообщениям пользователей на форумах, это следствие использования NodeMCU и в оригинальных платах Espruino подобной проблемы нет.

После первой заливки скетча необходимо вызвать процедуру save() для сохранения кода в память платы.

Так как к NodeMCU подключены реле 5 В, то для их срабатывание используется нестандартный подход - переключение режима пина вход-выход. Реле запитаны от пина VV, на который выведено 5 В с USB, что есть не на всех платах. В случае использование реле на 3 В, управление ими [вероятно] будет традиционным через устновку HIGH или 1 на соответствущем пине, в текущем же варианте наблюдается включение светодиода на модуле реле, который сигнализирует о срабатывании, но при этом само реле не переключается. Так же опытным путем установлено, что не все пины можно использовать для подобного включения реле, выявлены только D1, D2, D5 и D6 (но проверялись не все), при этом стоит иметь ввиду, что обозначения пинов на плате и их номера в коде не совпадают. NodeMCU в текущей реализации подключена к порту USB наблюдаемого ПК и загружается сразу при подключении блока питания ПК к электрической сети без включения ПК.

Реле подключены к пинам Power switch на материнских платах, использование пинов Reset switch в текущей реализации не дало гарантированной перезагрузки ПК + в ряде случаев для устранения проблем в работе видеокарт требуется именно отключение питания. Реле подключены так чтобы быть нормально выключенными - COM-NO.

Преимущества:

  • Цена: 250 р. + 50 р. за каждый дополнительный канал/реле;
  • Независимость от ОС, драйверов, программного обеспечения (в v0.2 нужна только сетевая служба, к которой возможно подключение;
    в v0.3 используется ICMP);
  • Удаленное интерактивное управление и обновление "прошивки" по воздуху;
  • Уведомления в Телеграм;
  • Масштабируемость;
  • Возможность подключения датчиков(температура, дым) и модулей.

Дорожная карта:

  • Переход на Espruino >v1.9x и использование функции Wifi.Ping() вместо модуля Ping, работающего на основе сокетов; см. v0.3
  • Защита от бесконечной перезагрузки; Решено в версиях >v0.21
  • Оптимизация кода;
  • Звуковая и визуальная индикации текущего состояния;
  • Свой Телеграм-бот;
  • Замена реле на оптроны;
  • Проверка работы функции wifi.restore() для автоматического восстановления Wi-Fi-соединения при перезагрузке платы.
    Так как настройки Wi-Fi сохраняются в памяти NodeMCU и нет необходимости каждый раз вызывать wifi.connect(), как и хранить параметры подключения;
    Не работает на NodeMCU.
  • Подключение к Wi-Fi при питании платы от внешнего БП. По неизвестной причине работает только если плата подключена к ПК. Решено в версиях >v0.21

Код:

var debug = 0;
var autostart = 1;
var counter_limit = 5;
var wifi = require("Wifi");
wifi.stopAP();
var p = require("Ping");
var TOKEN = "YOUR_TOKEN";
//define RELAY1R_PIN 4 5 // reboot D2 D1
//define RELAY1P_PIN 14 12 // power D5 D6
var ssid = "YOUR_SSID";
var password = "YOUR_PASSWORD";
var ipA = "10.0.0.40";
var ipB = "10.0.0.20";
var port = 8000;
var counter = [];
counter.A = 0;
counter.B = 0;
var state = [];
state.A = 1;
state.B = 1;

function status() {
  var message = "";
  if (state.A == 1) {
    message = "Ferma A online";
  } else {
    message = "Ferma A offline";
  }
  if (state.B == 1) {
    message += " Ferma B online";
  } else {
    message += " Ferma B offline";
  }
  return message;
}

function sendMessage(message) {
  require("http").get("http://crierbot.appspot.com/"+TOKEN+"/send?message="+message);
}

function help() {
  print ("Ferma switch PIN B-D14 A-D12");
}

function pingA() {
  setTimeout(function(){
    if (counter.A > counter_limit-1) {
      if (debug == 1) console.log("reboot");
      sendMessage("Ferma%20A%20reboot");
      ferma_switch(D12);
      counter.A=0;
    } else {
      ping(ipA, "A");
    }
    if (debug == 1) console.log(counter.A);
    pingA();
  }, 60000);
  return 1;
}
function pingB() {
  setTimeout(function(){
    if (counter.B > counter_limit-1) {
      if (debug == 1) console.log("reboot");
      sendMessage("Ferma%20B%20reboot");
      ferma_switch(D14);
      counter.B=0;
    } else {
      ping(ipB, "B");
    }
    if (debug == 1) console.log(counter.B);
    pingB();
  }, 60000);
  return 1;
}
function ping(ip, index){
  //console.log('.');
  //sendMessage(index);
  if (debug == 1) console.log(index);
  wifi.getStatus(function(data) {
    if (data.station == "connected"){
      if (debug == 1) console.log("Wi-Fi ok");
      p.ping({ address: ip, port: port }, function(err1, data1) {
      if (debug == 1) console.log(err1);
      if (debug == 1) console.log(data1);
      if (err1) {
          sendMessage("Ferma%20"+index+"%20ping%20fail");
          switch (index) {
            case "A":
              counter.A++;
              state.A = 0;
              break;
            case "B":
              counter.B++;
              state.B = 0;
              break;
          }
        } else {
          //console.log('+'+index);
          //sendMessage('Ferma%20'+index+'%20ping%20ok');
          switch (index) {
            case "A":
              counter.A = 0;
              if ( state.A == 0 ) sendMessage("Ferma%20A%20ping%20ok");
              state.A = 1;
              break;
            case "B":
              counter.B = 0;
              if ( state.B == 0 ) sendMessage("Ferma%20B%20ping%20ok");
              state.B = 1;
              break;
          }
        }
  });
    } else {
     if (debug == 1) console.log("Wi-Fi error"); 
    }
  });
  return 0;
}
function ferma_on(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
  }, 2000);
}
function ferma_off(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
  }, 10000);
}

function ferma_switch(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
    setTimeout(function(){
      pinMode(pin, "output");
      setTimeout(function(){
        pinMode(pin, "input");
      }, 2000);
    }, 5000);   
  }, 5000);
}
//wifi.restore();
wifi.connect(ssid, {password: password}, function(err){
  if (debug == 1) console.log("connected? err=", err, "info=", wifi.getIP());
  if (!err && autostart == 1) {
    pingA();
    setTimeout(function(){
      pingB();
    }, 30000);
  }
});
wifi.save();
//save();

Начальный вариант реализации. Для управления нагрузкой используется веб-интерфейс.

Код:

var debug = 0;
var autostart = 1;
var counter_limit = 5;

var wifi = require("Wifi");
wifi.stopAP();
var p = require("Ping");

var ssid = "YOUR_SSID";
var password = "YOUR_PASSWORD";
var ipA = "10.0.0.40";
var ipB = "10.0.0.20";
var port = 8000;
var counter = [];
counter.A = 0;
counter.B = 0;

function pingA() {
  setTimeout(function(){
    if (counter.A > counter_limit-1) {
      if (debug == 1) console.log("reboot");
      ferma_switch(D12);
      counter.A=0;
    } else {
      ping(ipA, "A");
    }
    if (debug == 1) console.log(counter.A);
    pingA();
  }, 60000);
  return 1;
}
function pingB() {
  setTimeout(function(){
    if (counter.B > counter_limit-1) {
      if (debug == 1) console.log("reboot");
      ferma_switch(D14);
      counter.B=0;
    } else {
      ping(ipB, "B");
    }
    if (debug == 1) console.log(counter.B);
    pingB();
  }, 60000);
  return 1;
}
function ping(ip, index){
  console.log('.');
  if (debug == 1) console.log(index);
  wifi.getStatus(function(data) {
    if (data.station == "connected"){
      if (debug == 1) console.log("Wi-Fi ok");
      p.ping({ address: ip, port: port }, function(err1, data1) {
      if (debug == 1) console.log(err1);
      if (debug == 1) console.log(data1);
      if (err1) {
          switch (index) {
            case "A":
              counter.A++;
              break;
            case "B":
              counter.B++;
              break;
          }
        } else {
          switch (index) {
            case "A":
              counter.A=0;
              break;
            case "B":
              counter.B=0;
              break;
          }
        }
  });
    } else {
     if (debug == 1) console.log("Wi-Fi error"); 
    }
  });
  return 0;
}
function ferma_on(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
  }, 2000);
}
function ferma_off(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
  }, 10000);
}
function ferma_switch(pin){
  pinMode(pin, "output");
  setTimeout(function(){
    pinMode(pin, "input");
    setTimeout(function(){
      pinMode(pin, "output");
      setTimeout(function(){
        pinMode(pin, "input");
      }, 2000);
    }, 5000);   
  }, 5000);
}
wifi.connect(ssid, {password: password}, function(err){
  if (debug == 1) console.log("connected? err=", err, "info=", wifi.getIP());
  if (!err && autostart == 1) {
    pingA();
    pingB();
  }
});
wifi.save();
//save();

30 апреля 2018
Версия 0.3
использование wifi.ping()
оптимизация кода

4 апреля 2018
Версия 0.21
решена проблема с подключением при перезагрузке
добавлена возможность остановить автоматическую перезагрузку
добавлено ограничение на число автоматических перезагрузок

4 марта 2018
Версия 0.2
уведомления в Телеграм через Crierbot
контроль текущего и предыдущего состояний наблюдаемого ПК

27 февраля 2018
Версия 0.1