Керування через Internet без реальних ip адрес

Управление устройствами через Интернет без использования реальных IP - адресов

Мясищев А.А.
Практика для студентов

Для удаленного управления через Интернет обычно используют web - сервер на микроконтроллере и клиентскую часть - браузер на компьютере, подключенные к Интернет. Причем сервер должен иметь реальный ip - адрес, или выход на него через NAT - сервер (например, по технологии проброса портов). IP - может быть и динамическим, но в этом случае необходимо использование DynamicDNS(DDNS), позволяющей связать внешний динамический ip-адрес и постоянное доменное имя. Однако не всегда удается получить доступ к реальному адресу. В этом случае необходимо между браузером клиента и удаленным микроконтроллером установить промежуточный web - сервер, который способен исполнять скрипты на PHP (рис.1).

Постановка задачи.
1. Клиент на микроконтроллере по командам от клиента на Андроид устройстве должен управлять 3-мя устройствами (включать и выключать) и снимать показания с температурного датчика.
2. Параметры IP сети (адрес, шлюз и т.д.) микроконтроллер должен получать от DHCP сервера при подключении его к сети.
3. Доменное имя или IP адрес промежуточного сервера должно храниться в энергонезависимой памяти. Должна быть возможность его изменения при подключении микроконтроллера к последовательному порту компьютера.
4. Программа на Андроид устройстве должна просто в кнопочном режиме управлять оборудованием микроконтроллера. Имя промежуточного сервера также должно храниться в энергонезависимой памяти Андроид устройства и должна быть возможность его замены.
5. Должна быть предусмотрена возможность запроса Андроид устройством состояния оборудования микроконтроллера.
6. Должна быть предусмотрена возможность использования бесплатного web hosting – а с возможностью запуска PHP скриптов в качестве промежуточного сервера.

Рис.1. Схема взаимодействия через промежуточный сервер.

Для решения перечисленных задач рассмотрим следующую схему работы системы с последующим поэтапным решением:
1. Браузер клиента (клиентская программа на сматфоне) для изменения состояния удаленного устройства обращается к промежуточному серверу http://ksm.890m.com и запускает скрипт с параметром a:
http://ksm.890m.com/b.php?a=0101
Кодирование параметра a понятно из следующего пояснения:
0101 - не выполнять запрос состояния оборудования, включить красный, выключить зеленый, включить синий.
При выполнении скрипт формирует на сервере файл comand.html, который и содержит значения параметра a (0101).

Ниже представлен скрипт b.php:

<?php
$a=$_GET[a];
echo "Received server XRGB: ", $a, " 0-Off, 1-On";
$file = fopen ("comand.html","w");
if ( !$file )
{
echo("Error reading file");
}
else
{
fputs ( $file, $a);
}
fclose ($file);
?>
Пример содержимого файла comand.html (некоторые варианты):
0110 -Нет запроса о состоянии оборудования и температуры;
красный включен, зеленый включен, синий выключен.
1XXX -Запрос состояния оборудования. Состояние оборудования не изменяется.
0XXX -Сброс запроса состояния оборудования.

2. Микроконтроллер работает как Web - клиент, каждые 14 секунд считывает значение файла comand.html. Если первая позиция(слева) файла -1, то микроконтроллер запускает каждые 14 секунд на сервере файл c.php и сообщает ему через параметры состояние оборудования и температуру. В этом случае микроконтроллер обращается к серверу каждые 7 секунд. Исполнение любой команды от Андроид устройства выполнится не позже, чем через 14 секунд при идеальной работе сервера и Интернет. Можно уменьшить обращение микроконтроллера к серверу до одной секунды, но тогда за одни сутки будет выполнено 86400 обращений к серверу. Если заказан бесплатный web - hosting, то он ограничен 10000 обращениями в сутки. В случае превышения, доступ будет блокирован. Существуют и другие ограничения, поэтому для стабильной работы системы управления через Интернет, их необходимо учитывать. При включении микроконтроллера и удерживании примерно 10 секунд кнопки на порту PD4 (4-й вывод Ардуино) через терминальную программу можно ввести имя (ip адрес) промежуточного сервера. После включения микроконтроллер по протоколу DHCP автоматически устанавливает свои ip параметры (ip адрес и т.д.). Работа в сети обеспечивается модулем WIZ812MJ (рис.2) с микросхемой W5100.

