Отправка данных с Arduino на сервер (ENC28J60) GET запросом

Как мне сообщили на форуме, чего я ранее не знал, GET запросы не желательно использовать для данной цели. Но мне кажется, что это стоит оставить как один из вариантов решения задачи. Позднее я постараюсь описать передачу данных с помощью POST запроса. 
Введение:

Всем рано или поздно приходит мысль о создании своего устройства по концепции «интернет вещей», но при разработке остро встает вопрос об организации этих устройств и сбор информации с них. В данном случае идеально подходит соединение всех устройств в локальной сети (LAN) через ethernet, и управление ими с помощью сервера, в роли которого может выступать любой компьютер, и даже телефон!

Что мы имеем на руках:

  • Arduino uno
  • Ethernet module (enc28j60)

Небольшое отступление по поводу сетевого контроллера. Если у вас есть свободные средства, то советую вам взять W5100 (стоит 6-7$ на AliExpress) — официальный модуль от Arduino, имеет мощную документацию, есть шикарная статья на этом сайте. Но поскольку я обычный школьник без свободных средств, то буду использовать дешевый ENC28J60 (стоит 2-3$ на AliExpress). О котором, к сожалению, не так много информации (в основном запуски веб-сервера, да прочие стандартные скетчи)

P.S. Если вам интересно запустить этот код, а не написать его. Все ссылки будут в конце статьи.

P.S.S. Статья была примерно на треть больше так, как я описывал так же отправку данных на ардуину, но по неизвестной мне причине ардуина не захотела работать. Я все ещё продолжаю гуглеж по этой теме, если найду причину моих неудач, я дополню статью. 

Теория:

Наше устройство будет передавать данные по локальной сети (при желании можно и по интернету), с помощью http get запроса.

Это работает примерно так:

  • На нашем компьютере запущен сервер, и есть некоторые обработчики(в нашем случае на питоне), которые отвечают за обработку запроса и выполнение заданных функций.
  • Устройство совершает запрос к серверу, который передает данные обработчику
  • обработчик получает эти данные, и дальше делает то, что нам необходимо

Практика:

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

Сервер:

Приступим, для начала вам будет необходимо скачать python с официального сайта (Качаем на ниже 3.4 версии!)

Для написания кода можете использовать любой текстовый редактор, в поставке с питоном уже идет IDLE (простая и легкая среда разработки), для наших экспериментов этого вполне хватит.

