Блог о Gentoo и около-линуксовым штукам

10 апреля 2018 г.

NoWOL: питание ПК через интернет. Часть 1.

04:28 Опубликовал Дмитрий Исаенко , , , Нет комментариев
Ох, давно хотел уже задокументировать это здесь, да всё времени не было. И вот, спустя полтора года…

Какую проблему решаем?

Стоит задача включать и выключать ПК нажимая на кнопки включения\выключения и сброса не руками, а через интернет (ну или через тоннель в интранет). В идеале спросонья через виджет в телефоне а-ля узнал погоду за бортом, включил ПК и можно идти ставить кофе.

Для этого было решено пилить какой-то контроллер Atmega с каким-то ethernet-свистком, чтобы он через оптопары замыкал кнопки включения. А заодно сообщал об успехе или неуспехе затеи.

Немного покопавшись я решил закупить основанную на enc28j60 приблуду для ардуины, а заодно и саму ардуину. Изучив особенности программирования enc28j60 руками (без высокоуровневых IDE и готовых библиотек) и бегло осмотрев errata, который, надо отметить, чуть ли не больше даташита, стало ясно, что на низком уровне рулить “этим” сущий ад и боль. Поэтому, будем собирать всё на базе добротного китайского клона Arduino nano или функционального аналога (лучше просто взять arduino, правда).

В итоге система будет состоять из основного модуля в виде связки Arduino+enc28j60 и выносного блока с оптопарами и резисторами.

В целом выглядеть и подключаться всё будет так:

Но перед тем, как начать…

Поставим себе пару пакетов:
1. dev-embedded/arduino
2. sci-electronics/eagle (ветка 7.x.x и лучше из нестабильной ветки) 
Все необходимые файлы лежат в проекте на GitHub:
https://github.com/developersu/NoWOL

Про выносной модуль

Файлы для Eagle CAD находятся в директории eagle_files/m2Board/ .
Необходимые библиотеки в eagle_files/lib/ .

Для выносного модуля понадобится:

Part Value Device Package Library Sheet
IC2 КР293КП4А DIL08 ic-package 1
IC3 КР293КП4А DIL08 ic-package 1
IN PINHD-1X6 1X06 pinhead 1
OUT PINHD-1X6 1X06 pinhead 1
R1 450Ω или 470Ω R-EU_0204/5 0204/5 resistor 1
R2 450Ω или 470Ω R-EU_0204/5 0204/5 resistor 1
R3 330Ω R-EU_0204/5 0204/5 resistor 1
TO_MCU PINHD-1X5 1X05 pinhead 1

По-поводу R1 и R2 резисторов, я собрал две пары, чтобы получилось 450 Ом в каждой. Думаю, что если взять просто пару штук на 470 Ом всё тоже будет работать. Но всё же обещать не буду.

Его принципиальная схема выглядит весьма просто:

Разводка выполняется на односторонней плате:


* 'cuz soviet works better
Если присмотреться, разводка слегка отличается от той, что приведена на первом рисунке. Тут я убрал перемычку.

Как вы уже поняли, в качестве оптопар используется отечественная схема КР293КП4А в корпусе DIP8. Внутри каждой микросхемы находится 2 оптопары. Одна из схем замыкает и размыкает кнопки (питание и сброс), а вторая говорит микроконтроллеру включена ли система, определяя это по светодиоду-индикатору питания. Если первую микросхему замыкает МК, то вторую — ПК. Через отдельный пин микроконтроллер зажигает индикатор питания сам, когда понимает, что и система (ПК) тоже его зажгла. Уж не знаю.. может это несколько избыточно и можно было сделать проще. Впрочем, получилось как получилось.

Примечание: наверное надо было навесить на вторую оптопару какие-то резисторы.. но блин, datasheet по ним такой немногословный. В общем как-то работает уже больше года, так что, пока всё не сгорело, имеет право на жизнь.

Если всё спаять и приправить должным количеством пыли, получится примерно следующее:


Про программирование Arduino

Теперь перейдём к программированию Arduino. Как говорилось ранее, предполагается использование Arduino Nano.
Я уже не помню что куда нажимать и подключать, чтобы это дело прошить но у меня есть скетч!
Для этого скетча понадобится библиотека ehtercard. И скетч и библиотека находятся в директории arduino_sketchbook/.

А вот и сам код:
#include <EtherCard.h>

#define PWR_PIN 6
#define RST_PIN 7
#define PWR_LED_IN 8
#define PWR_LED_OUT 5


int i;
// Зададим MAC адрес устройства
static byte mymac[] = { 0xDE,0xAD,0xBE,0xEE,0xEE,0xEF };
 
