diff --git a/_config.yml b/_config.yml index af8aa44..5514b8f 100644 --- a/_config.yml +++ b/_config.yml @@ -11,14 +11,14 @@ markdown: redcarpet highlighter: rouge paginate: 20 -domain_name: 'http://quentin.dufour.io' +domain_name: 'https://quentin.dufour.io' # Details for the RSS feed generator -url: 'http://quentin.dufour.io' +url: 'https://quentin.dufour.io' author: 'Quentin Dufour' authorTwitter: 'superboum' -permalink: /blog/:year-:month-:day/:title +permalink: /blog/:year-:month-:day/:title/ defaults: - diff --git a/_includes/navigation.html b/_includes/navigation.html index acd4a51..797d102 100644 --- a/_includes/navigation.html +++ b/_includes/navigation.html @@ -1,12 +1,15 @@ diff --git a/_posts/2019-02-10-manette-xbox-reparation.md b/_posts/2019-02-10-manette-xbox-reparation.md new file mode 100644 index 0000000..7d4c6cb --- /dev/null +++ b/_posts/2019-02-10-manette-xbox-reparation.md @@ -0,0 +1,48 @@ +--- +layout: post +slug: manette-xbox-reparation +status: published +sitemap: true +title: Réparation d'une manette Xbox One +description: La panne la plus bête du monde +categories: +- bricolage +tags: +--- + +Depuis maintenant plusieurs mois, j'ai perdu la gachette gauche de ma manette Xbox One, ma seule manette de jeu. +Ce qui est plutôt embêtant, car elle me rend de fiers services quand je joue à Trackmania et plus récemment à Firewatch. +Et pas de chance, bien que la peur n'évite pas le danger, le frein sur la gachette gauche évite les murs sur Trackmania. + +[![Panneau Trackmania "La peur n'évite pas le danger"](/assets/images/posts/xbox-controller-tm.jpg)](/assets/images/posts/xbox-controller-tm.jpg) + +Pas mieux du côté de Firewatch, car il s'agit du talkie walkie, un élément central du jeu. Sans grand espoir, je me suis donc lancé dans la réparation de cette manette, qui n'en est pas à sa première défaillance. En effet, j'ai dû jeter le cable micro-usb à usb fourni avec car il ne fonctionnait plus. + +## Dissection de la manette + +iFixit, comme toujours, propose [un guide](https://fr.ifixit.com/Vue+%C3%89clat%C3%A9e/Xbox+One+Wireless+Controller+Teardown/72986) très pratique pour démonter la manette, que j'ai suivi scrupuleusement. + +En démontant la manette, je me suis rendu compte qu'il manquait une vis à un endroit. Simple oubli ou début de réponse ? + +[![Vis manquante](/assets/images/posts/xbox-controller-td1.jpg)](/assets/images/posts/xbox-controller-td1.jpg) + +Une fois le démontage fini, je me retrouve avec les différents circuits de ma manette. + +[![Circuit électronique manette xbox](/assets/images/posts/xbox-controller-td2.jpg)](/assets/images/posts/xbox-controller-td2.jpg) + +En regardant un peu mieux, je me rends compte qu'une vis est aimantée sur la gachette. +En effet, le bout de la gachette possède un aimant relativement puissant. +Même en agitant la manette dans tous les sens, la vis ne risquait pas de se désaimanter. +Vis qui provient probablement de notre emplacement vide précédent... + +[![La vis perdue est aimantée sur une gachette !](/assets/images/posts/xbox-controller-td3.jpg)](/assets/images/posts/xbox-controller-td3.jpg) + +Une fois enlevée, un rapide test permet de vérifier que ma gachette gauche fonctionne de nouveau + +## Conclusion + +En remontant les vis, je me suis rendu compte qu'une des vis tournait plus ou moins dans le vide. +Je pense que le taraudage d'une pièce doit être cassé. La vis avait l'air de bien tenir pour l'instant, je l'ai donc laissée. +Mais si elle venait à se promener de nouveau, un peu de colle pourrait résoudre le problème. Ou simplement l'enlever... +Bref, une réparation plus simple que prévue, c'est suffisament rare pour être signalé ! Essayez de réparer vos objets, +parfois ça vaut le coup ! diff --git a/_posts/2019-05-31-installer-cura-4.md b/_posts/2019-05-31-installer-cura-4.md new file mode 100644 index 0000000..ba494db --- /dev/null +++ b/_posts/2019-05-31-installer-cura-4.md @@ -0,0 +1,48 @@ +--- +layout: post +slug: alfawise-u30-et-fedora-linux +status: published +sitemap: true +title: Utiliser une Alfawise U30 depuis Fedora +description: Pour des impressions 3D libres +categories: +- bricolage +tags: +--- + +L'Alfawise U30 n'a été intégrée que récemment dans Cura et n'est disponible que depuis le dépôt. +Qui plus est, la dernière version majeur de Cura, la version 4, est encore en beta et n'est pas non plus disponible dans Fedora stable. + +Nous allons donc installer Cura 4 beta depuis Fedora Rawhide (unstable) puis télécharger les fichiers de définition de l'Alfawise U30 depuis le dépôt officiel. +Une fois le code actuel du dépot publié dans une version stable, ce guide sera inutile. + +## Installer Cura 4 + +Nous allons tout d'abord commencer par installer un paquet qui permet d'activer les dépôts instables (mais seulement sur demande) : + +```bash +sudo dnf install fedora-repos-rawhide +``` + +Ensuite installons Cura depuis les dépôts instables. + +```bash +sudo dnf install --nogpg --enablerepo=rawhide cura +``` + +**Il semble y avoir un bug avec les clés PGP qui ne sont pas correctement installés pour le dépôt instable, j'ai donc désactivé la vérification juste pour cette commande. +Cela reste une très mauvaise idée et corriger durablement ce bug en important les clés PGP serait une meilleur solution.** + +## Installer les définitions de l'Alfawise U30 + +Les configurations pour les imprimantes 3D sont contenues dans des fichiers JSON. +Nous allons donc les télécharger au bon endroit, tout simplement : + +```bash +cd /usr/share/cura/resources/definitions/ +sudo wget https://raw.githubusercontent.com/Ultimaker/Cura/137619567a1d68139444f0fea76a022d63d86a0b/resources/definitions/alfawise_u30.def.json +cd /usr/share/cura/resources/extruders +sudo wget https://raw.githubusercontent.com/Ultimaker/Cura/057c30f86e86635721e8a269d864e1597699c834/resources/extruders/alfawise_u30_extruder_0.def.json +``` + +Et voilà, ce devrait être bon, bonne impression ! diff --git a/_posts/2019-08-06-protocole-shoutcast.md b/_posts/2019-08-06-protocole-shoutcast.md new file mode 100644 index 0000000..fdf87e0 --- /dev/null +++ b/_posts/2019-08-06-protocole-shoutcast.md @@ -0,0 +1,183 @@ +--- +layout: post +slug: le-protocole-shoutcast-au-scalpel +status: published +sitemap: true +title: Le protocole Shoutcast au scalpel +description: Découpage de flux HTTP +categories: +- network +tags: +--- + +Shoutcast est un protocole de diffusion pour webradio créé par Nullsoft, l'éditeur de Winamp entre autre. Il repose sur des technologies bien établies car au final il s'agit principalement de diffuser un fichier en MP3 en continu à travers le protocole HTTP. Il est donc possible d'écouter simplement une radio diffusé via Shoutcast en copiant son lien dans le navigateur. Nous allons prendre pour exemple ce flux : + +``` +http://streaming.radionti.com:80/nti-320.mp3 +``` + +Alors qu'apporte Shoutcast de plus ? Tout d'abord il standardise des en-têtes HTTP, ce qui est bien pratique car l'on peut écrire un lecteur qui fonctionnera avec plein de flux. Mais regardons de plus prêt ces en-têtes : + +``` +$ curl -vvv http://streaming.radionti.com:80/nti-320.mp3 +* Trying 51.15.166.151... +* TCP_NODELAY set +* Connected to streaming.radionti.com (51.15.166.151) port 80 (#0) +> GET /nti-320.mp3 HTTP/1.1 +> Host: streaming.radionti.com +> User-Agent: curl/7.64.0 +> Accept: */* +> +* HTTP 1.0, assume close after body +< HTTP/1.0 200 OK +< Server: Icecast 2.4.2 +< Date: Tue, 19 Dec 2017 21:45:23 GMT +< Content-Type: audio/mpeg +< Cache-Control: no-cache +< Expires: Mon, 26 Jul 1997 05:00:00 GMT +< Pragma: no-cache +< Access-Control-Allow-Origin: * +< icy-br:320 +< icy-description:NTI - LA NOUVELLE TENDANCE (FRANCE) +< icy-genre:DANCE & EDM +< icy-name:NTI - LA NOUVELLE TENDANCE (FRANCE) +< icy-pub:0 +< icy-url:http://www.radionti.com +``` + +On reconnait facilement les en-têtes définies par le protocole Shoutcast car elles sont préfixées par `icy-`. On peut retrouver des informations sur la radio, mais pas le titre de la musique qui est en train d'être joué. Et pour comprendre pourquoi, il faut se plonger un peu dans HTTP. + +## Le streaming HTTP + +On commence rarement par aborder HTTP à travers le streaming. Généralement, on fait une requête HTTP, on attend de recevoir la réponse dans sa totalité, si possible dans une magnifique chaine de caractère, on ferme la connexion HTTP et seulement ensuite on traite l'information. + +Oui, mais là on veut diffuser de la musique en continu ! Alors on pourrait envoyer des morceaux de musique de quelques secondes par requete et répéter le processus suivant en boucle. Mais ouvrir et fermer plein de connexion est couteux, peu optimisé et introduirait un décalage peu utile. + +Nous allons donc utiliser une seule connexion qui ne se fermera jamais, qui va nous envoyer de la donnée en continue, au fur et à mesure qu'elle arrive. Ce qui veut également dire que l'on devra gérer en même temps la connexion et le player audio, faire un petit peu de chaque. + +Mais vu que l'on ouvre la connexion qu'une seule fois, les en-têtes ne seront envoyées qu'une seule fois ! Et une fois que l'on a commencé à transférer des données, impossible d'envoyer de nouvelles en-têtes ! + +Alors comment fait-on ? Dans les données que le serveur Shoutcast va envoyer, se trouvera un mélange de flux audio et de méta donnée. Ce sera alors au client de séparer les deux et de rédiger le flux audio vers le lecteur audio et les méta données vers l'interface utilisateur. + +## Icy Metadata + +Par défaut et pour des raisons de compatibilité, seul le flux audio est envoyé. Pour obtenir les méta données en plus, il est nécessaire de le demander explicitement au moment où l'on réalise la requête. En échange, le serveur va nous fournir une nouvelle en-tête `icy-metaint` qui nous informera à quelle fréquence les méta données seront envoyées. Plus exactement, tous les combiens d'octets de musique envoyés se trouveront ces méta données. + +Une fois arrivée aux métadonnées, on commence par lire un octet. En le multipliant par 16, on peut en déduire la taille totale des métadonnées à lire. Une fois ces méta données lues, on recommence à compter `icy-metaint` octets, etc. + +Tout ça peut paraitre très abstrait, pourtant avec quelques lignes de python et des streams on peut s'en sortir sans trop de mal ! + +## Python, Streams et découpage de webradios + +Nous allons commencer avec ce squelette qui nous est un peu imposé par asyncio: + +```python +import asyncio + +async def icy(): + pass + +asyncio.run(icy()) +``` + +Nous n'utiliserons pas de bibliothèque HTTP mais une simple connexion TCP pour bien comprendre comment ça se passe dans les niveaux en dessous. Nous utiliserons également les objets [Streams de Python](https://docs.python.org/3/library/asyncio-stream.html) qui semblent appropriés pour résoudre notre problème. + +Nous allons commencer par nous connecter au serveur et lui demander le stream qui nous intéresse : + +```python +async def icy(): + reader, writer = await asyncio.open_connection('streaming.radionti.com', 80) + + writer.write(b"""GET /nti-320.mp3 HTTP/1.0 +Host: streaming.radionti.com +User-Agent: Icy-Test +Accept: */* +Icy-Metadata: 1 + +""") +``` + +On commence par ouvrire une connexion TCP vers l'URL et le port du serveur. +Ensuite, on utilise l'objet `writer` pour envoyer notre requête HTTP. +Pour rappel, HTTP est un protocole texte. La première ligne permet d'indiquer le verbe HTTP, la page ainsi que la version du protocole. Les lignes suivantes sont des en-tête au format clé valeur, une par ligne, séparées par deux points. + +Nous avons justement fait attention à préciser l'entête `Icy-Metadata: 1` pour demander un flux audio mélangé avec des métadonnées (sinon, nous n'aurions eu que le flux audio sans les métadonnées !). + +La ligne vide indique la fin de l'envoie des en-têtes. +Puisque nous envoyons une requête GET, nous n'avons pas de données à envoyer. +Le serveur sait alors qu'il peut commencer à générer la réponse. + +Nous allons donc pouvoir nous préparer à analyser la réponse que le serveur va nous faire. +Mais permettons-nous d'écrire une petite fonction utilitaire pour extraire les en-têtes renvoyées par le serveur : + +```python +async def readHeaders(reader): + status_code = await reader.readline() + assert (status_code, b'HTTP/1.0 200 OK\r\n') + + headers = {} + while True: + data = await reader.readline() + if data == b'\r\n': + print("End of metadata part, all key/value headers have been read") + return headers + header_name, header_value = data.split(b':', 1) + headers[header_name] = header_value +``` + +Tout d'abord, la première ligne est un peu particulière. On vérifie que le protocole correspond, que le code de status est bien 200 (qui veut dire OK, tout s'est bien passé). +Ensuite, nous récuperons les en-têtes envoyées par le serveur sous le même format que celles que nous avons envoyées ! Une ligne vide indique également la fin des en-têtes, et dans notre cas le début du contenu que nous avons demandé ! + +Armés de cette fonction, complétons notre fonction `icy` pour les récupérer, et surtout récupérer l'en-tête qui nous intéresse, `icy-metaint` qui nous indiquera comment découper notre flux ! + +```python +async def icy(): + # ... + + headers = await readHeaders(reader) + metaint = int(headers[b'icy-metaint']) +``` + +Nous avons donc stocké dans la variable `metaint` le nombre d'octets d'audio à lire dans le flux envoyé par notre serveur Shoutcast. + +Maintenant que nous avons toutes les informations dont nous avons besoin, récupérons ce flux et découpons le ! + +```python +async def icy(): + # ... + + while True: + audio = await reader.readexactly(metaint) + metadata_size_raw = await reader.readexactly(1) + metadata_size_bytes = \ + 16 * int.from_bytes(metadata_size_raw, "big") + metadata_content = \ + await reader.readexactly(metadata_size) + if metadata_size > 0: + print(metadata_content) +``` + +Nous y voilà ! On récupère exactement le nombre de bytes indiqués par l'en-tête. Puis nous lisons un octet, qui va nous permettre de calculer la taille des métadonnées, nous allons lire exactement ce nombre d'octets. Les méta données sont du simple texte, donc on les affiche. Ici, on ne fait rien avec l'audio, mais il faudrait écrire le contenu de audio dans notre lecteur. Ce dernier fournirait probablement un Stream dans lequel on pourrait écrire le contenu de la variable. Il suffit ensuite de répéter cette action en boucle. + +**Attention ! Les métadonnées seront la plupart du temps vides. En effet, pas besoin de renvoyer le titre de la chanson quand il n'a pas changé. Donc il sera envoyé uniquement au lancement du stream puis à chaque changement de chanson. Le reste du temps, les métadonnées indiqueront que leur taille est de zéro.** + +Et voilà la sortie de notre application : + +``` +$ python3 /tmp/icy.py +End of metadata part, all key/value headers have been read +b"StreamTitle='SOPHIE ELLIS BEXTOR - MURDER ON THE DANCEFLOOR - 2002';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='QUINTINO - CAN'T BRING ME DOWN - 2019';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='DIRTY VEGAS - WHY DID YOU DO IT 2K19';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='FMZ - GET DOWN TO THIS - 2010';\x00\x00\x00\x00" +b"StreamTitle='MOSIMANN + MARUV - MON AMOUR - 2019';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='DUA LIPA - SWAN SONG (REMIX) 2019';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='MILK & SUGAR - LOVE IS IN THE AIR 2K19';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='PAPA ZEUS - ABOUT YOU - 2019';\x00\x00\x00\x00\x00" +b"StreamTitle='DON DIABLO - BRAVE - 2019';\x00\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='MARTIN GARRIX - SUMMER DAYS - 2019';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='DEORRO - FIVE HOURS - 2014';\x00\x00\x00\x00\x00\x00\x00" +b"StreamTitle='INTERNATIONAL DRINKING PARTY - DESOLEE - 2017';\x00\x00\x00\x00" +``` + +Le code complet de l'exemple se trouve dans ce fichier [icy.py](/assets/code/icy.py). diff --git a/assets/code/icy.py b/assets/code/icy.py new file mode 100644 index 0000000..f89696f --- /dev/null +++ b/assets/code/icy.py @@ -0,0 +1,37 @@ +import asyncio + +async def readHeaders(reader): + status_code = await reader.readline() + assert status_code, b'HTTP/1.0 200 OK\r\n' + + headers = {} + while True: + data = await reader.readline() + if data == b'\r\n': + print("End of metadata part, all key/value headers have been read") + return headers + header_name, header_value = data.split(b':', 1) + headers[header_name] = header_value + + +async def icy(): + reader, writer = await asyncio.open_connection('streaming.radionti.com', 80) + + writer.write(b"""GET /nti-320.mp3 HTTP/1.0 +Host: streaming.radionti.com +User-Agent: Icy-Test +Accept: */* +Icy-Metadata: 1 + +""") + + headers = await readHeaders(reader) + metaint = int(headers[b'icy-metaint']) + + while True: + audio = await reader.readexactly(metaint) + metadata_size = int.from_bytes(await reader.readexactly(1), "big") * 16 + metadata_content = await reader.readexactly(metadata_size) + if metadata_size > 0: print(metadata_content) + +asyncio.run(icy()) diff --git a/assets/images/posts/xbox-controller-td1.jpg b/assets/images/posts/xbox-controller-td1.jpg new file mode 100644 index 0000000..69ae814 Binary files /dev/null and b/assets/images/posts/xbox-controller-td1.jpg differ diff --git a/assets/images/posts/xbox-controller-td2.jpg b/assets/images/posts/xbox-controller-td2.jpg new file mode 100644 index 0000000..e4187bd Binary files /dev/null and b/assets/images/posts/xbox-controller-td2.jpg differ diff --git a/assets/images/posts/xbox-controller-td3.jpg b/assets/images/posts/xbox-controller-td3.jpg new file mode 100644 index 0000000..5fe9978 Binary files /dev/null and b/assets/images/posts/xbox-controller-td3.jpg differ diff --git a/assets/images/posts/xbox-controller-tm.jpg b/assets/images/posts/xbox-controller-tm.jpg new file mode 100644 index 0000000..2dad1c5 Binary files /dev/null and b/assets/images/posts/xbox-controller-tm.jpg differ diff --git a/assets/images/posts/xbox-controller-tm.png b/assets/images/posts/xbox-controller-tm.png new file mode 100644 index 0000000..b69be44 Binary files /dev/null and b/assets/images/posts/xbox-controller-tm.png differ diff --git a/cours/index.md b/cours/index.md new file mode 100644 index 0000000..f42235d --- /dev/null +++ b/cours/index.md @@ -0,0 +1,25 @@ +--- +title: Cours +profile: false +--- + +### Développement logiciel pour le Cloud (TLC) + +Slides: + + * [Slides 1: Introduction to cloud computing](tlc-course-1.pdf) + * [Slides 2: Microservices](tlc-course-2.pdf) + * [Slides 3: Analytics](tlc-course-3.pdf) + * [Slides 4: Data Management](tlc-course-4.pdf) + * [Slide 4.1 : DHT (Pastry)](tlc-course-4.1.pdf) + * [Slide 4.2 : NoSQL (Dynamo DB)](tlc-course-4.2.pdf) + +Travaux dirigés : + + * [TD1 : Base de données NoSQL : Google Datastore](td-datastore.pdf) + +Lab: + + * [Lab 1: Découverte d'un PaaS : Google AppEngine](tlc-tp1.pdf) + * [Lab 2: Découverte d'un orchestrateur de conteneurs : Kubernetes](tlc-tp2.pdf) + * [Lab 3: Découverte d'un IaaS : AWS EC2](tlc-tp3.pdf) diff --git a/cours/td-datastore.pdf b/cours/td-datastore.pdf new file mode 100644 index 0000000..16bca5f Binary files /dev/null and b/cours/td-datastore.pdf differ diff --git a/cours/tlc-course-1.pdf b/cours/tlc-course-1.pdf new file mode 100644 index 0000000..71f17a3 Binary files /dev/null and b/cours/tlc-course-1.pdf differ diff --git a/cours/tlc-course-2.pdf b/cours/tlc-course-2.pdf new file mode 100644 index 0000000..8eb489a Binary files /dev/null and b/cours/tlc-course-2.pdf differ diff --git a/cours/tlc-course-3.pdf b/cours/tlc-course-3.pdf new file mode 100644 index 0000000..6af5049 Binary files /dev/null and b/cours/tlc-course-3.pdf differ diff --git a/cours/tlc-course-4.1.pdf b/cours/tlc-course-4.1.pdf new file mode 100644 index 0000000..a0e4564 Binary files /dev/null and b/cours/tlc-course-4.1.pdf differ diff --git a/cours/tlc-course-4.2.pdf b/cours/tlc-course-4.2.pdf new file mode 100644 index 0000000..d086e71 Binary files /dev/null and b/cours/tlc-course-4.2.pdf differ diff --git a/cours/tlc-course-4.pdf b/cours/tlc-course-4.pdf new file mode 100644 index 0000000..4e57ea0 Binary files /dev/null and b/cours/tlc-course-4.pdf differ diff --git a/cours/tlc-tp1.pdf b/cours/tlc-tp1.pdf new file mode 100644 index 0000000..9182639 Binary files /dev/null and b/cours/tlc-tp1.pdf differ diff --git a/cours/tlc-tp2.pdf b/cours/tlc-tp2.pdf new file mode 100644 index 0000000..cf78d1b Binary files /dev/null and b/cours/tlc-tp2.pdf differ diff --git a/cours/tlc-tp3.pdf b/cours/tlc-tp3.pdf new file mode 100644 index 0000000..a2d5548 Binary files /dev/null and b/cours/tlc-tp3.pdf differ diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..e647a5f --- /dev/null +++ b/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /cours/