Открыть список
Как стать автором
Обновить

Слежение за статистикой в WoT c помощью Arduino

DIY или Сделай сам
Из песочницы


Привет, Хабр. Вспоминая свою инженерную молодость, захотелось снова поковыряться с железками. Возвращаться к PIC'акм и программингу на асме было откровенно лень (травматические детские воспоминания о ручной работе без сторонних библиотек с шиной i2c и HD44780), по этому полез в интернет и узнал о существовании платформы Arduino.

Беглый взгляд показал, что это как раз то, что мне нужно для удовлетворения ностальгических позывов. Неделю назад был приобретен набор. Мигать светодиодом бывшему инженегру электронщику с пятилетним стажем как-то не комильфо, по этому и родилась идея замерителя статистики в танках (каюсь, грешен...). Это мой своеобразный «Hello, Habr!» и «Hello, World!».

Идея проста — мониторить сервер статистики Wargaming и оповещать юзера о его игровых успехах (или на оборот). Мониторить решил при помощи Wargaming API, так как идея с парсингом веб-страниц посторонних ресурсов уперлась в размер RAM (8кб на прием) использованного для доступа в сеть шилда на контроллере W5100.

Для вывода информации взял то что было под рукой — стандартный дисплей 1602. Но что бы не было уж слишком тривиально, решил подключить его через сдвиговый регистр 74HC595. Быстрый поиск доказал старую истину, что все уже придумано до нас: link. Дисплей садится на шину SPI и дополнительно использует один пин для выбора устройства (у меня 3-й). Для успешного компилирования нужно заменить библиотеку LiquidCrystal на модифицированную из ссылки выше. Также по ссылке выше автор забыл посадить r/w пин дисплея на землю, из-за чего изначально подумал что пример не рабочий. На всякий случай моя бредовая доска:
Breadboard

Так же хотел реализовать логирование достижений на SD карту, но решил оставить на будущее, как и не большой веб-сервер с результатами мониторинга.

Скетч
#include <SPI.h>
#include <Ethernet.h>
#include <LiquidCrystal.h>

#define ETHERNET_PIN 10
#define LCD_PIN 3

//Принудительный IP адрес в сети
IPAddress ip(192, 168, 1, 40);

//MAC
byte mac[] = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42 };

//Сервак, зарегестрированный идешник, номер профиля
const char server[] = «api.worldoftanks.ru»;
const char application_id[] = «demo»;
const char account_id[] = «4848655»;

//Частота обновления в милисекундах
const unsigned int UpdateDelta = 20000;
//Осталось до обновления
unsigned int UpdateTime = 0;

//Сетевой клиент
EthernetClient client;
//LCD
LiquidCrystal lcd(LCD_PIN);

byte newChar1[8] = {
B00100,
B01110,
B10101,
B00100,
B00100,
B00100,
B00100,
B00000
};

byte newChar2[8] = {
B00100,
B00100,
B00100,
B00100,
B10101,
B01110,
B00100,
B00000
};

//Состояние парсинга
enum ReadStatusEnum {
WiteTag,
ReadTag,
WiteValue
} ReadStatus = WiteTag;

String nickname = «Unknown Noob»;
long WG_rating, pre_WG_rating, buf_WG_rating = 0;
unsigned long battles, pre_battles, buf_battles = 0;
unsigned long wins, pre_wins, buf_wins = 0;
unsigned long damage, pre_damage, buf_damage = 0;
unsigned long frags, pre_frags, buf_frags = 0;

String LCD_strings[6];

//Ищем нужное среди тегов
void FilterData(String SectionTagName, String TagName, String Value)
{
if (SectionTagName.compareTo(«statistics») == 0) {
if (TagName.compareTo(«nickname») == 0)
nickname = Value;
if (TagName.compareTo(«global_rating») == 0)
buf_WG_rating = Value.toInt();
}
if (SectionTagName.compareTo(«all») == 0) {
if (TagName.compareTo(«battles») == 0)
buf_battles = Value.toInt();
if (TagName.compareTo(«wins») == 0)
buf_wins = Value.toInt();
if (TagName.compareTo(«damage_dealt») == 0)
buf_damage = Value.toInt();
if (TagName.compareTo(«frags») == 0)
buf_frags = Value.toInt();
}
}

