1i7 (1i7) wrote,
1i7
1i7

Category:

Сервер Роботов: управление платой ChipKIT WF32 из облака

Продолжаем эксперименты с управлением близкими устройствами из далёких облаков. В прошлый раз написали на языке Java и запустили на виртуальной машине в облаке Амазон Сервер Роботов. Сегодня напишем на языке С++ и подключим к нему Робота Клиента, сделанного из платы ChipKIT WF32.

Сервер Роботов: управление платой ChipKIT WF32 из облака from 1i7 on Vimeo.



Сервер Роботов запущен в облаке (виртуальная машина Амазон) по адресу robotc.lasto4ka.su:1116 и слушает подключения. Робот Клиент (плата ChipKIT WF32) подключается к Серверу Роботов через интернет и переходит в режим приема команд: включить или выключить лампочку. Пользователь отправляет команды подключенной плате через интерфейс командной строки, запущенный на удаленной системе в оболочке ssh. Плата принимает и распознает команду, включает или выключает лампочку и отправляет ответ.



Предварительные приготовления

Еще раз напомню, что необходимо для Робота Клиента на плате ChipKIT WF32.

0) Нужна плата из серии ChipKIT, которая умеет выходить в интернет (на аналогичных платах Arduino этот код не заработает): ChipKIT Uno32+WiFi Shield, ChipKIT WF32 или ChipKIT Wi-FIRE.

1) Лабораторная работа: Подключение платы ChipKIT WF32 к точке доступа WiFi.

Плата должна уметь выходить в интернет. Я использовал точку доступа на смартфоне Android с мобильным интернетом, но пойдет обычный домашний вайфай-роутер.

2) Запустить Сервер Роботов в виртуальной машине Амазон или любом другом облаке.

Исходники

1) Сервер Роботов и Робот Клиент на Java: chipkit-cloud-wifi/JavaTcpServerMaster
2) Робот Клиент на ChipKIT WF32: chipkit-cloud-wifi/chipkit_tcp_client_slave
3) Робот Клиент на Android: chipkit-cloud-wifi/AndroidTcpClientSlave

Протокол
Протокол общения между Сервером Роботов и Роботом Клиентом (плата, смартфон или что-то еще): сервер отправляет команду, клиент выполняет команду и присылает ответ; команды и ответы строковые.

Весь полезный функционал определим двумя основными командами:
ledon - включить лампочку;
ledoff - выключить лампочку.

Ответы от клиента:
ok - в случае успеха выполнения команды;
dontunderstand - команда не распознана.


Подчинённый клиент на ChipKIT WF32

Т.к. наш Робот Клиент после подключения к Серверу Роботов переходит в пассивный режим ожидания команд от сервера, будем называть такого Робота Клиента подчинённым клиентом (в английских терминах Slave - раб), а Сервер Роботов - управляющим сервером (англ. Master - хозяин).

Код подключения к точке WiFi опущен, за подробностями в отдельную статью.

Весь код примера содержится в одном файле: chipkit_tcp_client_slave.pde

Подключаем необходимые библиотеки:


#include <WiFiShieldOrPmodWiFi_G.h>

#include <DNETcK.h>
#include <DWIFIcK.h>


Определяем константы для протокола:


// Команды, принимаемые от Сервера Роботов
const char* CMD_LEDON = "ledon";
const char* CMD_LEDOFF = "ledoff";

// Ответы для Сервера Роботов
const char* REPLY_OK = "ok";
const char* REPLY_DONTUNDERSTAND = "dontunderstand";


Порт для подключения тестовой лампочки:


// Пин для тестовой лампочки
#define LED_PIN 13



Значения для подключения к Серверу Роботов - адрес и порт:


// Сервер Роботов
const char* robot_server_host = "robotc.lasto4ka.su";
//const char* robot_server_host = "192.168.1.3";
const int robot_server_port = 1116;



Ссылка на объект TCP-клиент, через который мы будем общаться с сервером:


// TCP-клиент - подключение к серверу
TcpClient tcpClient;


Буферы для чтения и записи данных при общении с сервером:


// Буферы для обмена данными с сервером
static char read_buffer[128];
static char write_buffer[128];
int write_size;



В сетапе ничего особенного - включим порт отладки UART, чтобы иметь возможность смотреть отладочные сообщения с платы на компьютере в MPIDE в окне Tools/Serial Monitor, и переведем ножку с тестовой лампочкой в режим вывода:


void setup() {
    Serial.begin(9600);
    Serial.println("Start wifi network client demo");

    pinMode(LED_PIN, OUTPUT);
}



Весь полезный код в главном цикле loop: объявим статусные и вспомогательные переменные, разместим вызов periodicTasks, необходимый для работы Tcp-стека (подробности разъяснял в лабе про подключение к WiFi).

Блок подключения к точке WiFi тоже здесь, но мы его опустим - все главные события будут разворачиваться по ветке else, в которую мы попадем в том случае, если подключение к точке WiFi проведено успешно.


