1i7

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

Jun 29, 2014 13:43

Продолжаем эксперименты с управлением близкими устройствами из далёких облаков. В прошлый раз написали на языке 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

#include
#include

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

// Команды, принимаемые от Сервера Роботов
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 и готов принимать внешние входящие подключения через интернет.




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




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

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







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







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




исходники занятия, подсветка синтаксиса.

облако, типовые задачи, сервер роботов, chipkit

Previous post Next post
Up