EPISODE · Jul 6, 2026 · 26 MIN
376 日記 | 棚選択ボタンとLEDランプを追加
from TanaRadio · host たな
ボタンで棚を選択できるようにしました。選んでいる棚番号はLEDランプの点滅回数で表示します(フェーズ5F完了)。# TanaRadio Pi フェーズ5F 詳細マニュアル ## 棚選びボタンとLED1を追加する ## 0. 今回の目標 フェーズ5Eでは、棚ファイルを指定してプレイリストを作り、mpvで再生できるようになりました。 フェーズ5Fでは、その「棚の切り替え」を物理ボタンで行えるようにします。 今回の動きは次の通りです。 ```text 棚選びボタンを押す ↓ 棚番号が1つ進む ↓ LED1が棚番号ぶん点滅する ↓ 選ばれた棚ファイルから playlist.m3u を作る ↓ mpvをその棚で再生し直す ``` 今回はまだ、再生モード選びボタンは追加しません。 再生モードは、5Eまでと同じく、 ```text 過去3日分 古い順 ``` を基本にします。 --- # 1. 今回使う棚構成 フェーズ5Fでは、フェーズ5Eで作った次の棚構成を使います。 ```text 1: 声日記・生活 (voice) -> shelf_01_voice_life.txt 2: 技術・AI・ものづくり (tech) -> shelf_02_tech_ai_mono.txt 3: 教育・研究・知の実践 (edu) -> shelf_03_education_research.txt 4: 本・文化・物語 (books) -> shelf_04_books_culture.txt 5: TanaRadio (tana) -> shelf_05_tanaradio.txt ``` 棚選びボタンを押すと、この順番で進みます。 ```text 声日記・生活 ↓ 技術・AI・ものづくり ↓ 教育・研究・知の実践 ↓ 本・文化・物語 ↓ TanaRadio ↓ 声日記・生活 ``` LED1の点滅回数は、次の対応です。 ```text 1回点滅:声日記・生活 2回点滅:技術・AI・ものづくり 3回点滅:教育・研究・知の実践 4回点滅:本・文化・物語 5回点滅:TanaRadio ``` --- # 2. 今回追加する部品 今回追加する部品は2つです。 ```text タクトスイッチ 1個 LED 1個 ``` 用途は次の通りです。 ```text タクトスイッチ:棚選びボタン LED:棚番号表示LED ``` 既存の3ボタンは、そのまま使います。 ```text 再生/一時停止 戻る 次へ ``` つまり、フェーズ5F終了時点では、次の構成になります。 ```text ボタン1:再生/一時停止 ボタン2:戻る ボタン3:次へ ボタン4:棚選び LED1:棚番号表示 ``` --- # 3. GPIO番号 このマニュアルでは、次のGPIO番号を使います。 ```text 再生/一時停止:GPIO17 戻る:GPIO27 次へ:GPIO22 棚選びボタン:GPIO23 棚番号LED:GPIO24 ``` Raspberry Piの物理ピン番号では、次の対応です。 ```text GPIO17:物理ピン11 GPIO27:物理ピン13 GPIO22:物理ピン15 GPIO23:物理ピン16 GPIO24:物理ピン18 GND:物理ピン6, 9, 14, 20, 25 など ``` 注意点です。 ```text GPIO番号と物理ピン番号は違います。 ``` コードでは「GPIO番号」を使います。 たとえば、 ```text GPIO23 ``` は、物理ピン番号では、 ```text 16番ピン ``` です。 ここを取り違えると、ボタンを押しても無反応になります。静かな失敗です。電子工作ではいちばん地味に困るやつです。 --- # 4. 電源を切る 配線を触る前に、必ずRaspberry Piを終了します。 ターミナルで次を実行します。 ```bash sudo shutdown -h now ``` 完全に電源が切れてから、USB-C電源を抜きます。 --- # 5. 棚選びボタンを配線する 棚選びボタンは、次のようにつなぎます。 ```text GPIO23 ─ 1kΩ抵抗 ─ タクトスイッチ ─ GND ``` この構成では、Python側で内部プルアップを使います。 そのため、動作は次のようになります。 ```text ボタンを押していない:HIGH ボタンを押した:LOW ``` これまでのボタンと同じ考え方です。 --- # 6. 棚番号LEDを配線する LEDは、次のようにつなぎます。 ```text GPIO24 ─ 330Ω抵抗 ─ LEDの長い足 LEDの短い足 ─ GND ``` LEDの足の目安です。 ```text 長い足:プラス側 短い足:GND側 ``` 光らない場合は、まずLEDの向きを疑ってください。 LEDは、向きが逆だと基本的に光りません。 壊れたわけではなく、ただ黙っているだけです。 --- # 7. Raspberry Piを起動する 配線が終わったら、Raspberry Piの電源を入れます。 ターミナルを開き、作業フォルダに移動します。 ```bash cd ~/tanaradio5 ``` 現在地を確認します。 ```bash pwd ``` 次のように表示されればOKです。 ```text /home/tana/tanaradio5 ``` ユーザー名が違う場合は、そこだけ変わります。 --- # 8. 必要なパッケージを確認する GPIOとmpvを使うため、念のため次を実行します。 ```bash sudo apt update sudo apt install -y python3-gpiozero python3-lgpio mpv ``` すでに入っている場合は、そのままで大丈夫です。 --- # 9. 棚ファイルを確認する 今回使う棚ファイルがあるか確認します。 ```bash ls shelf_*.txt ``` 次の5つが見えればOKです。 ```text shelf_01_voice_life.txt shelf_02_tech_ai_mono.txt shelf_03_education_research.txt shelf_04_books_culture.txt shelf_05_tanaradio.txt ``` それぞれの中身も確認します。 ```bash cat shelf_01_voice_life.txt cat shelf_02_tech_ai_mono.txt cat shelf_03_education_research.txt cat shelf_04_books_culture.txt cat shelf_05_tanaradio.txt ``` RSS URLが1行ずつ並んでいればOKです。 例です。 ```text https://listen.style/p/xxxx/rss https://listen.style/p/yyyy/rss https://listen.style/p/zzzz/rss ``` 空の棚ファイルがある場合、その棚に切り替えたときにエラーになります。 テスト段階では、空の棚にも一時的に動作確認用のRSS URLを1本入れておくと安心です。 --- # 10. 5F用Pythonファイルを作る 新しいPythonファイルを作ります。 ```bash nano tanaradio_5f.py ``` 次のコードをそのまま貼り付けます。 重要です。 最初の行は必ず、次で始めてください。 ```text #!/usr/bin/env python3 ``` この前に、余計な文字を入れないでください。 ```python #!/usr/bin/env python3 import argparse import json import os import signal import socket import subprocess import time import urllib.request import xml.etree.ElementTree as ET from dataclasses import dataclass from datetime import datetime, timedelta, timezone from email.utils import parsedate_to_datetime from pathlib import Path from threading import Lock from gpiozero import Button, LED # ========================= # GPIO設定 # 5Dまでと違うGPIOを使っていた場合は、ここだけ直してください。 # BCM番号です。物理ピン番号ではありません。 # ========================= PLAY_GPIO = 17 # 再生/一時停止ボタン PREV_GPIO = 27 # 戻るボタン NEXT_GPIO = 22 # 次へボタン SHELF_GPIO = 23 # 棚選びボタン(今回追加) SHELF_LED_GPIO = 24 # 棚番号表示LED(今回追加) # ========================= # ファイル設定 # ========================= BASE_DIR = Path(__file__).resolve().parent PLAYLIST_FILE = BASE_DIR / "playlist.m3u" STATE_FILE = BASE_DIR / "state_5f.json" MPV_SOCKET = "/tmp/tanaradio_5f_mpv.sock" # フェーズ5Fでは、まだ再生モードボタンは追加しません。 # 5Eまでと同じく「過去3日分・古い順」を基本にします。 DAYS_BACK = 3 # ========================= # 棚設定 # フェーズ5Eで作った棚構成に合わせています。 # ========================= SHELVES = [ { "label": "声日記・生活", "short": "voice", "file": "shelf_01_voice_life.txt", }, { "label": "技術・AI・ものづくり", "short": "tech", "file": "shelf_02_tech_ai_mono.txt", }, { "label": "教育・研究・知の実践", "short": "edu", "file": "shelf_03_education_research.txt", }, { "label": "本・文化・物語", "short": "books", "file": "shelf_04_books_culture.txt", }, { "label": "TanaRadio", "short": "tana", "file": "shelf_05_tanaradio.txt", }, ] @dataclass class Episode: title: str audio_url: str pub_date: datetime | None feed_title: str mpv_process: subprocess.Popen | None = None current_shelf_index = 0 shelf_lock = Lock() shelf_led: LED | None = None def read_lines(path: Path) -> list[str]: if not path.exists(): return [] lines = [] for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line: continue if line.startswith("#"): continue lines.append(line) return lines def load_state() -> int: if not STATE_FILE.exists(): return 0 try: data = json.loads(STATE_FILE.read_text(encoding="utf-8")) index = int(data.get("shelf_index", 0)) if 0 <= index < len(SHELVES): return index except Exception: pass return 0 def save_state(index: int) -> None: data = { "shelf_index": index, "shelf_label": SHELVES[index]["label"], "shelf_short": SHELVES[index]["short"], "shelf_file": SHELVES[index]["file"], "saved_at": datetime.now().isoformat(timespec="seconds"), } STATE_FILE.write_text( json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8", ) def download_rss(url: str) -> bytes: request = urllib.request.Request( url, headers={"User-Agent": "TanaRadio-Pi/5F"}, ) with urllib.request.urlopen(request, timeout=25) as response: return response.read() def get_child_text(parent: ET.Element, tag_name: str) -> str: element = parent.find(tag_name) if element is not None and element.text: return element.text.strip() return "" def find_audio_url(item: ET.Element) -> str | None: # 一般的なRSS Podcastのenclosureタグ enclosure = item.find("enclosure") if enclosure is not None: audio_url = enclosure.attrib.get("url") if audio_url: return audio_url # media:content など、名前空間つきタグにも軽く対応 for child in item: if child.tag.endswith("content"): audio_url = child.attrib.get("url") if audio_url: return audio_url return None def parse_pub_date(text: str) -> datetime | None: if not text: return None try: dt = parsedate_to_datetime(text) if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return dt.astimezone() except Exception: return None def parse_rss(rss_data: bytes, feed_url: str) -> list[Episode]: root = ET.fromstring(rss_data) channel = root.find("channel") if channel is None: raise ValueError("RSSのchannelが見つかりません。") feed_title = get_child_text(channel, "title") or feed_url episodes: list[Episode] = [] for item in channel.findall("item"): audio_url = find_audio_url(item) if not audio_url: continue title = get_child_text(item, "title") or "タイトルなし" pub_date = parse_pub_date(get_child_text(item, "pubDate")) episodes.append( Episode( title=title, audio_url=audio_url, pub_date=pub_date, feed_title=feed_title, ) ) return episodes def collect_episodes_for_shelf(index: int) -> list[Episode]: shelf = SHELVES[index] shelf_path = BASE_DIR / shelf["file"] feed_urls = read_lines(shelf_path) if not feed_urls: raise ValueError(f"{shelf_path.name} にRSS URLがありません。") print() print(f"棚 {index + 1}: {shelf['label']} ({shelf['short']})") print(f"棚ファイル: {shelf_path.name}") print(f"RSS数: {len(feed_urls)}") all_episodes: list[Episode] = [] for feed_url in feed_urls: print(f"RSS取得中: {feed_url}") try: rss_data = download_rss(feed_url) episodes = parse_rss(rss_data, feed_url) all_episodes.extend(episodes) print(f" 取得: {len(episodes)} 件") except Exception as e: print(f" このRSSは読み飛ばしました: {e}") if not all_episodes: raise ValueError("音声エピソードを1件も取得できませんでした。") now = datetime.now().astimezone() cutoff = now - timedelta(days=DAYS_BACK) filtered = [ ep for ep in all_episodes if ep.pub_date is not None and ep.pub_date >= cutoff ] if not filtered: print(f"過去{DAYS_BACK}日分のエピソードがありません。") print("動作確認しやすいように、今回は最新10件でプレイリストを作ります。") filtered = sorted( all_episodes, key=lambda ep: ep.pub_date or datetime.min.replace(tzinfo=timezone.utc), reverse=True, )[:10] filtered.sort( key=lambda ep: ep.pub_date or datetime.min.replace(tzinfo=timezone.utc) ) else: filtered.sort( key=lambda ep: ep.pub_date or datetime.min.replace(tzinfo=timezone.utc) ) return filtered def make_playlist(index: int) -> int: episodes = collect_episodes_for_shelf(index) lines = ["#EXTM3U"] for ep in episodes: date_text = ep.pub_date.isoformat(timespec="minutes") if ep.pub_date else "日付不明" safe_title = ep.title.replace("\n", " ").strip() safe_feed = ep.feed_title.replace("\n", " ").strip() lines.append(f"# {date_text} / {safe_feed} / {safe_title}") lines.append(ep.audio_url) PLAYLIST_FILE.write_text( "\n".join(lines) + "\n", encoding="utf-8", ) print(f"{PLAYLIST_FILE.name} を作成しました。") print(f"音声URL数: {len(episodes)} 件") return len(episodes) def wait_for_mpv_socket(timeout: float = 5.0) -> bool: start = time.time() while time.time() - start < timeout: if os.path.exists(MPV_SOCKET): return True time.sleep(0.1) return False def send_mpv_command(command: list[str | int | float]) -> None: if not os.path.exists(MPV_SOCKET): print("mpvのソケットが見つかりません。mpvがまだ起動していない可能性があります。") return payload = json.dumps({"command": command}) + "\n" try: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client: client.connect(MPV_SOCKET) client.sendall(payload.encode("utf-8")) except Exception as e: print(f"mpvへの命令送信に失敗しました: {e}") def stop_mpv() -> None: global mpv_process if mpv_process is None: return if mpv_process.poll() is None: try: send_mpv_command(["quit"]) mpv_process.wait(timeout=3) except Exception: mpv_process.terminate() try: mpv_process.wait(timeout=3) except Exception: mpv_process.kill() mpv_process = None if os.path.exists(MPV_SOCKET): try: os.remove(MPV_SOCKET) except OSError: pass def start_mpv() -> None: global mpv_process stop_mpv() if os.path.exists(MPV_SOCKET): os.remove(MPV_SOCKET) print("mpvを起動します。") mpv_process = subprocess.Popen( [ "mpv", "--input-ipc-server=" + MPV_SOCKET, "--force-window=no", str(PLAYLIST_FILE), ] ) if not wait_for_mpv_socket(): print("mpvの操作ソケットを確認できませんでした。") print("再生自体は始まっている可能性があります。") def blink_shelf_number(index: int) -> None: if shelf_led is None: return count = index + 1 print(f"LED点滅: {count}回") for _ in range(count): shelf_led.on() time.sleep(0.18) shelf_led.off() time.sleep(0.18) time.sleep(0.25) def blink_error() -> None: if shelf_led is None: return for _ in range(6): shelf_led.on() time.sleep(0.07) shelf_led.off() time.sleep(0.07) def select_shelf(index: int, restart_player: bool = True) -> bool: global current_shelf_index with shelf_lock: try: make_playlist(index) current_shelf_index = index save_state(index) blink_shelf_number(index) if restart_player: start_mpv() print(f"現在の棚: {index + 1} / {SHELVES[index]['label']} ({SHELVES[index]['short']})") return True except Exception as e: print("棚の切り替えに失敗しました。") print(e) blink_error() return False def next_shelf() -> None: next_index = (current_shelf_index + 1) % len(SHELVES) print() print(f"棚ボタン: {current_shelf_index + 1} → {next_index + 1}") print(f"次の棚: {SHELVES[next_index]['label']} ({SHELVES[next_index]['short']})") select_shelf(next_index, restart_player=True) def on_play() -> None: print("再生/一時停止") send_mpv_command(["cycle", "pause"]) def on_prev() -> None: print("戻る") send_mpv_command(["playlist-prev"]) def on_next() -> None: print("次へ") send_mpv_command(["playlist-next"]) def list_shelves() -> None: print("棚一覧") for i, shelf in enumerate(SHELVES, start=1): path = BASE_DIR / shelf["file"] urls = read_lines(path) print( f"{i}: {shelf['label']} ({shelf['short']}) " f"-> {path.name} RSS数: {len(urls)}" ) def test_led() -> None: global shelf_led shelf_led = LED(SHELF_LED_GPIO) try: for index, shelf in enumerate(SHELVES): print(f"棚{index + 1}: {shelf['label']} ({shelf['short']}) の点滅テスト") blink_shelf_number(index) time.sleep(0.5) finally: shelf_led.off() shelf_led.close() shelf_led = None def main() -> None: global current_shelf_index, shelf_led parser = argparse.ArgumentParser(description="TanaRadio Pi フェーズ5F") parser.add_argument("--list", action="store_true", help="棚一覧を表示する") parser.add_argument("--test-led", action="store_true", help="LEDの点滅をテストする") args = parser.parse_args() if args.list: list_shelves() return if args.test_led: test_led() return current_shelf_index = load_state() print("TanaRadio Pi フェーズ5Fを起動します。") print() list_shelves() print() print( f"前回の棚: {current_shelf_index + 1} / " f"{SHELVES[current_shelf_index]['label']} " f"({SHELVES[current_shelf_index]['short']})" ) shelf_led = LED(SHELF_LED_GPIO) play_button = Button(PLAY_GPIO, pull_up=True, bounce_time=0.08) prev_button = Button(PREV_GPIO, pull_up=True, bounce_time=0.08) next_button = Button(NEXT_GPIO, pull_up=True, bounce_time=0.08) shelf_button = Button(SHELF_GPIO, pull_up=True, bounce_time=0.12) play_button.when_pressed = on_play prev_button.when_pressed = on_prev next_button.when_pressed = on_next shelf_button.when_pressed = next_shelf try: ok = select_shelf(current_shelf_index, restart_player=True) if not ok: print("最初の棚を作れませんでした。棚ファイルを確認してください。") return print() print("操作できます。") print("再生/一時停止、戻る、次へ、棚選びボタンを押してください。") print("終了するには Ctrl + C を押します。") signal.pause() except KeyboardInterrupt: print() print("終了します。") finally: stop_mpv() if shelf_led is not None: shelf_led.off() shelf_led.close() play_button.close() prev_button.close() next_button.close() shelf_button.close() if __name__ == "__main__": main() ``` 保存します。 ```text Ctrl + O Enter Ctrl + X ``` --- # 11. 実行権限を付ける 次を実行します。 ```bash chmod +x tanaradio_5f.py ``` ただし、実行は `python3` で行うのが安全です。 ```bash python3 tanaradio_5f.py ``` 以前のように、Pythonファイルをシェルスクリプトとして読んでしまう事故を避けるためです。 --- # 12. 棚一覧を確認する まず、棚ファイルが正しく認識されているか確認します。 ```bash python3 tanaradio_5f.py --list ``` 期待される表示例です。 ```text 棚一覧 1: 声日記・生活 (voice) -> shelf_01_voice_life.txt RSS数: 8 2: 技術・AI・ものづくり (tech) -> shelf_02_tech_ai_mono.txt RSS数: 6 3: 教育・研究・知の実践 (edu) -> shelf_03_education_research.txt RSS数: 5 4: 本・文化・物語 (books) -> shelf_04_books_culture.txt RSS数: 4 5: TanaRadio (tana) -> shelf_05_tanaradio.txt RSS数: 1 ``` RSS数は実際のファイル内容によって変わります。 ここで重要なのは、次の対応が正しく出ることです。 ```text 1: 声日記・生活 -> shelf_01_voice_life.txt 2: 技術・AI・ものづくり -> shelf_02_tech_ai_mono.txt 3: 教育・研究・知の実践 -> shelf_03_education_research.txt 4: 本・文化・物語 -> shelf_04_books_culture.txt 5: TanaRadio -> shelf_05_tanaradio.txt ``` --- # 13. LEDだけテストする いきなり再生まで行かず、まずLEDの点滅を確認します。 ```bash python3 tanaradio_5f.py --test-led ``` 期待される動きです。 ```text 棚1:LEDが1回点滅 棚2:LEDが2回点滅 棚3:LEDが3回点滅 棚4:LEDが4回点滅 棚5:LEDが5回点滅 ``` 表示例です。 ```text 棚1: 声日記・生活 (voice) の点滅テスト LED点滅: 1回 棚2: 技術・AI・ものづくり (tech) の点滅テスト LED点滅: 2回 棚3: 教育・研究・知の実践 (edu) の点滅テスト LED点滅: 3回 棚4: 本・文化・物語 (books) の点滅テスト LED点滅: 4回 棚5: TanaRadio (tana) の点滅テスト LED点滅: 5回 ``` LEDが光らない場合は、次を確認します。 ```text LEDの向きが逆ではないか GPIO24ではなく別のピンにつないでいないか GNDにつながっているか 抵抗を入れているか ``` --- # 14. 5Fを起動する いよいよ5Fを起動します。 ```bash python3 tanaradio_5f.py ``` うまくいくと、次のような表示になります。 ```text TanaRadio Pi フェーズ5Fを起動します。 棚一覧 1: 声日記・生活 (voice) -> shelf_01_voice_life.txt RSS数: 8 2: 技術・AI・ものづくり (tech) -> shelf_02_tech_ai_mono.txt RSS数: 6 3: 教育・研究・知の実践 (edu) -> shelf_03_education_research.txt RSS数: 5 4: 本・文化・物語 (books) -> shelf_04_books_culture.txt RSS数: 4 5: TanaRadio (tana) -> shelf_05_tanaradio.txt RSS数: 1 前回の棚: 1 / 声日記・生活 (voice) 棚 1: 声日記・生活 (voice) 棚ファイル: shelf_01_voice_life.txt RSS数: 8 RSS取得中: ... playlist.m3u を作成しました。 音声URL数: 10 件 LED点滅: 1回 mpvを起動します。 現在の棚: 1 / 声日記・生活 (voice) 操作できます。 再生/一時停止、戻る、次へ、棚選びボタンを押してください。 終了するには Ctrl + C を押します。 ``` 音が出れば、まず成功です。 --- # 15. 既存3ボタンを確認する 次のボタンを押します。 ```text 再生/一時停止 戻る 次へ ``` ターミナルに次のように表示されれば、ボタン入力は読めています。 ```text 再生/一時停止 戻る 次へ ``` 音声もそれに合わせて動けばOKです。 --- # 16. 棚選びボタンを押す 棚選びボタンを1回押します。 棚1から棚2に進む場合、期待される動きは次の通りです。 ```text 棚1:声日記・生活 ↓ 棚2:技術・AI・ものづくり ``` ターミナルには、たとえば次のように表示されます。 ```text 棚ボタン: 1 → 2 次の棚: 技術・AI・ものづくり (tech) 棚 2: 技術・AI・ものづくり (tech) 棚ファイル: shelf_02_tech_ai_mono.txt RSS数: 6 RSS取得中: ... playlist.m3u を作成しました。 音声URL数: 12 件 LED点滅: 2回 mpvを起動します。 現在の棚: 2 / 技術・AI・ものづくり (tech) ``` さらに押すと、次のように進みます。 ```text 2 → 3:教育・研究・知の実践 3 → 4:本・文化・物語 4 → 5:TanaRadio 5 → 1:声日記・生活 ``` LED点滅の対応は次の通りです。 ```text 1回点滅:声日記・生活 2回点滅:技術・AI・ものづくり 3回点滅:教育・研究・知の実践 4回点滅:本・文化・物語 5回点滅:TanaRadio ``` --- # 17. 前回の棚が保存されるか確認する 一度終了します。 ```text Ctrl + C ``` もう一度起動します。 ```bash python3 tanaradio_5f.py ``` 前回最後に選んだ棚から始まれば成功です。 棚の状態は、次のファイルに保存されます。 ```text state_5f.json ``` 中身を確認するには、次を実行します。 ```bash cat state_5f.json ``` 表示例です。 ```json { "shelf_index": 1, "shelf_label": "技術・AI・ものづくり", "shelf_short": "tech", "shelf_file": "shelf_02_tech_ai_mono.txt", "saved_at": "2026-07-06T09:30:00" } ``` ここで `shelf_index` は0始まりです。 つまり、 ```text shelf_index 0:棚1 shelf_index 1:棚2 shelf_index 2:棚3 shelf_index 3:棚4 shelf_index 4:棚5 ``` です。 --- # 18. 成功条件 フェーズ5Fの成功条件は、次の4つです。 ```text 1. 棚選びボタンを押すと、棚番号が進む 2. LED1が棚番号ぶん点滅する 3. 選ばれた棚ファイルに応じて playlist.m3u が作り直される 4. mpvが新しい棚で再生し直される ``` 棚番号の対応は次の通りです。 ```text 1:声日記・生活 2:技術・AI・ものづくり 3:教育・研究・知の実践 4:本・文化・物語 5:TanaRadio ``` LED点滅の対応は次の通りです。 ```text 1回点滅:声日記・生活 2回点滅:技術・AI・ものづくり 3回点滅:教育・研究・知の実践 4回点滅:本・文化・物語 5回点滅:TanaRadio ``` --- # 20. 今回はここで止めてよい フェーズ5Fでは、ここまでで十分です。 今回できるようになることは、次です。 ```text 物理ボタンで棚を選べる LEDの点滅で棚番号を確認できる 棚ごとのRSSリストからプレイリストを作れる 選んだ棚をmpvで再生し直せる ``` これはかなり大きな進歩です。 フェーズ5Eまでは、棚はまだ「コマンドで指定するもの」でした。 フェーズ5Fで、棚は「手元のボタンで選ぶもの」になります。 ここから一気にラジオ感が出ます。 次のフェーズ5Gでは、5つ目のボタンとLED2を追加して、再生モードを選べるようにします。 候補は、まず次の3つで十分です。 ```text 1:最新順 2:今日 3:3日前から古い順 ``` 「7日前から古い順」や「未聴っぽいもの」は、そのあとで大丈夫です。 ここで焦らないのが勝ち筋です。#声日記 #TanaRadioPi LISTENで開く
Embed this episode
NOW PLAYING
376 日記 | 棚選択ボタンとLEDランプを追加
No transcript for this episode yet
Similar Episodes
No similar episodes found.
Similar Podcasts
No similar podcasts found.