Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Автоматизированная система уведомлений о выходе новых серий сериалов и аниме

Компоненты и материалы

Печатная плата JLCPCB (своя разработка)×1
Arduino Nano 33 IoT×1
Raspberry Pi 3 Model B (или Pi 4)×1
Raspberry Pi 4 Model B (альтернатива)×1
Экран Nokia 5110 (SparkFun)×1
Serial MP3 Player (OPEN-SMART)×1
RGB LED (общий анод)×1
SD-карта (Flash Memory)×2
Резистор 220 Ом×12
Тактильная кнопка 6×6×5 мм×4
DC Jack 2.1 мм (PCB mount)×1

Инструменты

Программное обеспечение и сервисы

О проекте

Описание

Существует множество веб/мобильных приложений для отслеживания дат выхода серий, но я хотел создать автономное устройство «с нуля» — железо и софт — с уникальными функциями.

Сначала разработано веб-приложение TV Series / Anime Release Date Tracker на PHP. Оно формирует список сериалов/аниме и получает даты выходов через TVMAZE REST API (endpoint /shows/:id/episodesbydate?date=:date). API возвращает полную информацию об эпизоде или 404, если в эту дату ничего не вышло.

Веб-сервер (LAMP + MariaDB) развёрнут на Raspberry Pi. Для тех, у кого нет Pi или не хотят его использовать — опубликована бесплатная версия с удобным интерфейсом на моём сайте TheAmplituhedron. Исходный код приложения приведён ниже.

Arduino Nano 33 IoT опрашивает веб-приложение. Если есть новая серия — парсит ответ с разделителем % (формат: %Название%Сезон%Эпизод%Имя_эпизода%), выводит информацию на Nokia 5110 и воспроизводит опенинг через Serial MP3 Player. Интерфейс меню на экране с 4 кнопками.

Плейлист опенингов хранится на microSD в папке 01/ как 001xxx.mp3, 002xxx.mp3 и т.д.

После тестов на макетке разработана собственная PCB в форме ретро-телефона (JLCPCB спонсировал изготовление).

Шаг 1: Проектирование и пайка PCB

Плата разработана в KiCad. Gerber-файлы в разделе файлов. Компоненты на плате:

Шаг 2: Настройка LAMP-сервера (MariaDB) на Raspberry Pi

  1. sudo apt-get install apache2 -y
  2. Проверка: https://localhost или https://<IP_Pi> (hostname -I)
  3. sudo apt-get update && sudo apt-get install php -y
  4. sudo apt-get install mariadb-server php-mysql -y
  5. sudo mysql_secure_installation — root password: bot, ответы Y на все вопросы
  6. Создание БД: sudo mysql -uroot -p → create database tvseries; GRANT ALL PRIVILEGES ON tvseries.* TO 'root'@'localhost' IDENTIFIED BY 'bot'; FLUSH PRIVILEGES;
  7. sudo apt-get install php-curl → sudo service apache2 restart

Шаг 3: Получение данных из TVMAZE REST API

Поиск шоу на TVMAZE → берем TV ID из URL.

Пример: https://api.tvmaze.com/shows/1505/episodesbydate?date=2023-10-15 (One Piece).

Если эпизод есть — возвращается JSON с полями season, number, name. Иначе — 404.

Мой список для тестов:

  1. One Piece — TV ID: 1505
  2. My Hero Academia — TV ID: 13615
  3. Westworld — TV ID: 1371
  4. The Simpsons — TV ID: 83
  5. The Late Late Show — TV ID: 2831 (ежедневный, удобен для тестов)

Шаг 4: Веб-приложение (PHP) и таблица series

Скачайте TV-Series-Anime-Episode-Tracker.zip (index.php, query.php), распакуйте, перенесите в /var/www/html/:

sudo mv /home/pi/Downloads/TV-Series-Anime-Episode-Tracker/ /var/www/html/

Откройте https://localhost/TV-Series-Anime-Episode-Tracker/ — создастся таблица series.

Добавьте шоу в БД:

INSERT INTO `series` (`series_name`, `tv_id`, `status`) VALUES ('One Piece', '1505', 'Pending');

query.php опрашивает TVMAZE для каждого шоу. При находке эпизода — выводит строку с разделителями % и меняет статус на Released (чтобы не повторяться). Пример ответа:

%The Late Late Show%2020%91%Jason Schwartzman, Alicia Key%

Если ничего — No Entry Found!

Работа без Raspberry Pi