Сервер я буду писать на питоне, по многим причинам.

  • Python — очень простой в изучении язык (похож на псевдокод)
  • В поставке с питоном уже идут библиотеки которые помогут вам запустить сервер без лишних шаманств
  • Я знаком с этим языком уже довольно продолжительное время и знаю что от него ожидать
  • Плюсы можно перечислять долго, но если вы любите писать на PHP или же Ruby, можете использовать их, это не принципиально.
    Так же если вы не знакомы с языком Python, но хотите понять что здесь происходит, советую почитать несколько первых глав из этого учебника.

    Начнем:

    Подготовка:

    • Создадим папку для нашего проекта, я назову ее server/
    • В папке проекта создадим:
      • Подпапку cgi-bin/ — в этой директории будут лежать скрипты, обрабатывающие запросы
        • Внутри создадим скрипт handler.py — это обработчик запросов посылаемых ардуиной.
      • файл run.py — этот скрипт будет запускать наш веб-сервер, который будет принимать запросы и передавать их скриптам.

    В итоге все должно выглядеть вот так:

    Напишем скрипт который будет запускать наш сервер (run.py)

    Перейдем к файлу run.py:

    from http.server import HTTPServer, CGIHTTPRequestHandler
    import socket

    if len(socket.gethostbyname_ex(socket.gethostname())[2]) > 1:
    print(«Сервер запущен на адресе: %s»%socket.gethostbyname_ex(socket.gethostname())[2][1])
    server_address = («», 80)
    httpd = HTTPServer(server_address, CGIHTTPRequestHandler)
    httpd.serve_forever()

    Разберем код построчно:

  • Импортируем библиотеку для запуска HTTP сервера
  • Импортируем библиотеку для работы с WEB-сокетами (для того, что бы узнать ip адресс компьютера)
  • <Пусто>
  • Данная функция возвращает список(массив в python) в котором содержится: имя компьютера в сети, ip адрес компьютера. Если компьютер на подключен к сети, то в списке будет лишь имя.
  • Выводим сообщение с текущим ip адресом
  • Задаем адрес на котором будет работать наш сервер. Кавычки оставляем пустыми, потому что это локальный сервер. В качестве порта обязательно указываем 80 (это стандартный http порт). Подробнее о портах читать здесь.
  • Создаем экземпляр класса сервера, и передаем ему все необходимые настройки
  • Запускаем сервер
  • Как видим, все очень просто, потому что всю работу за нас выполняет библиотека, а мы лишь указываем необходимые настройки для сервера. Но это лишь половина пути , теперь необходимо создать обработчики запросов. На этом этапе большая часть ответственности лежит на нас. Я напишу простой обработчик на примере которого вы сможете написать свои.

    Наш обработчик будет принимать сообщение и записывать его в файл. Просто?! Да, просто, но это пожалуй, самый востребованный кейс.  

    Перейдем к коду handler.py:

    #!/usr/bin/env python3
    import cgi

    values = cgi.FieldStorage()
    cxem = values.getfirst(«cxem», «none»)
    output_file = open(«out.txt»,»a»)
    output_file.write(cxem)
    output_file.close()

    Разберем код построчно:

  • Команда для интерпретатора, не будем вдаваться в подробности (если интересно просто загуглите)
  • Импортируем библиотеку для получения данных из get запроса
  • <пусто>
  • Получаем из Get запроса переданные значения в виде словаря (ассоциативного массива)
  • Определяем переменной значение поля с названием «cxem», в случае если значение не будет задано переменная примет значение «none»
  • Открываем файл на запись
  • Записываем в файл значение
  • Закрываем файл
  • Поскольку мы ещё не подготовили клиента (ардуино), то запрос произведем из браузера. Получаем вот такой результат.

    Мы можем наблюдать что данные записались файл. Если вы обладаете дедукцией и некоторой логикой вы могли догадаться что данные указываются в таком виде путь_до_обработчика?название_переменной=значение

    Q: А что  если необходимо передать несколько переменных? 
    A: Все просто, в коде мы делаем так же как и с первой переменной, а при формировании запроса разделяем переменные знаком амперсант («&»), то есть это будет выглядеть примерно так: путь_до_обработчика?название_переменной=значение&название_переменной=значение

    На этом наша работа с сервером закончена, перейдем к работе с Ардуино.

    Ардуино:

    Для работы с ethernet модулем enc28j60 мы будем использовать самую адекватную (и до сих пор поддерживающуюся библиотеку ethercard)

    Ссылка не библиотеку: клац

    Теория:

    Запрос будет формироваться с помощью метода BrowseUrl. Данный метод совершает запрос на определенный url и с помощью указанного колбэка обрабатывает полученные от сервера данные (у меня не к сожалению это не получилось)

    Практика:

    Наш скетч выглядит следующим образом: ссылка

    Поскольку здесь много строк, объяснять буду не построчно, а поблочно.

    Это переменные и константы которые мы задаем вначале программы.

    // Библиотека для работы с сетевой картой
    #include <EtherCard.h>

    // Буффер сетевой карты
    byte Ethernet::buffer[200];

    // Адрес сайта на который будем стучаться (в моем случае он запускается на одном с сервером IP)
    const char website[] PROGMEM = «192.168.0.100»; //Замените на ip адрес вашего компьютера

    // IP адрес сервера.
    static byte websiteip[] = { 192,168,0,100 }; //Замените на ip адрес вашего компьютера
    static uint32_t timer;

    // Действия после успешной отправки данных

    //Мак-адрес устройства
    static byte mac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };

    • Ethernet::buffer — буффер сетевой карты, в который она будет записывать полученные данные, поскольку в нашем случае данные получаемые данные не так важны, я выставил не большой размер буффера (Извиняюсь, в видео ниже такая же оговорка. В ходе экспериментов выяснилось что при буфере меньше 700 байт, сетевой модуль ведет себя не правильно)
    • website — доменое имя сервера, в нашем случае это ip адрес компьютера (его нам покажет сервер при запуске)
    • websiteip — ip адрес сервера (записывается массивом десятичных чисел)
    • timer — переменная счетчика таймера (для переодического пинга сервера)
    • mac — мак адрес нашего устройства (должен быть уникальным в сети)

     Думаю что  в принципе здесь все понятно, и можно переходить к другому блоку.

    void setup () {
    Serial.begin(9800);

    if (ether.begin(sizeof Ethernet::buffer, mac) == 0)
    Serial.println(F(«Failed to access Ethernet controller»));
    else
    Serial.println(F(«Successfully to access Ethernet controller»));
    if (!ether.dhcpSetup())
    Serial.println(F(«DHCP failed»));
    else
    Serial.println(F(«Successfully DHCP»));

    memcpy(ether.hisip, websiteip, sizeof(websiteip));
    ether.printIp(F(«SRV: «), ether.hisip);
    }

    Это функция setup, вызывается один раз при старте работы устройства.

    Здесь мы:

    • Устанавливаем скорость работы с serial монитором
    • Проверяем подключен ли ethernet контроллер
    • Проверяем подключение к DHCP
    • Записываем в память имя и ip сервера (необходимо для библиотеки)
    • Выводим на экран ip сервера к которому будем  подключаться

    Далее идет функция loop, которая вызывается постоянно во время работы устройства. Но в ней я лишь прописал вызов функции send_package Отправку данных я специально выделил в отдельную функцию, чтобы вы могли вызвать ее из любого места программы.

    // Функция отправки данных на сервер
    void send_package(){
    ether.packetLoop(ether.packetReceive());
    if (millis() > timer) {
    Serial.println(F(«<<PING»));
    timer = millis() + 5000;
    ether.browseUrl(PSTR(«/cgi-bin/handler.py?»),»cxem=i_love_arduno», website, callback);
    }
    }

    • Первая строка принимает пакеты от сервера не останавливая работы программы
    • Далее мы совершаем отправляем данные на сервер каждые 5 секунд (без данного счетчика данные могут не отправится)
    • Ну и пожалуй то, что необходимо рассмотреть  больше всего это формирование запроса c помощью BrowseUrl. Первым аргументом мы передаем константное значение, пути до обработчика запроса (считая от корневой директории сервера). Вторым аргументом мы передаем переменные и их значения. Прошу заметить что здесь значения заданы константами, но вы можете передавать значения из переменных, главное что бы они были представлены с помощью типа данных char array.Третьим аргументом мы передаем доменное имя сервера (которое мы задали заранее). Четвертым аргументом мы передаем функцию колбэк, которую мы рассмотрим ниже.

    static void callback (byte status, word off, word len) {
    Serial.println(«>>>»);
    Ethernet::buffer[off+300] = 0;
    Serial.print((const char*) Ethernet::buffer + off);
    Serial.println(«…»);
    }

    Это функция коллбэк, которую вызывает библиотека для обработки полученных данных.

    Первым аргументом в нее передается некий байт статус, который не использовался ни в одном примере, так что это загадка для нас всех. Второй параметр — это указатель в памяти, который показывает с какого места начинаются переданные данные. И третий параметр — длина переданных данных. К сожалению передать данные с питоновского сервера не получилось. В данный момент я занимаюсь решением данной проблемы. Сама функция лишь отпечатывает в консоль полученные данные.

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

    Если вам было лень читать описанное выше, или вы хотите посмотреть на  пример  того как это работает, то прошу к экрану!

    Ссылки:
    Cсылка на исходный код: тык
    Ссылка на библиотеку: клац
    Ссылка на питон: клик

    P.S. В дальнейшем я постараюсь найти решение с получением тела http ответа, но если это не удастся, то я напишу о работе enc28j60 в обход http протоколов, на прямую через TCP. Надеюсь вам было интересно и полезно.

    Добавить комментарий

    Ваш адрес email не будет опубликован.