// Чем больше данных отображается на странице, тем больше требуется буфер для них
byte Ethernet::buffer[900];
BufferFiller bfill;
 
//Массив задействованных контактов для управления оптопарами
int LedPins[] = {PWR_PIN,RST_PIN};
 
//Массив для фиксации изменений
boolean PinUpdate = 0;      //100-reset 200-power 5050-powerOFF
 
const char http_OK[] PROGMEM =
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/html\r\n"
"Pragma: no-cache\r\n\r\n"
"\r\n"
"<html>"
  "<head><title>"
    "noWOL"
  "</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><style>"
  "body { font-family: monospace;\n"
   "background-color: #000000;\n"
   "font-size: 30px;\n"
   "font-weight: bold;\n"
   "color: #b2b2b2;}\n"
   "a:link, a:visited {  display: inline-block;\n"
  "color: #b2b2b2;\n"
  "font-weight: 700;\n"
  "text-decoration: none;\n"
  "padding: .3em 2em;\n"
  "outline: none;\n"
  "border: 2px solid;\n"
  "border-radius: 15px;\n"
  "transition: 0.2s;}\n"
  "a:hover {color: #fefefe;}\n"
  "</style></head>"
  "<body><center>"
;
const char http_Found[] PROGMEM =
"HTTP/1.0 302 Found\r\n"
"Location: /\r\n\r\n";

const char http_Unauthorized[] PROGMEM =
"HTTP/1.0 401 Unauthorized\r\n"
"Content-Type: text/html\r\n\r\n"
"401 Unauthorized";
 
void homePage()
{
bfill.emit_p(PSTR("$F"
  " "
  "$F<p>"
  "<a href='?RESET=on'>  RESET   </a><p>"  //D6
  "<a href='?POWER0=on'>  POWER   </a><p>" //D7 short
  "<a href='?POWER1=on'>POWER 5sec</a><br />"),  //D7 long
  //"PWR LED out D5 : $F <br /></body></html>"),
  
  http_OK,
  (digitalRead(PWR_LED_IN) == HIGH)?PSTR("<span style=\"color: #AA0000\">          ▄▄▄▄▄▄</span>"):PSTR("<span style=\"color: #00c600\">          ▄▄▄▄▄▄</span>")),//⬤
//  (bitRead(PORTD,PWR_LED_OUT) == HIGH)?PSTR("HIGH"):PSTR("LOW")),

"</center></body>"
"</html>"
  ;
}

void pick_pin(int pin, int del) {
      digitalWrite(pin,HIGH);
        delay(del);
        digitalWrite(pin,LOW);
        PinUpdate = 0;
}
 
void setup()
{
// SET UART
//  Serial.begin(9600);
//Change default ethercard CS - pin 8 to 10
  //if (ether.begin(sizeof Ethernet::buffer, mymac, 10) == 0)

//  Serial.println( "Access to Ethernet controller failed");
    ether.begin(sizeof Ethernet::buffer, mymac, 10);
    ether.dhcpSetup();
//  if (!ether.dhcpSetup())
//       Serial.println("DHCP routine failed");
    //Print over UART configuration recieved:
//  ether.printIp("IP:  ", ether.myip);          
//  ether.printIp("GW:  ", ether.gwip);  
//  ether.printIp("DNS: ", ether.dnsip);  

  for(i = 0; i < 2; i++) {
    pinMode(LedPins[i],OUTPUT);
  }
  
    pinMode(PWR_LED_OUT,OUTPUT);
    pinMode(PWR_LED_IN,INPUT_PULLUP); // LED-питания как вход
}
 
