Продолжаем эксперименты с управлением близкими устройствами из далёких облаков. В прошлый раз
написали на языке 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/JavaTcpServerMaster2) Робот Клиент на ChipKIT WF32:
chipkit-cloud-wifi/chipkit_tcp_client_slave3) Робот Клиент на 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
исходники занятия,
подсветка синтаксиса.