Подписчики TheAmplituhedron могут использовать хостингую версию: веб-интерфейс.

Шаг 5: Настройка Arduino Nano 33 IoT

Шаг 6: Команды Serial MP3 Player (OPEN-SMART)

Плейлист опенингов (5 треков) в папке 01/ на microSD (FAT16/32). Имена: 001One Piece-Opening.mp3, 002My Hero Academia-Opening.mp3 и т.д.

UART команды (все начинаются с 7E, заканчиваются EF, второй байт — длина):

// Выбор SD-карты
{0x7E,0x03,0x35,0x01,0xEF}
// Воспроизведение трека 1..5
{0x7E,0x04,0x41,0x00,0x01,0xEF} ... {0x7E,0x04,0x41,0x00,0x05,0xEF}
// Play / Pause / Next / Prev
{0x7E,0x02,0x01,0xEF} / {0x7E,0x02,0x02,0xEF} / {0x7E,0x02,0x03,0xEF} / {0x7E,0x02,0x04,0xEF}

Функция send_command_to_MP3_player(cmd, len) шлёт байты в Serial1.

Шаг 7: Программирование Arduino Nano 33 IoT

Подключение

Nokia 5110:  D2-SCK, D3-MOSI, D4-DC, D5-RST, D6-CS
RGB:         D9-R, D10-G, D11-B
Кнопки:      A0-Left, A1-OK, A2-Right, A3-Exit
DS3231 (опц): A4-SDA, A5-SCL