void loop()
{
    delay(1);

        if (digitalRead(PWR_LED_IN) == HIGH) {        // если мать выключена
            if (bitRead(PORTD,PWR_LED_OUT) == HIGH) digitalWrite(PWR_LED_OUT,LOW);
        }
        else if (bitRead(PORTD,PWR_LED_OUT) == LOW) {
          digitalWrite(PWR_LED_OUT,HIGH);
        }

        switch (PinUpdate) {
          case 0:
            break;
          case 1:
            pick_pin(RST_PIN, 200);
            break;
          case 2:
            pick_pin(PWR_PIN, 200);
            break;
          case 3:
            pick_pin(PWR_PIN, 5050);
            break;
        }

    word len = ether.packetReceive(); //Проверить ethernet пакеты
    word pos = ether.packetLoop(len); //Проверить TCP пакеты
    if (pos) {
        bfill = ether.tcpOffset();
        char *data = (char *) Ethernet::buffer + pos;
        if (strncmp("GET /", data, 5) != 0) {
            bfill.emit_p(http_Unauthorized);
        }
        else {
            data += 5;
            if (data[0] == ' ') {
                    homePage(); //Если обнаружено изменения на станице, то запускаем функцию
            }
            //"10" = количество символов "?RESET=on ".
            else if (strncmp("?RESET=on ", data, 10) == 0) {
                bfill.emit_p(http_Found);
                                PinUpdate = 1;
            }
 
            else if (strncmp("?POWER0=on ", data, 11) == 0) {
                  bfill.emit_p(http_Found);
                                PinUpdate = 2;
            }
 
            else if (strncmp("?POWER1=on ", data, 11) == 0) {
                    bfill.emit_p(http_Found);
                                PinUpdate = 3;
            }

            else {
                //Страница не найдена
                bfill.emit_p(http_Unauthorized);
            }
        }
        ether.httpServerReply(bfill.position());
    }
}
Теперь, когда всё что надо прошито, соединяем как указано на рисунке в начале публикации и подключаем это дело к сети. На машрутизаторе должен быть настроен DHCP сервер.
Мак-адрес нового устройства, как ясно из исходного кода, будет de:ad:be:ee:ee:ef. Лучше сразу назначить постоянную аренду. В OpenWRT с LuCI идём в:
«Сеть» → «DHCP и DNS» → вкладка «Основные настройки» → «Постоянные аренды» → «Добавить».
Пишем там как показано на рисунке:


Когда всё настроено и подключено, заходим на адрес http://192.168.1.100 и видим следующее:


Только вот квадратик будет красным, а не зеленым. Это и есть индикатор питания.
Назначение кнопок пояснять нет смысла. Последняя симулирует долгое нажатие, что приводит к принудительному отключению.

То, что всё это находится в интрасети и не отсвечивает в мир меня вполне устраивает, ведь при подключении по VPN доступ появляется и в Android-приложении и через WEB-интерфейс. Ах да! Для всего этого дела есть ещё и Android-приложение :) Но о нём во второй части.

Вредные советы

Как бы не использовать Arduino?

Лучше таки его использовать.
Но можно и спаять свою ардуину, с блекджеком и профусетками, чтобы она была поменьше и конкретно под этот проект.
Сразу оговорюсь, я наделал кучу ошибок и паял отдельные костыли чтобы всё сошлось.. потом переделал схему, потом ещё пару раз, и уже не помню на сколько там правильная разводка. Так что лучше внимательно изучить и прислать мне фидбек в виде линки на исправленную версию. Или апробировать, если всё правильно =)

Используется двусторонний текстолит. Перемычки тут не помогут.

Находится всё в директории eagle_files/atmegaBoard/ .

Список компонентов:

Part Value Device Package Library Sheet
C1 22pF C-EUC0603 C0603 resistor 1
C2 22pF C-EUC0603 C0603 resistor 1
C3 10uF CC0603 C0603 eagle-ltspice 1
C4 22uF CC0603 C0603 eagle-ltspice 1
IC1 atmega328p atmega328p TQFP32-08 avr-6 1
IC3 LM1117IMPX-3.3 LM1117IMPX-3.3 SOT223 lm1117 1
JP1
PINHD-2X5 2X05 pinhead 1
JP3 PINHD-1X2 1X02 pinhead 1
Q1 16MHz XTAL/S QS special 1
SV1 MA05-1 MA05-1 con-lstb 1
U1 USB"" USB-MICRO-SMD MicroUSB 1

Схема принципиальная:


Сама разводка:


Что тут добавить.. земля на гребёнке справа. JP3 нужен для прошивки схемы. Прошивается она через Arduino IDE.. Кажется с помощью самой ардуины. Ну или через USBasp программатор. В общем, главное ничего не перепутать. А настройки должны быть вроде таких:

1)
 

2)


В следующей части будет обзор Android-приложения. APK файл находится в папке Android_APK.

Итого

У меня вся эта разработка шла не очень гладко, но получилось вполне сносно. Вот конечный результат в корпусе из спичек. Для антуража покрашено йодом.



Что касается дальнейшего развития этой инициативы, то я планирую добавить сюда ещё один выносной модуль для ещё одного ПК.

--
Особая благодарность Unyuu за неоценимый вклад. Во многом благодаря его советам этот проект стал возможен.


Лицензия Creative Commons
Текстовый материал распространяется по лицензии Creative Commons С указанием авторства-Некоммерческая-С сохранением условий 4.0 Всемирная.
Обратите внимание: это ограничение не касается графических изображений и исходного кода. Если на самом изображении не указано иного, они доступны по лицензии Creative Commons С указанием авторства 4.0 Всемирная.

0 коммент.:

Отправить комментарий