Ethernet TCP з'єднання з Arduino
З програмної точки зору встановлення з'єднання Ethernet з Arduino Це дуже просто. Для цього використовуйте Бібліотека Ethernet. Ця бібліотека призначена для a Ethernet Shield яка базується на інтегр W5100, але існують інші різні плати чи модулі та/або які використовують інші інтегровані, наприклад ENC28J60. Щоб спростити його використання та підвищити сумісність, інші бібліотеки використовують (майже) те саме API що Бібліотека Ethernet, вам доведеться лише замінити альтернативну бібліотеку оригінальною або включити її (якщо ім’я інше) на її місці, навіть якщо в коді використовуються ті самі (або дуже схожі) функції. У моєму випадку я використовую Бібліотека UIPEthernet de Норберт Трухсесс слідуючи тому самому процесу, який я збираюся описати в цьому тексті.
1. Визначте підключення Ethernet
Чи збираєтеся ви прийняти роль клієнт як і той сервер, перш за все, ви повинні визначити зв'язок із функцією почати () який можна передати як параметр лише MAC-адреса і дочекатися сервера DHCP у мережі призначити a IP-адреса та решту конфігурації або також можна вказати (необов’язково) більше параметрів, поки не буде визначена повна конфігурація:
- Директорія MAC (про що вже згадувалося)
- IP-адреса щита або модуля
- IP-адреса сервера DNS (лише один сервер)
- IP-адреса Шлюз
- Маска сітки
Бажано вказати всі параметри, якщо їх виведення не є звичайним, щоб уникнути неправильної конфігурації (наприклад, що шлюз не є першою адресою мережі).
Зі сказаного вище зрозуміло, що дані, що представляють IP-адреси, повинні використовуватися досить часто, тому бібліотека включає клас IP-адреса з якого створювати об’єкти IP-адреси. Параметри, які його визначають, — це чотири байти адреси IPV4
La MAC-адреса Він визначений для цієї бібліотеки як 6-байтовий масив. MAC-адреса — це (має бути) унікальний ідентифікатор, у якому перші байти вказують на виробника та модель, а останні — на конкретний пристрій. Інтегрований ENC28J60 не включає MAC-адресу, якщо ви не вирішите також придбати a Інтегрована MAC-адреса від Microchip (або цілий блок ТАК адрес до IEEE якщо тираж пристроїв достатньо великий, щоб це було варто). Якщо у вас немає MAC-адреси, ви можете придумати її, подбавши про те, щоб вона не конфліктувала з іншими в мережі, де знаходиться пристрій.
Якщо налаштування виконується за допомогою DHCP-сервера, а не «вручну», функція локальний IP() Корисно переглянути адресу, яку сервер призначив модулю. Відновити присвоєну адресу (якщо відповідний час минув) Бібліотека Ethernet забезпечує функцію підтримувати() який також повідомить, повернувши код, який відповідає статусу поновлення:
- Операція не дала ефекту
-
Помилка оновлення IP-адреси
Неможливо продовжити використання призначеної IP-адреси на тому самому сервері - IP-адресу успішно оновлено
-
Помилка повторного прив’язування IP-адреси
Використання призначеної IP-адреси не можна розширити на жодному сервері - IP-адресу успішно перепризначено
З наявною інформацією ви можете написати приклад того, як буде ініційовано з’єднання Ethernet шляхом налаштування IP-адреси через сервер DHCP у мережі. Наведений нижче приклад коду намагається оновити IP-адресу кожен певний період часу та повідомляє про результат.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
//#include <UIPEthernet.h> // Librería Ethernet que usaré después con el módulo ENC28J60
#include <Ethernet.h> // Librería Ethernet estándar
#define ESPERA_RENOVACION_IP 60000 // Un minuto
unsigned long reloj;
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
byte estado_DHCP;
void setup()
{
Serial.begin(9600);
Ethernet.begin(direccion_mac);
mostrar_direccion_ip();
reloj=millis()+ESPERA_RENOVACION_IP;
}
void loop()
{
if(millis()>reloj) // Tratar de renovar la IP cada ESPERA_RENOVACION_IP milisegundos
{
estado_DHCP=Ethernet.maintain();
switch(estado_DHCP)
{
case 0:
Serial.println(“Sin cambios”);
break;
case 1:
Serial.println(“Error al renovar la dirección IP”);
break;
case 2:
Serial.println(“Dirección IP renovada correctamente”);
break;
case 3:
Serial.println(“Error al reasignar la dirección IP”);
break;
case 4:
Serial.println(“Dirección IP reasignada correctamente”);
break;
default:
Serial.println(“Error desconocido”);
}
mostrar_direccion_ip();
reloj=millis()+ESPERA_RENOVACION_IP;
}
}
void mostrar_direccion_ip()
{
Serial.print(“Dirección IP actual [“);
Serial.print(Ethernet.localIP()); // Mostrará la dirección IP asignada por el servidor DHCP
Serial.println(“]”);
}
|
Наведений нижче приклад призначає IP-адресу та решту конфігурації вручну за допомогою об’єктів IP-адреса щоб зробити його більш зручним для читання та (у випадку складнішого коду) щоб уникнути помилок, які могли б виникнути, якщо адресу було (не)записано кожного разу, коли вона використовується.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
// #include <Ethernet.h> // Librería Ethernet estándar
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor DNS OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
void setup()
{
Serial.begin(9600);
while(!Serial){;} // He usado una placa Leonardo, me toca esperar a que el puerto serie esté operativo
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
Serial.print(“Dirección IP asignada [“);
Serial.print(Ethernet.localIP()); // Poco misterio, devolverá la dirección IP asignada manualmente
Serial.println(“]”);
}
void loop()
{
// Sólo es un ejemplo de configuración, no hace nada
}
|
2. Запустіть підключення в режимі клієнта або сервера
Під час ініціювання з’єднання в режимі сервера запити від інших систем прослуховує мікрокерована система, яка розробляється. Щоб почати підключення як сервер, скористайтеся EthernetServer() і порт, на якому сервер буде слухати, вказується як параметр. EthernetServer() є конструктором класу сервер, який підтримує всі операції Ethernet як сервер. Хоча найбільш ортодоксальним є звернення до конструктора EthernetServer(), нерідко можна знайти деякі приклади, які безпосередньо використовують клас сервер або альтернативні бібліотеки для з’єднання Ethernet, які вирішили використовувати цю систему інстанціювання.
З’єднання як клієнт – це те, що надсилає запити серверній системі, яка чекає на них і відповідним чином на них відповідає. Щоб ініціювати підключення як клієнт, використовуйте EthernetClient() що таке конструктор класу Клієнт походження всіх операцій Ethernet як клієнта.
На відміну від того, що відбувається з серверним режимом, який, як передбачається, працює з моменту створення екземпляра класу (хоча він відповідатиме клієнтам, лише якщо це дійсно так), ви повинні перевірити, що підключення клієнта готове до його використання. Клієнтський об’єкт, який створюється під час ініціювання з’єднання, можна запитати, чи він доступний. Наприклад, операції запиту можуть бути включені в структуру якщо (EthernetClient) виконувати їх лише тоді, коли клієнтське підключення доступне.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
// #include <Ethernet.h> // Librería Ethernet estándar
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
EthernetServer servidor=EthernetServer(80); // Puerto 80 (típico de un servidor HTTP)
void setup()
{
Serial.begin(9600);
while(!Serial){;} // He usado una placa Leonardo, hay que esperar a que el puerto serie esté operativo
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
servidor.begin();
Serial.println(“Servidor HTTP iniciado”);
}
void loop()
{
// Sólo es un ejemplo de configuración, no hace nada productivo
}
|
3. Встановіть підключення як клієнт
Як було сказано, щойно з’єднання створено, саме клієнт бере на себе ініціативу надсилати запити. Сервер чекатиме цієї ініціативи та відповідним чином відповість. Отже, для цього ми використовуємо клієнт, який підключається до сервера connect () із зазначенням сервера як параметрів (IP-адреса або URL) та порт в тому, хто слухає.
На основі результату операції функція поверне значення
- ( ) Підключення успішно встановлено
- Встановлення з'єднання
- ( ) Минув час очікування без встановлення з’єднання
- ( ) Сервер не знайдено або не відповідає належним чином
- ( ) З’єднання було розірвано до повного встановлення
- ( ) Відповідь сервера неправильна
Перш ніж почати робити запити, необхідно перевірити, чи працює з’єднання з функцією підключений() що повернеться якщо він уже доступний або інакше.
Наведений нижче приклад ілюструє з’єднання як клієнта, перевіряючи кожні 10 секунд, чи є з’єднання з сервером (це не має на меті бути чимось продуктивним, просто щоб показати синтаксис функцій) те, що, до речі, виробництво веб-сервер не дуже хотів би.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
// #include <Ethernet.h> // Librería Ethernet estándar
#define INTERVALO_CONSULTA 10000 // Se comprueba cada 10 segundos si hay conexión
#define LED_CONEXION 13 // Pin del LED que parpadea cuando hay conexión
#define TIEMPO_PARPADEO 300 // Milisegundos entre encendido/apagado del LED que indica conexión
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
IPAddress ip_servidor_web(192,168,1,21); // Dirección IP del servidor web (en la intranet)
//char url_servidor_web[]=”sleepmanager.onironauta.es”; // URL poético para un gestor de sueño (en Internet)
EthernetClient cliente=EthernetClient();
byte estado_conexion;
boolean anteriormente_conectado;
boolean estado_led_conexion;
unsigned long cronometro_parpadeo;
unsigned long cronometro_consulta;
void conectar_ethernet()
{
estado_conexion=cliente.connect(ip_servidor_web,80); // Conexión desde la intranet
//estado_conexion=cliente.connect(url_servidor_web,80); // Conexión desde Internet
delay(100); // Un pequeño retraso para permitir que se active la conexión
anteriormente_conectado=false;
switch(estado_conexion)
{
case 1:
Serial.println(“Conexión con el servidor SleepManager establecida correctamente”);
anteriormente_conectado=true;
break;
case –1:
Serial.println(“Ha pasado el tiempo de espera sin que se establezca la conexión”);
break;
case –2:
Serial.println(“No se ha encontrado el servidor o no responde correctamente”);
break;
case –3:
Serial.println(“La conexión se ha interrumpido antes de establecerse completamente”);
break;
case –4:
Serial.println(“La respuesta del servidor es incorrecta”);
break;
}
}
void setup()
{
pinMode(LED_CONEXION,OUTPUT);
Serial.begin(9600);
while(!Serial){;} // Esperar al puerto serie de la placa Leonardo
Serial.println(“Conectando con el servidor SleepManager…”);
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
conectar_ethernet();
estado_led_conexion=false;
cronometro_parpadeo=0;
cronometro_consulta=millis()+INTERVALO_CONSULTA;
}
void loop()
{
if(anteriormente_conectado&&millis()>cronometro_parpadeo)
{
estado_led_conexion=!estado_led_conexion;
digitalWrite(LED_CONEXION,estado_led_conexion);
cronometro_parpadeo=millis()+TIEMPO_PARPADEO;
}
if(millis()>cronometro_consulta)
{
if(!cliente.connected())
{
conectar_ethernet();
}
cronometro_consulta=millis()+INTERVALO_CONSULTA;
}
}
|
4. Надіслати дані
Як і інші більш відомі класи, такі як Послідовний, а за порівнянного використання — класи Клієнт y сервер мають функції
-
Надсилає інформацію за допомогою об’єкта клієнта або сервера, з якого вона викликається. Параметр "data" є єдиним байт o бак тоді як "буфер" - це масив байт o бак з яких надсилається кількість, що дорівнює «довжині».Ця функція використовується для бінарних операцій, порівняно з наступними двома, які зазвичай зарезервовані для надсилання тексту.
-
Надсилає клієнту або серверу (залежно від класу, з якого він використовується) інформацію, що відповідає "даним", як текст. Якщо інформація не виражена у вигляді тексту (наприклад, це ціле число), необов’язковий параметр «база» може бути використаний для вибору перетворення, яке може бути однією з констант BIN, OCT, DEC або HEX, які вказують відповідно. основи, що відповідають двійковій (основа 2), вісімковій (основа 8), десятковій (основа 10) і шістнадцятковій системі (основа 16)
-
Операція ідентична попередній, за винятком надсилання після інформації, чітко зазначеної параметром "data", повернення каретки (код 13, який може бути представлений як \r) і кінця рядка (код 10, який може бути представлені \n) Ці коди часто називають відповідно акронімом CR (повернення каретки) і LF (переведення рядка)
Три попередні функції повертають кількість надісланих байтів, як і еквівалентні функції класу Послідовний; Як було сказано вище, операція порівнянна.
5. Отримувати дані
Як і у випадку операцій надсилання даних, операції отримання можна порівняти з операціями широкого використання Послідовний. Протокол отримання також схожий: перевірте, чи є (достатньо) доступних даних (доступний) і в такому випадку прочитайте їх
-
Повертає кількість байтів, доступних для читання. Ця функція присутня в обох класах Клієнт як сервер; У першому випадку він повідомляє кількість байтів, які сервер надіслав у відповідь на запит і які доступні для читання клієнтом (зчитування), а в другому випадку клієнт (об’єкт), який виконав операцію, або false, якщо її немає.
-
Він використовується для зчитування отриманої інформації. Ця функція доступна лише в класі Клієнт. Якщо програма, що розробляється, виконує роль сервера, для читання інформації, що надійшла, клієнтський об’єкт повинен бути створений з відповіддю функції доступний () розглянуто в попередньому розділі.
У наступному прикладі наведено «сервер великих літер», який прослуховує порт 2000 і відповідає на запити тим, що було надіслано великими літерами, коли це можливо. Його можна перевірити, наприклад, з PuTTY або просто з Це, звичайно, не дуже практично, його мета - лише показати, як отримати дані, надіслані йому клієнтом із сервера.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
//#include <Ethernet.h> // Librería Ethernet estándar
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
EthernetServer servidor=EthernetServer(2000);
EthernetClient cliente;
char texto_recibido; // Sólo para que se más fácil leer el programa usando varias líneas de código
void setup()
{
Serial.begin(9600);
while(!Serial){;} // Esperar al puerto serie de la placa Leonardo esté operativo
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
Serial.println(“Iniciando el servidor de mayúsculas”);
servidor.begin();
}
void loop()
{
cliente=servidor.available();
// Si hay disponible alguna petición de un cliente leerla y devolverla en mayúsculas
if(cliente)
{
texto_recibido=cliente.read();
if(texto_recibido>96&&texto_recibido<123) // Si se recibe una letra minúscula…
{
texto_recibido-=32; // …convertirla a mayúsculas
}
// Si se tiene la seguridad de recibir texto se puede usar print en lugar de write
servidor.write(texto_recibido); // Responder con lo recibido pasado a mayúsculas si procede
}
}
|
6. Завершіть з'єднання
Хоча серверна програма зазвичай працює нескінченно довго, клієнтські з’єднання встановлюються, створюють з’єднання та припиняються, дозволяючи ресурси відновлювати та використовувати в інших з’єднаннях або виділяти для інших цілей програми. Функція Стоп() класу Клієнт Він використовується для припинення підключення клієнта та звільнення будь-яких ресурсів, які він використовує.
Для сервера той факт, що клієнт розриває з’єднання, коли інформаційний об’єкт запиту було надіслано або отримано, також дозволяє йому звільнити ресурси для розподілу їх для інших з’єднань або інших цілей. Коротше кажучи, хоча це здається незначним, доцільно розірвати з’єднання після завершення операцій клієнта.
Ще одна хороша практика під час припинення з’єднання клієнта – очистити які використовує клас. Для цього доступна функція змити () слід викликати після завершення з'єднання клієнта з Стоп()
Приклад запиту HTTP GET
Щоб краще прояснити все вищесказане, нижче наведено більш повний приклад запитів. TCP за допомогою запитів GET за допомогою протокол HTTP. У прикладі значення, отримані аналоговими датчиками, підключеними до плати Arduino, надсилаються на веб-сервер, який зберігає їх у базі даних.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
#include <UIPEthernet.h> // Librería Ethernet usada con el módulo ENC28J60
// #include <Ethernet.h> // Librería Ethernet estándar
#define INTERVALO_CONSULTA 60000 // Enviar datos cada minuto
#define INTERVALO_RECONEXION 10000 // Reintentar la conexión 10 segundos más tarde si no ha sido posible hacerlo cuado correspondía
#define CANTIDAD_SENSORES 6 // Número de sensores analógicos empezando en A0
byte direccion_mac[]={0x12,0x34,0x56,0x78,0x9a,0xbc}; // Dirección MAC inventada
IPAddress direccion_ip_fija(192,168,1,69); // Dirección IP elegida para el módulo
IPAddress servidor_dns(87,216,170,85); // Servidor OpenNIC (de Alejandro Bonet, http://opennic.alargador.org)
IPAddress puerta_enlace(192,168,1,14); // Dirección IP del router
IPAddress mascara_red(255,255,255,0); // Máscara de la red
//IPAddress ip_servidor_web(192,168,1,21); // Dirección IP del servidor web (en la intranet)
char url_servidor_web[]=“sleepmanager.onironauta.es”; // URL poético para un gestor de sueño (en Internet)
EthernetClient cliente;
byte estado_conexion;
String texto_consulta;
unsigned long cronometro_consulta;
byte contador;
void setup()
{
Serial.begin(9600);
while(!Serial){;} // Esperar al puerto serie de la placa Leonardo
Serial.println(“Conectando con el servidor SleepManager…”);
Ethernet.begin(direccion_mac,direccion_ip_fija,servidor_dns,puerta_enlace,mascara_red);
cronometro_consulta=millis()+INTERVALO_CONSULTA;
}
void loop()
{
if(millis()>cronometro_consulta)
{
//estado_conexion=cliente.connect(ip_servidor_web,80); // Conexión desde la intranet
estado_conexion=cliente.connect(url_servidor_web,80); // Conexión desde Internet
while(estado_conexion==0) // esperar a que se establezca la conexión o se produzca un error
{
switch(estado_conexion)
{
case 1:
Serial.println(“Conexión con el servidor SleepManager establecida correctamente”);
break;
case –1:
Serial.println(“Ha pasado el tiempo de espera sin que se establezca la conexión”);
break;
case –2:
Serial.println(“No se ha encontrado el servidor o no responde correctamente”);
break;
case –3:
Serial.println(“La conexión se ha interrumpido antes de establecerse completamente”);
break;
case –4:
Serial.println(“La respuesta del servidor es incorrecta”);
break;
}
}
if(cliente.connected()) // Si ha sido posible conectar realizar la consulta
{
cronometro_consulta=millis()+INTERVALO_CONSULTA;
texto_consulta=“GET /pruebas/guardar_sensores_analogicos.php?origen=SleepManager”;
for(contador=0;contador<CANTIDAD_SENSORES;contador++)
{
texto_consulta=“&sensor_”+String(contador+1,DEC)+“=”+String(analogRead(contador),DEC);
delay(1); // Como tarda 100 μs en obtener el valor analógico, con 1 ms seguro le da tiempo
}
texto_consulta+=” HTTP/1.1\r\nHost: “+String(url_servidor_web)+“\r\nUser-Agent: sleep_inspector\r\n\r\n”;
cliente.print(texto_consulta);
cliente.flush();
cliente.stop();
}
else // Si no ha sido posible conectar reintentarlo más tarde pero no tanto como si hubiera sido posible hacerlo
{
cronometro_consulta=millis()+INTERVALO_RECONEXION;
}
}
}
|
Дати коментар