Режимы и функции

  1. Init Tracker — подключение к серверу, опрос каждые 10 сек. При новой серии: инфо + опенинг + цвет. Без серии: «No Released Episode Detected :(» + иконка TV.
  2. MP3 Player — проигрывание плейлиста, управление кнопками.
  3. Sleep — сон через 10 сек, RGB-ночник, выход по Exit.

Видео и итог

Устройство закреплено на книжном шкафу клеевым пистолетом. Работает отлично!

Код

Series_Release_Date_Notifier.ino (Arduino)

////////////////////////////////////////////////////
// TV Series / Anime New Episode Release Date Notifier
// Arduino Nano 33 IoT
// by Kutluhan Aktar
////////////////////////////////////////////////////
#include 
#include 
#include 

char ssid[] = "[_SSID_]";
char pass[] = "[_PASSWORD_]";
int keyIndex = 0;
int status = WL_IDLE_STATUS;

IPAddress server(192, 168, 1, 22); // IP Raspberry Pi
String application = "/TV-Series-Anime-Episode-Tracker/query.php";

WiFiClient client;
LCD5110 myGLCD(2,3,4,5,6);
extern uint8_t SmallFont[], MediumNumbers[], tv[], music[];

static int8_t select_SD_card[] = {0x7e,0x03,0x35,0x01,0xef};
static int8_t play_song_1[] = {0x7e,0x04,0x41,0x00,0x01,0xef};
static int8_t play_song_2[] = {0x7e,0x04,0x41,0x00,0x02,0xef};
static int8_t play_song_3[] = {0x7e,0x04,0x41,0x00,0x03,0xef};
static int8_t play_song_4[] = {0x7e,0x04,0x41,0x00,0x04,0xef};
static int8_t play_song_5[] = {0x7e,0x04,0x41,0x00,0x05,0xef};
static int8_t play[] = {0x7e,0x02,0x01,0xef};
static int8_t pause[] = {0x7e,0x02,0x02,0xef};
static int8_t next_song[] = {0x7e,0x02,0x03,0xef};
static int8_t previous_song[] = {0x7e,0x02,0x04,0xef};

volatile boolean TV=false, Music=false, Sleep=false, Activated=false;
#define B_Exit A3; #define B_Right A2; #define B_OK A1; #define B_Left A0
#define redPin 9; #define greenPin 10; #define bluePin 11

int Right, OK, Left, Exit, selected=0;

void setup() {
  pinMode(B_Exit,INPUT); pinMode(B_Right,INPUT); pinMode(B_OK,INPUT); pinMode(B_Left,INPUT);
  pinMode(redPin,OUTPUT); pinMode(greenPin,OUTPUT); pinMode(bluePin,OUTPUT);
  adjustColor(0,0,0);
  myGLCD.InitLCD(); myGLCD.setFont(SmallFont);
  Serial.begin(9600); Serial1.begin(9600);
  send_command_to_MP3_player(select_SD_card,5);
  
  if(WiFi.status()==WL_NO_MODULE) { Serial.println("WiFi module failed!"); while(true); }
  while(status!=WL_CONNECTED) {
    Serial.print("Connecting to "); Serial.println(ssid);
    myGLCD.print("Waiting...",0,8); myGLCD.print("Attempting to",0,16); myGLCD.print("connect to",0,24); myGLCD.print("WiFi !!!",0,32);
    status = WiFi.begin(ssid,pass); delay(10000);
  }
  Serial.println("Connected to wifi");
  myGLCD.clrScr(); myGLCD.print("Connected to",0,8); myGLCD.print("WiFi!!!",0,16); delay(2000); myGLCD.clrScr();
}

void loop() {
  read_buttons(); change_menu_options(); interface();
  if(TV) { do { myGLCD.invertText(true); myGLCD.print("A.Init Tracker",0,16); myGLCD.invertText(false); delay(100);
    if(OK==HIGH) { myGLCD.clrScr(); Activated=true;
      while(Activated) {
        if(client.connect(server,80)) {
          Serial.println("connected to server");
          myGLCD.print("Connected to",0,8); myGLCD.print("the server!!!",0,16);
          client.println("GET "+application+" HTTP/1.1");
          client.println("Host: 192.168.1.22"); client.println("Connection: close"); client.println();
        } else { myGLCD.print("Connection",0,8); myGLCD.print("Error!!!",0,16); }
        delay(2000);
        String response="";
        while(client.available()) { char c=client.read(); response+=c; }
        if(response!="" && response.endsWith("%")) {
          int d=response.indexOf("%"), d1=response.indexOf("%",d+1), d2=response.indexOf("%",d1+1), d3=response.indexOf("%",d2+1), d4=response.indexOf("%",d3+1);
          String Series_Name=response.substring(d+1,d1), Season=response.substring(d1+1,d2), Episode=response.substring(d2+1,d3), Episode_Name=response.substring(d3+1,d4);
          myGLCD.clrScr(); myGLCD.print(Series_Name,0,0); myGLCD.print(Season+" x "+Episode,0,16); myGLCD.print(Episode_Name,0,32);
          if(Series_Name=="One Piece") { send_command_to_MP3_player(play_song_1,6); adjustColor(255,0,0); }
          else if(Series_Name=="My Hero Academia") { send_command_to_MP3_player(play_song_2,6); adjustColor(0,255,0); }
          else if(Series_Name=="Westworld") { send_command_to_MP3_player(play_song_3,6); adjustColor(0,0,255); }
          else if(Series_Name=="The Simpsons") { send_command_to_MP3_player(play_song_4,6); adjustColor(255,255,0); }
          else if(Series_Name=="The Late Late Show") { send_command_to_MP3_player(play_song_5,6); adjustColor(80,0,80); }
          boolean song=true;
          while(song) { read_buttons(); if(Exit==HIGH) { song=false; send_command_to_MP3_player(pause,4); adjustColor(0,0,0); } }
          myGLCD.clrScr();
        } else {
          myGLCD.clrScr(); myGLCD.print("No Released",0,0); myGLCD.print("Episode",0,16); myGLCD.print("Detected :(",0,32); delay(5000); myGLCD.clrScr();
        }
        myGLCD.drawBitmap(8,0,tv,60,48); delay(10000); myGLCD.clrScr();
        read_buttons(); if(Exit==HIGH) { Activated=false; myGLCD.clrScr(); }
      }
    }
  } while(TV==false); }
  
  if(Music) { do { myGLCD.invertText(true); myGLCD.print("B.MP3 Player",0,24); myGLCD.invertText(false); delay(100);
    if(OK==HIGH) { myGLCD.clrScr(); Activated=true;
      while(Activated) { read_buttons(); myGLCD.drawBitmap(8,0,music,60,48);
        if(Right) send_command_to_MP3_player(next_song,4);
        if(Left) send_command_to_MP3_player(previous_song,4);
        if(OK) send_command_to_MP3_player(pause,4);
        if(Exit==HIGH) { Activated=false; myGLCD.clrScr(); send_command_to_MP3_player(pause,4); }
      }
    }
  } while(Music==false); }
  
  if(Sleep) { do { myGLCD.invertText(true); myGLCD.print("C.Sleep",0,32); myGLCD.invertText(false); delay(100);
    if(OK==HIGH) {
      myGLCD.clrScr(); myGLCD.print("Entering",CENTER,0); myGLCD.print("Sleep Mode",CENTER,8); myGLCD.print("in",CENTER,16); myGLCD.print("Seconds",CENTER,40);
      myGLCD.setFont(MediumNumbers);
      for(int s=10;s>=0;s--) { myGLCD.printNumI(s,CENTER,24,2,'0'); delay(1000); }
      myGLCD.enableSleep(); Activated=true;
      while(Activated) {
        adjustColor(255,0,0); delay(500); adjustColor(0,255,0); delay(500);
        adjustColor(0,0,255); delay(500); adjustColor(255,255,0); delay(500); adjustColor(80,0,80); delay(500);
        read_buttons(); if(Exit==HIGH) { Activated=false; myGLCD.clrScr(); myGLCD.disableSleep(); myGLCD.setFont(SmallFont); adjustColor(0,0,0); }
      }
    }
  } while(Sleep==false); }
}

void read_buttons() { Right=digitalRead(B_Right); OK=digitalRead(B_OK); Left=digitalRead(B_Left); Exit=digitalRead(B_Exit); }

void send_command_to_MP3_player(int8_t cmd[], int len) {
  Serial.print("\nMP3 Command => ");
  for(int i=0;i3) selected=1; delay(100);
  switch(selected) {
    case 1: TV=true; Music=false; Sleep=false; break;
    case 2: TV=false; Music=true; Sleep=false; break;
    case 3: TV=false; Music=false; Sleep=true; break;
  }
}

void adjustColor(int r,int g,int b) {
  r=255-r; g=255-g; b=255-b;
  analogWrite(redPin,r); analogWrite(greenPin,g); analogWrite(bluePin,b);
}

index.php

0;
}
function createTable($table,$conn) {
  if(!checkDatabase($table,$conn)) {
    $sql="CREATE TABLE `$table`(id int(11) AUTO_INCREMENT PRIMARY KEY NOT NULL, series_name varchar(255) NOT NULL, tv_id varchar(255) NOT NULL, status varchar(255) NOT NULL);";
    mysqli_query($conn,$sql) ? print("Database Table Created!") : print("Error!");
  } else print("Database Table Found!");
}
createTable("series",$conn);
?>