void readServer()
{
//Конектимся
if (client.connect(server, 80)) {
client.print(«GET /wot/account/info/?application_id=»);
client.print(application_id);
client.print("&account_id=");
client.print(account_id);
client.println(" HTTP/1.1");
client.print(«Host: „);
client.println(server);
client.println(“User-Agent: arduino-ethernet»);
client.println(«Accept: text/html»);
client.println(«Connection: close»);
client.println();

//Ждем готовности
bool Wait = true;
unsigned int Time = 0;
while (Wait)
{
Wait = !client.available();
if (Time > 200) Wait = false;
Time++;
delay(10);
}

char symbol = ' ';
char pre_symbol = ' ';
String TagName = "";
String SectionTagName = "";
String Value = "";
String PreSectionTagName = "";

ReadStatus = WiteTag;

//Повторять до опустошения буфера
while (client.available()) {
//Читаем байт из буфера
pre_symbol = symbol;
symbol = client.read();
//Serial.print(symbol);
if ( ReadStatus == WiteTag && pre_symbol == '"' && symbol != ':') {
//Найдено начало тега
ReadStatus = ReadTag;
TagName = "";
}

if (ReadStatus == ReadTag) {
if(symbol != '"') {
//Читаем имя тега
TagName += symbol;
}
else
{
//Найден конец тега
Value = "";
ReadStatus = WiteValue;
}
}

if (ReadStatus == WiteValue){
if(symbol == ',' || symbol == '}'){
//Конец значения
ReadStatus = WiteTag;
FilterData(SectionTagName, TagName, Value);
if (symbol == '}') {
//Конец секции
SectionTagName = PreSectionTagName;
}
}
else {
if (symbol == '{') {
//Начало секции
PreSectionTagName = SectionTagName;
SectionTagName = TagName;
ReadStatus = WiteTag;
}
else {
//Читаем значение
if (symbol != ':' && symbol != '"' && symbol != ' ')
Value += symbol;
}
}
}
}
}
//Стопим клиент
client.flush();
client.stop();
}

void generateSrings()
{
if(pre_battles == 0 || battles == 0) {
//Инициализация
pre_WG_rating = buf_WG_rating;
WG_rating = buf_WG_rating;
pre_battles = buf_battles;
battles = buf_battles;
pre_wins = buf_wins;
wins = buf_wins;
pre_damage = buf_damage;
damage = buf_damage;
pre_frags = buf_frags;
frags = buf_frags;
}

if (buf_battles > battles)
{
//Было обновление статы
pre_WG_rating = WG_rating;
WG_rating = buf_WG_rating;
pre_battles = battles;
battles = buf_battles;
pre_wins = wins;
wins = buf_wins;
pre_damage = damage;
damage = buf_damage;
pre_frags = frags;
frags = buf_frags;
}

if (pre_battles == battles)
{
LCD_strings[0] = nickname + " ";
LCD_strings[0] = LCD_strings[0].substring(0,16);

LCD_strings[1] = «Btl:» + String(battles) + " — ";
LCD_strings[1] = LCD_strings[1].substring(0,16);

float wins_percent = (float)wins/(float)battles*100.0;
LCD_strings[2] = «Wins:» + String(wins_percent) + " — ";
LCD_strings[2] = LCD_strings[2].substring(0,16);

float avrage_damage = (float)damage/(float)battles;
String D = String(avrage_damage);
D = D.substring(0,D.length()-1);
LCD_strings[3] = «Dmg:» + D + " — ";
LCD_strings[3] = LCD_strings[3].substring(0,16);

float avrage_frags = (float)frags/(float)battles;
LCD_strings[4] = «Frag:» + String(avrage_frags) + " — ";
LCD_strings[4] = LCD_strings[4].substring(0,16);

LCD_strings[5] = " WG:" + String(WG_rating) + " — ";
LCD_strings[5] = LCD_strings[5].substring(0,16);
}
else
{
LCD_strings[0] = nickname + " ";
LCD_strings[0] = LCD_strings[0].substring(0,16);

LCD_strings[1] = «Btl:» + String(battles) + " " + char(0x01) + String(battles-pre_battles) +" ";
LCD_strings[1] = LCD_strings[1].substring(0,16);

float wins_percent = (float)wins/(float)battles*100.0;
float pre_wins_percent = (float)pre_wins/(float)pre_battles*100.0;
char Delta = char(0x01);
if (wins_percent < pre_wins_percent) Delta = char(0x02);
LCD_strings[2] = «Wins:» + String(wins_percent) + " " + Delta + String(abs(wins_percent — pre_wins_percent)) + " ";
LCD_strings[2] = LCD_strings[2].substring(0,16);

float avrage_damage = (float)damage/(float)battles;
float pre_avrage_damage = (float)pre_damage/(float)pre_battles;
Delta = char(0x01);
if (avrage_damage < pre_avrage_damage) Delta = char(0x02);
String D = String(avrage_damage);
D = D.substring(0,D.length()-1);
LCD_strings[3] = «Dmg:» + D + " " + Delta + String(abs(avrage_damage — pre_avrage_damage)) + " ";
LCD_strings[3] = LCD_strings[3].substring(0,16);

float avrage_frags = (float)frags/(float)battles;
float pre_avrage_frags = (float)pre_frags/(float)pre_battles;
Delta = char(0x01);
if (avrage_frags < pre_avrage_frags) Delta = char(0x02);
LCD_strings[4] = «Frag:» + String(avrage_frags) + " " + Delta + String(abs(avrage_frags — pre_avrage_frags)) + " ";
LCD_strings[4] = LCD_strings[4].substring(0,16);

Delta = char(0x01);
if (WG_rating < pre_WG_rating) Delta = char(0x02);
LCD_strings[5] = " WG:" + String(WG_rating) + " " + Delta + String(abs(WG_rating — pre_WG_rating)) + " ";
LCD_strings[5] = LCD_strings[5].substring(0,16);
}
}

