T-Watch 2020: TV-B-Gone

La dernière fois, nous avons vu comment obtenir un environnement pour la T-Watch2020 avec Micropython et LVGL. Cette fois, nous allons jouer avec l’emeteur infrarouge de la montre afin de voir s’il est possible d’en faire un TV-B-Gone.

Qu’est ce qu’un TV-B-Gone

Vu que Wikipedia l’écrit très bien sur la page dédiée au TV-B-Gone, nous allons reprendre sa présentation :

Le TV-B-Gone est une télécommande universelle dont l’unique fonction est d’éteindre et d’allumer les téléviseurs. Le concept a été réalisé en open source et est commercialisé par Mitch Altman, ingénieur dans la Silicon Valley. Il s’agit d’un détournement de la télécommande universelle : le TV-B-Gone parcourt les différentes fréquences utilisées par les différentes marques en émettant pour chacune d’entre elles le signal correspondant à la commande d’arrêt du téléviseur à l’aide d’une diode infrarouge. Cette télécommande a été pensée pour permettre d’éteindre les téléviseurs dans les lieux publics afin de ne plus subir en permanence la publicité ou les émissions télévisées, notamment dans les fast-foods, les magasins ou encore les files d’attente. Ce gadget provoque l’engouement des activistes anti-télévision ou antipub, qui organisent régulièrement des « raids » dans les magasins d’électroménager pour éteindre en quelques pressions des murs entiers de téléviseurs en démonstration.

Plusieurs implémentations existent pour Arduino, mais il ne semble pas y avoir d’existant pour du Micropython. Nous allons donc voir comment fonctionne une télécommande et comment on peut le réimplementer en Python.

Comment fonctionne l’émission infrarouge d’une télécommande

Pour faire simple une télécommande encode une valeur en signaux lumineux. Cet encodage n’est pas universel et plus ou moins chaque constructeur utilise le sien. Par exemple, SONY propose un encodage simple. Il définit tout d’abord un temps T à 600µs qui lui permet d’encoder différentes valeurs. Si il doit encoder un 1, alors il envoie un signal lumineux de 2T puis une absence de signal de 1T, soit 1200µs à l’état haut et 600µs à l’état bas. Si il doit encoder un zero, alors il envoie un signal lumineux de 1T et une absence de signal de 1T, soit 600µs à l’état haut et 600µs à l’état bas. Le schéma suivant résume l’encodage utilisé par SONY :

Schéma de cypress.com présentant l’encodage SONY

Comme on peut le voir sur le schéma, un signal “START” de 2400µs à l’état haut et 600µs à l’état bas est utilisé pour débuter la communication avant d’encoder un message.

Quasiment tous les protocoles utilisent ce fonctionnement, seules les temps hauts et bas changent. Afin de pouvoir faire quelque chose d’universel, nous n’allons pas prendre en compte chaques particularités des protocoles. Nous ne ferons qu’alterner des temps hauts et temps bas qui seront interpreté par le récepteur IR de notre télévision. C’est d’ailleurs ce fonctionnement que l’on retrouve sur tout un tas d’implémentation du TVBGone que ce soit sur Arduino ou d’autres plateformes.

Utiliser la diode infrarouge depuis Micropython

La diode infrarouge de la T-Watch est reliée à la Pin 13 de l’esp32. Du coup, nous pouvons facilement la piloter. Pour envoyer un signal brut, nous pouvons tester quelque chose comme ça :

from machine import Pin
from utime import sleep_ms

ir = Pin(13, Pin.OUTPUT)
raw_signal = {'freq': 38000, 'codes': [600, 600, 1200, 600]}
codes = raw_signal.get('codes')
for i in range(0, len(codes), 2):
    ir.on()
    sleep_ms(codes[i])
    ir.off()
    sleep_ms(codes[i+1])

C’est très basique, mais ça permet de tenter une implémentation en pure python. Après plusieurs essais, il s’est avéré qu’une latence assez importante s’est fait ressentir et que le signal n’était pas interpreté correctement. Du coup, j’ai modifié légérement la bibliothèque “ir”, développée en C, présente dans le firmware afin qu’elle soit en mesure d’envoyer des données brutes. Le fork est téléchargeable ici. Pour l’utiliser depuis Micropython c’est assez simple :

from ir import rmtlib_raw_send
raw_signal = {'freq': 38000, 'codes': [600, 600, 1200, 600]}
freq = raw_signal.get('freq')
codes = raw_signal.get('codes')
rmtlib_raw_send(freq, codes)

Après plusieurs tests, on est sur quelque chose de beaucoup plus concluant. J’ai donc créé une bibliothèque TVBGone que l’on va pouvoir utiliser depuis notre montre :

wget https://forge.tedomum.net/-/snippets/46/raw -O tvbgone.py
ampy -p /dev/ttyUSB0 put tvbgone.py

Depuis l’interpreteur python de votre montre, vous devriez pouvoir lancer votre TVBGone avec :

picocom /dev/ttyUSB0 -b115200
>> from tvbgone import TVBgone
>> TVBgone().start()

Créer une interface