void loop() {
    DNETcK::STATUS networkStatus;
    int readSize;
    int writeSize;
   
    // Держим Tcp-стек в живом состоянии
    DNETcK::periodicTasks();
       
    if(!DWIFIcK::isConnected(conectionId)) {
    ...
    } else ...



Итак, выход в Сеть есть, проверим, подключены ли к Серверу Роботов:


    } else if(!tcpClient.isConnected()) {
        // Подключимся к Серверу Роботов
       
        bool connectedToServer = false;



Не подключены - пробуем подключиться:


        Serial.print("Connecting to Robot Server...");
        tcpClient.connect(robot_server_host, robot_server_port);



Вызов tcpClient.connect() по давно известным нам принчинам не является блокирующим, поэтому дождемся успешного подключения к серверу или же сообщения о невозможности подключиния внутри следующего несложного блока:


        // Сокет для подключения назначен, подождем, чем завершится процесс подключения
        bool connecting = true;
        while(connecting) {
            Serial.print(".");
            if(tcpClient.isConnected(&networkStatus)) {
                // Подключились к сереверу
                connectedToServer = true;
                                   
                connecting = false;
            } else if(DNETcK::isStatusAnError(networkStatus)) {
                // Не смогли подключиться к серверу из-за ошибки,
                // в этом месте больше не пробуем
                connecting = false;
            }
        }
        Serial.println();



В случае успешного подключения распечатаем его статус в отладочный порт и пойдем на следующий заход loop уже в другую ветку. В случае ошибки подключения подождем 4 секунды и пойдем тоже на следующий заход loop повторять попытку.


        if(connectedToServer) {
            // Подключились к Серверу Роботов
            Serial.println("Connected to Robot Server");
           
            printTcpClientStatus();
        } else {
            // Так и не получилось подключиться
            Serial.print("Failed to connect Robot Server, status: ");
            //Serial.print(networkStatus, DEC);
            printDNETcKStatus(networkStatus);
            Serial.println();
           
            // Вернем TCP-клиента в исходное состояние
            tcpClient.close();
           
            // Немного подождем и попробуем переподключиться на следующей итерации
            Serial.println("Retry after 4 seconds...");
            delay(4000);
        }
    }



Мы подключены к Серверу Роботов - ожидаем команду:


    } else {
        // Подключены к серверу - читаем команды, отправляем ответы
       
        // есть что почитать?
        if((readSize = tcpClient.available()) > 0) {
            readSize = readSize < sizeof(read_buffer) ? readSize : sizeof(read_buffer);
            readSize = tcpClient.readStream((byte*)read_buffer, readSize);


Команда пришла - она уже записана в буфер read_buffer, только добавим завершающий ноль, чтобы далее с ней можно было работать как с обычной строкой:


            // Считали порцию данных - добавим завершающий ноль
            read_buffer[readSize] = 0;
           
            Serial.print("Read: ");
            Serial.println(read_buffer);



Распознаем и выполним команду в методе handleInput(), ответ будет записан в буфер write_buffer. Установка ненулевого значения в переменную write_size приведет к тому, что содержимое буфера write_buffer (первые write_size байт) будет записано в сокет далее по циклу.


            // и можно выполнить команду, ответ попадет в write_buffer
            writeSize = handleInput(read_buffer, readSize, write_buffer);
            write_size = writeSize;
        }



Отправим первые write_size байт из буфера write_buffer на Сервер Роботов через сокет:


        if(write_size > 0) {
            Serial.print("Write: ");
            Serial.print(write_buffer);
            Serial.println();
           
            tcpClient.writeStream((const byte*)write_buffer, write_size);
            write_size = 0;
        }
    }



Обработка входных данных - метод handleInput распознает команду (включить лампочку 'ledon' или выключить лампочку 'ledoff'), выполняет действие (включает или выключает лампочку), формирует ответ внутри reply_buffer ('ok' в случае успешного выполнения команды, 'dontunderstand', если команда не поддерживается), возвращает размер ответа байтах:


/**
* Обработать входные данные - разобрать строку, выполнить команду.
* @return размер ответа в байтах (0, чтобы не отправлять ответ).
*/
int handleInput(char* buffer, int size, char* reply_buffer) {
    int replySize = 0;
    reply_buffer[0] = 0;
   
    // Включить лампочку по команде "ledon", выключить по команде "ledoff"
    if(strcmp(buffer, CMD_LEDON) == 0) {
        Serial.println("Command 'ledon': turn light on");
       
        // Выполнить команду
        digitalWrite(LED_PIN, HIGH);
       
        // Подготовить ответ
        strcpy(reply_buffer, REPLY_OK);
        replySize = strlen(reply_buffer);
    } else if (strcmp(buffer, CMD_LEDOFF) == 0) {
        Serial.println("Command 'ledoff': turn light off");
       
        // Выполнить команду
        digitalWrite(LED_PIN, LOW);
       
        // Подготовить ответ
        strcpy(reply_buffer, REPLY_OK);
        replySize = strlen(reply_buffer);
    } else {
        Serial.print("Unknown command: ");
        Serial.println(buffer);
       
        // Подготовить ответ
        strcpy(reply_buffer, REPLY_DONTUNDERSTAND);
        replySize = strlen(reply_buffer);
    }
   
    return replySize;
}



Подключаем Робота Клиента WF32 к запущенному Серверу Роботов

Итак, Сервер Роботов запущен по адресу robotc.lasto4ka.su:1116 и готов принимать внешние входящие подключения через интернет.

konsole_ssh2_robotserver_start.png

Теперь подключим к нему Робота Клиента на плате ChipKIT WF32 - просто загружаем в плату прошивку, код которой представлен выше, и ждем некоторое количество секунд (для подключения к точке вайфай требуется время), пока в приглашении Сервера Роботов не появится сообщение о подключившемся клиенте (не забыть включить точку доступа WiFi для платы с выходом в интернет):

konsole_ssh3_robotserver_connected1.png

Робот Клиент подключился, вводим команды.

Включим лампочку: ledon, Enter:

konsole_ssh4_robotserver_ledon.png

server_chipkit1_ledon.jpg

Выключим лампочку:  ledoff, Enter:

konsole_ssh5_robotserver_ledoff.png

server_chipkit2_ledoff.jpg

Избавимся от подключенного клиента: kick

konsole_ssh6_robotserver_kick.png



исходники занятия, подсветка синтаксиса.
Tags: chipkit, облако, сервер роботов, типовые задачи
Subscribe

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 3 comments