query.php

conn=$conn; $this->table_name=$table; }
  private function check_ep_release_date($id,$name,$status) {
    $date=date("Y-m-d");
    $url=self::ROOT.$id.self::PATH.$date;
    if($data=json_decode(file_get_contents($url))) {
      if($status!="Released") {
        echo "%".$name."%".$data[0]->season."%".$data[0]->number."%".$data[0]->name."%";
        mysqli_query($this->conn,"UPDATE `$this->table_name` SET `status`='Released' WHERE series_name='$name'");
      }
    } else {
      if($status!="Pending") mysqli_query($this->conn,"UPDATE `$this->table_name` SET `status`='Pending' WHERE series_name='$name'");
    }
  }
  public function track_db_entries() {
    $sql="SELECT * FROM `$this->table_name` ORDER BY `id` DESC";
    if($res=mysqli_query($this->conn,$sql)) {
      if(mysqli_num_rows($res)>0) while($row=mysqli_fetch_assoc($res)) $this->check_ep_release_date($row['tv_id'],$row['series_name'],$row['status']);
      else echo "No Entry Found!";
    } else echo "No Database Found!";
  }
}
$conn=mysqli_connect("localhost","root","bot","tvseries");
$q=new query(); $q->define_user($conn,"series"); $q->track_db_entries();
?>

Файлы проекта

Схемы

Схемы подключения (4 варианта) в разделе файлов.

Производственный процесс

  1. Создание профессионального универсального пульта на Raspberry Pi с LIRC
  2. Преобразование RF в DC с Raspberry Pi: сборка и отладка детектора огибающей
  3. Cycle Chaser: превратите свой велосипед в ночной световый шоу с Raspberry Pi
  4. Создание детектора вора на базе Raspberry Pi — пошаговое руководство
  5. Создайте домашний монитор температуры на Raspberry Pi с MCP9808, InfluxDB и Grafana
  6. Создайте систему домашней автоматизации на Raspberry Pi для удаленного управления
  7. Raspberry Pi 3 B+ — Обновленные характеристики, спецификации и руководство по покупке
  8. DIY робот в стиле Wall‑E на Raspberry Pi из коробки от CD
  9. RasPiRobot Board V2: Расширьте Raspberry Pi в мощный робот-контроллер
  10. Построение ровера на Windows IoT Core с Raspberry Pi 2 — Руководство от новичка к профи