Afin de rendre l’utilisation du TVBGone plus simple, nous allons créer une simple interface à l’aide de LVGL. Celle-ci est juste composée d’un bouton qui lance l’attaque lorsqu’on appuie dessus et qui affiche un spinner le temps du l’attaque.

Le code de l’interface est le suivant :

import lvgl as lv
import ttgo
import _thread
from IR import TVBgone


class Button:
    def __init__(self, app):
        self.btn = lv.btn(app)
        self.btn.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
        self.btn.set_event_cb(self.onevent)

        self.text_label = lv.label(self.btn)
        self.text_label.set_text("Click to start")

        self.spinner = lv.spinner(app)
        self.spinner.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
        self.spinner.set_hidden(True)

    def onevent(self, btn, event):
        if event == lv.EVENT.CLICKED:
            #self.text_label.set_text("In progress")
            #self.btn.set_state(self.btn.STATE.DISABLED)
            self.btn.set_hidden(True)
            self.spinner.set_hidden(False)
            _thread.start_new_thread(self.attack, ())
            
    def attack(self):
        TVBgone().start()
        self.spinner.set_hidden(True)
        self.btn.set_hidden(False)

# Init watch
watch = ttgo.Watch()

# Init lvgl
lv.init()
watch.lvgl_begin()

# Enable backlight
watch.tft.backlight_fade(100)

# Init interface
scr = lv.obj()
win = lv.win(scr)
win.set_title("TVBGone PoC")
Button(win)
lv.scr_load(scr)

Si tout se passe bien vous devriez avoir un résultat équivalent au mien: https://mastodon.tedomum.net/@y0no/104785810302905250

Nous avons donc un TV-B-Gone très élémentaire mais fonctionnel. Plusieurs améliorations peuvent être envisageable :

  • Possibilité d’arrêter l’attaque en cours
  • Selection du payload Europe ou US
  • Gestion des signaux plus exotiques

Ressources :

T-Watch 2020 : Micropython + LVGL

Lors de mon dernier article sur la twatch, je vous présentais comment installer MicroPython sur la TTGO T-Watch 2020. L’environnement déployé était plutôt “rustique” et ne permettait pas de profiter pleinement des fonctionnalités que propose la T-Watch. Depuis, les choses ont un peu bougé, et il est maintenant possible d’avoir un environnement bien plus développé avec notamment le support de LVGL pour avoir une interfaces graphiques qui dépote.

Qu’est ce que LVGL ?

LVGL est une bibliothèque permettant de facilement créer des interfaces graphiques pour de l’embarqué. Le tout semble plutôt léger et propose tout un tas de widget bien pratique. Par exemple voici le type de rendu que l’on peut avoir :

Exemple d’interface créée avec LVGL

Créer l’image Micropython avec LVGL

Contrairement au dernier article, nous n’allons pas partir du dépot Micropython officiel, mais d’une version modifiée. En effet, Nikita Selin a fait une grosse partie du boulot pour porter proprement la montre avec Micropython et notamment offrir une compatibilité avec LVGL. Nous allons donc partir de son dépôt.

Comme la dernière fois nous allons, préparer notre environnement avec les commandes suivantes :

mkdir twatch-micropython-lvgl && cd twatch-micropython-lvgl
# Téléchargement du code Micropython
git clone https://github.com/OPHoperHPO/lilygo-ttgo-twatch-2020-micropython micropyhon
# Téléchargement du framework de développement Espressif
git clone https://github.com/espressif/esp-idf.git
# Téléchargement de la toolchain ESP32 Xtensa
wget https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz
tar xzvf xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz

Maintenant que tout est téléchargé, Il suffit de se rendre dans le dossier lilygo-ttgo-twatch-2020-micropython puis de modifier le fichier makefile présent dans ports/esp32 afin d’y définir les chemins vers ESP-IDF et la toolchain ESP32 Xtensa. Cela evite de se pastiller les variables d’environnement à définir à chaque fois comme c’était le cas précédemment.

Une fois que c’est fait, on prépare l’environnement :

cd micropython
git submodule update --init
make -C mpy-cross

Maintenant, nous pouvons brancher la montre, compiler, puis déployer le firmware:

make -C ports/esp32 deploy

Tester le firmware

Maintenant que nous avons déployer notre firmware, nous allons pouvoir tester si LVGL fonctionne correctement avec le script suivant :

import lvgl as lv
import ttgo

# Init watch
watch = ttgo.Watch()

# Init lvgl
lv.init()
watch.lvgl_begin()

# Enable backlight
watch.tft.backlight_fade(100)


# Init lvgl
lv.init()
watch.lvgl_begin()

# Enable backlight
watch.tft.backlight_fade(100)

# Init interface
scr = lv.obj()
win = lv.win(scr)
win.set_title("TWatch + LVGL")
btn = lv.btn(win)
label = lv.btn(btn)
label.set_text("Hello world")
lv.scr_load(scr)

Si une interface s’affiche, c’est gagné! On va maintenant pouvoir s’amuser avec les différents composant de la montre. Pourquoi pas créer un TVBGone ou outils d’attaque Bluetooth ? ;)