void PrintMSG(int LCD_tick, int ScrNum)
{
if (LCD_tick <= 16)
{
lcd.setCursor(0, 0);
lcd.print(LCD_strings[ScrNum*2 + 0].substring(0, LCD_tick) + "_ ");
}
else
{
lcd.setCursor(0, 1);
lcd.print(LCD_strings[ScrNum*2 + 1].substring(0, LCD_tick-16) + "_ ");
}
}

unsigned int LCD_Screen = 0;
unsigned int LCD_tick = 0;
void UpdateLCD(unsigned int UpdateTime)
{
if (UpdateTime > UpdateDelta/3*2)
{
//Первый экран
if(LCD_Screen != 0) {
LCD_Screen = 0;
LCD_tick = 0;
}
PrintMSG( LCD_tick, LCD_Screen);
}
else
{
if (UpdateTime > UpdateDelta/3)
{
//Второй экран
if(LCD_Screen != 1) {
LCD_Screen = 1;
LCD_tick = 0;
}
PrintMSG( LCD_tick, LCD_Screen);
}
else
{
//Третий экран
if(LCD_Screen != 2) {
LCD_Screen = 2;
LCD_tick = 0;
}
PrintMSG( LCD_tick, LCD_Screen);
}
}

LCD_tick++;
if (LCD_tick > 32) LCD_tick = 32;
}

void setup()
{
//Инициализация дисплея
lcd.createChar(1, newChar1);
lcd.createChar(2, newChar2);
lcd.begin(16, 2);

//Инициализация последовательного порта
//Serial.begin(57600);
pinMode(ETHERNET_PIN, OUTPUT);
digitalWrite(ETHERNET_PIN, LOW);
delay(1000);
Ethernet.begin(mac, ip);
delay(1000);
digitalWrite(ETHERNET_PIN, HIGH);
}

void loop()
{
//Запрашиваем стату
if (UpdateTime == 0) {
UpdateTime = UpdateDelta;
digitalWrite(ETHERNET_PIN, LOW);
readServer();
digitalWrite(ETHERNET_PIN, HIGH);
generateSrings();
}

UpdateLCD(UpdateTime);
delay(100);
UpdateTime -= 100;
}

А вот демонстрация работы. К сожалению, сервера Wargaming обновляют статистику только после завершения игровой сессии:



Ну и напоследок ложка дегтя — по непонятным мне причинам, через некоторое время работы, девайс перестает забирать статистику с сервера. По дебагу и индикаторам видно, что запрос уходит, а вот ответа нет. Надеюсь, это из-за моего криво настроенного роутера.
Upd. Уже не актуально, поправил скетч
Теги:wargaming.networld of tanksarduinoethernet shield
Хабы: DIY или Сделай сам
Всего голосов 33: ↑23 и ↓10 +13
Просмотры26.2K

Похожие публикации

VP of engineering
от 300 000 до 390 000 ₽MakeomaticМожно удаленно
Head of product
от 350 000 ₽IglooМоскваМожно удаленно
NodeJS (Middle или Junior+)
от 90 000 до 120 000 ₽idPowersМожно удаленно
Веб-разработчик (Backend или Fullstack)
от 80 000 ₽PlenexyМожно удаленно

Лучшие публикации за сутки