Рис.2. Модуль WIZ812MJ

Программа для микроконтроллера в среде Ардуино 
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SPI.h>
#include <Ethernet.h>
#include <EEPROM.h>
#define ONE_WIRE_BUS 6
OneWire oneWire(ONE_WIRE_BUS); //Настройка 1wire для работы с 5-м выводом Ардуино
DallasTemperature sensors(&oneWire); //Подключаем датчик температуры
int ii=1;
int ij=0;
byte r=0,g=0,b=0;
char buf[100];
char bb[40];
// assign a MAC address for the ethernet controller.
// fill in your address here:
byte mac[] = {
0x0E, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
// fill in an available IP address on your network here,
// for manual configuration:
IPAddress ip(192,168,1,30);

// fill in your Domain Name Server address here:
IPAddress myDns(8,8,8,8);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

// initialize the library instance:
EthernetClient client;
int ia=0,ja=0;
int addr = 0;
char server[100];
int buttonState = 1;

unsigned long lastConnectionTime = 0; // last time you connected to the server, in milliseconds
boolean lastConnected = false; // state of the connection last time through the main loop
const unsigned long postingInterval = 14*1000; // delay between updates, in milliseconds

int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
void setup() {
pinMode(3, OUTPUT);
pinMode(4, INPUT);
pinMode(5, OUTPUT);
pinMode(7, OUTPUT);
// start serial port:
Serial.begin(9600);
// give the ethernet module time to boot up:
delay(1000);
buttonState = digitalRead(4);
// Проверка, нажата ли кнопка PD4 сразу после включения питания
if (buttonState == 0){ // Если нажата, необходимо ввести имя промежуточного сервера
for(ia=0;ia<100;ia++){ addr=ia; EEPROM.write(addr, 0); } // Обнуляем EEPROM
Serial.print("Input server name: ");
while(Serial.available() <= 0){ ;} // Ожидание ввода
delay(1000); // Задержка для передачи в буфер
ia=0;
while(Serial.available() > 0){
server[ia] = Serial.read(); ja=ia; ia++;} //Чтение данных с клавиатуры
for(ia=0;ia<=ja;ia++){
addr=ia;
EEPROM.write(ia, server[ia]); //Запись в EEPROM
}
Serial.println();
}
for(int ia=0;ia<100;ia++){
addr=ia; server[ia]=EEPROM.read(addr); // Чтение имени сервера с EEPROM
}
Serial.print("Server name: "); Serial.println(server);
// Определение IP параметров с помощью DHCP
if (Ethernet.begin(mac) == 0) {
Serial.println("Failed to configure Ethernet using DHCP");
// initialize the ethernet device not using DHCP:
Ethernet.begin(mac, ip, myDns, gateway, subnet);
}
// start the Ethernet connection using a fixed IP address and DNS server:

// print the Ethernet board/shield's IP address:
Serial.print("My IP address: ");
Serial.println(Ethernet.localIP());
Serial.print("My DNS address: ");
Serial.println(Ethernet.dnsServerIP());
Serial.print("Gateway address: ");
Serial.println(Ethernet.gatewayIP());
Serial.print("SubnetMask: ");
Serial.println(Ethernet.subnetMask());
}
void loop() {
// if there's incoming data from the net connection.
// send it out the serial port. This is for debugging
// purposes only:
if (client.available()) {
char c = client.read();
Serial.print(c);
buf[ii-1]=c; ij=ii;
if(c=='\n') ii=0;
ii++;
}
// if there's no net connection, but there was one last time
// through the loop, then stop the client:
if (!client.connected() && lastConnected) {
Serial.println();
Serial.print("disconnecting. FreeRAM:");
Serial.println(freeRam());
if(ij<=1) goto lab;
for(ii=0;ii<ij;ii++)
Serial.write(buf[ii]);
Serial.println();
if( buf[1]=='1') {digitalWrite(3,HIGH);}
if( buf[1]=='0') {digitalWrite(3,LOW);}
if( buf[2]=='1') {digitalWrite(5,HIGH);}
if( buf[2]=='0') {digitalWrite(5,LOW);}
if( buf[3]=='1') {digitalWrite(7,HIGH);}
if( buf[3]=='0') {digitalWrite(7,LOW);}
lab:
client.stop();
}
// if you're not connected, and 14 seconds have passed since
// your last connection, then connect again and send data:
if(!client.connected() && (millis() - lastConnectionTime > postingInterval)) {
httpRequest();
}
if( !client.connected() && buf[0]=='1' && (millis() - lastConnectionTime > postingInterval/2)) {
httpRequest1();
}
// store the state of the connection for next time through
// the loop:
lastConnected = client.connected();
}

// this method makes a HTTP connection to the server:
void httpRequest() {
// if there's a successful connection:
if (client.connect(server, 80)) {
Serial.println("connecting_000...");
// send the HTTP PUT request:
client.println("GET /comand.html HTTP/1.1");
client.println("Host: aaa.aaa.aaa");
client.println("User-Agent: arduino-ethernet");
client.println("Connection: close");
client.println();
// note the time that the connection was made:
lastConnectionTime = millis();
}
else {
// if you couldn't make a connection:
Serial.println("connection failed");
Serial.println("disconnecting.");
client.stop();
}
}
void httpRequest1() {
// if there's a successful connection:
if (client.connect(server, 80)) {
Serial.println("connecting_aaa...");
sensors.requestTemperatures();
int temp=sensors.getTempCByIndex(0);
if (digitalRead(3)) {r=1;}
else {r=0;}
if (digitalRead(5)) {g=1;}
else {g=0;}
if (digitalRead(7)) {b=1;}
else {b=0;}
// send the HTTP PUT request:
if(r==0&&g==0&&b==0) {sprintf(bb,"GET /c.php?r=0&g=0&b=0&t=%2d HTTP/1.1\r\n",temp);
for(int i1=0;i1<38;i1++)
client.write(bb[i1]);}
if(r==1&&g==0&&b==0) {sprintf(bb,"GET /c.php?r=1&g=0&b=0&t=%2d HTTP/1.1\r\n",temp);
for(int i1=0;i1<38;i1++)
client.write(bb[i1]);}
if(r==1&&g==1&&b==0) {sprintf(bb,"GET /c.php?r=1&g=1&b=0&t=%2d HTTP/1.1\r\n",temp);
for(int i1=0;i1<38;i1++)
client.write(bb[i1]);}
if(r==1&&g==1&&b==1) {sprintf(bb,"GET /c.php?r=1&g=1&b=1&t=%2d HTTP/1.1\r\n",temp);
for(int i1=0;i1<38;i1++)
client.write(bb[i1]);}
if(r==0&&g==1&&b==0) {sprintf(bb,"GET /c.php?r=0&g=1&b=0&t=%2d HTTP/1.1\r\n",temp);
for(int i1=0;i1<38;i1++)
client.write(bb[i1]);}
if(r==0&&g==1&&b==1) {sprintf(bb,"GET /c.php?r=0&g=1&b=1&t=%2d HTTP/1.1\r\n",temp);
for(int i1=0;i1<38;i1++)
client.write(bb[i1]);}
if(r==0&&g==0&&b==1) {sprintf(bb,"GET /c.php?r=0&g=0&b=1&t=%2d HTTP/1.1\r\n",temp);
for(int i1=0;i1<38;i1++)
client.write(bb[i1]);}
if(r==1&&g==0&&b==1) {sprintf(bb,"GET /c.php?r=1&g=0&b=1&t=%2d HTTP/1.1\r\n",temp);
for(int i1=0;i1<38;i1++)
client.write(bb[i1]);}
client.println("Host: aaa.aaa.aaa");
client.println("User-Agent: arduino-ethernet");
client.println("Connection: close");
client.println();
}
}

На рисунке 3 показано устройство на микроконтроллере ATmega32, собранное на стенде. Здесь DS18B20 - датчик температуры, LED - три светодиода, вместо которых подключается релейный блок для удаленного управления оборудованием.

Рис.3. Устройство на микроконтроллере ATmega32

На рисунке 4 показаны два двухрядных штыревых разъема модуля WIZ812MJ с подключением их к портам отладочного комплекса AVR-EASY-KIT, на котором установлен микроконтроллер ATmega32.

Рис.4. Подключение модуля WIZ812MJ к портам микроконтроллера 

3. Файл c.php запускается микроконтроллером через каждые 14 секунд при условии, что Андроид устройство послало запрос о состоянии оборудования. При этом создается файл out.html. Его содержимое распечатывается на Андроид устройстве. Строка запуска:
http://ksm.890m.com/c.php?r=1&g=0&b=1&t=25
Параметры:
r=1    -  Красный включен
g=0    - Зеленый выключен
b=1    - Синий включен
t=25   - Температура 25 градусов по Цельсию
Эти параметры формируют данные в файле out.html
Один из вариантов файла out.html:
RedOn GreenOff BlueOn Temp=25 

Ниже представлен скрипт c.php в виде 2-х столбцов:

<?php
$r=$_GET[r];
$g=$_GET[g];
$b=$_GET[b];
$t=$_GET[t];
#echo $r;
if($r=="1") {
$rr="RedOn";
}
if($r=="0")
{
$rr="RedOff";
}
#echo $g;
if($g=="1") {
$gg=" GreenOn";
}
#echo $b;
if($b=="1") {
$bb=" BlueOn";
}

if($b=="0")
{
$bb=" BlueOff";
}
$t1=" Temp=";
$t2=$t1.$t;
$tt=$rr.$gg.$bb.$t2;

$file = fopen ("out.html","w");
if ( !$file )
{
echo("Error reading file");
}
else
{
fputs ( $file, $tt);
}
fclose ($file);
?>

4. Программа для web - клиента Андроид устройства.
Написана на языке визуального программирования App Inventor 2. Программа также предусматривает установку имени (ip адреса) промежуточного сервера. Он запоминается в энергонезависимой памяти. Перед созданием программы целесообразно посмотреть видео урок, например, по ссылке https://www.youtube.com/watch?v=RpbPcLt4uvA

4.1. Дизайн с компонентами:

4.2. Блочная программа (1)

4.3. Блочная программа (2)

4.4. Пояснения к программе

 Приведенные ниже блоки выполняют действия, которые описаны возле каждого блока:

 1. При нажатии на кнопку Button12 (Сохранить) вызывается метод StoreValue компонента TinyDB1. Он сохраняет текст, который находится в окне TextBox1 в tag(переменную) с именем ksm_led_mem. Текст в это окно предварительно заносится клавиатурой телефона (планшета). Далее вызывается компонент TextBox1 со свойством Text. Содержимое окна TextBox1 обнуляется.

2. При нажатии на кнопку Button13 (Вызвать) вызывается метод GetValue компонента TinyDB1. Он с переменной ksm_led_mem считывает значение и распечатывает его в окне TextBox1. Если в переменной ksm_led_mem ничего нет, то возвращается значение valueIfTagNotThere. Там установлено пустое поле.

3. При нажатии на кнопку Button14 (Ввести) вызывается метод StoreValue компонента TinyDB1. Он сохраняет текст, который находится в окне TextBox1 в tag(переменную) с именем ksm_led_mem1. Текст в это окно предварительно заносится после нажатия на кнопку "Вызвать". Далее вызывается компонент TextBox1 со свойством Text. Содержимое окна TextBox1 обнуляется.

4. При нажатии на кнопку Button2 (Синий) вызывается метод GetValue компонента TinyDB1. Он с переменной ksm_led_mem1 считывает значение. С помощью блока join выполняется объединение строк "http://", "строки в переменной ksm_led_mem1" и "/b.php?a=0001". Если было введено имя сервера ksm.890m.com, формируется строка http://ksm.890m.com/b.php?a=0001. Используя свойство Url, компонента Web1 формирует HTTP GET запрос и извлекает ответ.

5. Получение ответа от сервера порождает событие GotText компонента Web1. По этому событию в строку Label1 помещается html текст, полученный с web – сервера ksm.890m.com.
Остальные блоки аналогичны рассмотренным.

Выводы.
1. Построенная система выполняет решение поставленных выше задач.
2. Эксплуатация системы показало ее достаточную надежность для решения бытовых задач.
3. Недостатком является ограничение на бесплатном web hosting – е числа обращений в течении суток. Например, для http://www.hostinger.com.ua/ ежесуточное число обращений не превышает 10000. Поэтому микроконтроллер может выполнять не чаще одного обращения за 8 секунд для предотвращения блокирования hosting – а.