Ressusciter un téléphone à cadran S63 avec un RaspberryPi et NodeJS
MIT License
Le software c'est bien, mais coupl du hardware c'est encore plus fun !
Ma passion pour le dtournement d'objets prend de plus en plus de place, et je vais vous montrer qu'il est assez facile de dtourner les objets de notre enfance pour en faire... des oeuvres d'art ! (ou pas)
Mes connaissances en lctronique sont trs limites donc n'hsitez pas me corriger et faire vos suggestions par email : [email protected] ou twitter : @revolunet.
Intressons-nous au mythique tlphone SOCOTEL S63, conu au debut des annes 60, qui a t norme un succs commercial (des dizaines de millions d'exemplaires vendus) et reste un petit bijou de l'industrie de cette poque, d'une conception simple mais trs fonctionnelle et d'une durabilit impressionnante. On peut le retrouver un peu partout dans le monde et dans les brocantes en trs bon tat pour moins de 10.
Un autre avantage de ce modle est que l'on peut y loger un RaspberryPi complet + ses modules sans problmes.
Vous pouvez retrouver l'ensemble du code et du matriel sur GitHub : http://github.com/revolunet/s63
L'objectif est de prendre contrle des organes du tlphone qui nous intressent, et de les connecter un RaspberryPi qui vous permettra de programmer vos dlires les plus fous. Dans cet exemple, on va simplement associer certains numros composs un fichier audio, mais de nombreuses applications sont possibles !
Voici un schema du circuit du S63 :
Ce qu'on peut voir sur ce schema :
Pour plus de dtails vous pouvez consulter ces sites : alain.levasseur et jla1313
Le Raspberry est un mini-ordinateur sous linux, 35, qui permet de connecter des input/output physiques. c'est lui qui va couter les venements du tlphon (dcroch, composition...) et dclencher des actions : faire sonner, diffuser un son...
Il est galement capable de se connecter Internet, par RJ45, ou WiFI sur la dernire version (3B)
Ce cadran rotatif est une jolie pice de mcanique, qui convertit la rotation du cadran en un nombre d'impulsions correspondant, comme on peut le voir sur ces images (au ralenti), en bas gauche. Il nous suffit alors de compter le nombre d'impulsions pour connaitre le numro compos :)
Les cbles qui nous intressent sont donc les deux gauche : le rouge et le "rouge altern". Le bleu et le blanc servent informer de l'activit du cadran : ds qu'on le tourne il laisse passer le courant et ds qu'il est de retour en position normal il se ferme. (merci @Tekkharibo)
Nous brancherons ces deux cables directement aux pins 9 (GND) et 11 (GPIO17) du RaspberryPi, lequel coutera le nombre d'impulsions pour en dduire le numro compos.
Le combin du S63 est un trs classique haut-parleur + micro, que nous allons pouvoir brancher directement sur la sortie audio du RaspberryPi. L'ecouteur secondaire, un autre charme du S63, sera branch en parallle.
Par chance, l'audio du RaspberryPi est juste assez puissant pour qu'on entende bien, donc pas besoin d'amplificateur audio.
La sonnerie du S63 est dclenche par un moteur qui frappe alternativement sur 2 cloches la frquence de 50Hz.
Il suffirait d'alimenter ce moteur avec une tension alternative de 40 80V pour que cela sonne correctement. N'ayant pas trouv de moyen simple de rduire du 220V AC en 80V AC, j'utilise le 220V directement et ca fonctionne galement.
Ne manipulez jamais de courant 220V directement, risque d'lctrocution fatale !
Ne branchez par le courant lorsque le capot du tlphone est ouvert
Nous allons utliser un module relai qui sera command par le RaspberryPi pour activer/dsactiver le courant sur ce circuit. Le relai s'allumera et s'eteindra toutes les secondes jusqu'au dcroch.
L'alimentation de la sonnerie se fait par les pins 13 et 17 du S63, que nous connecterons via la relai aux pins 7 (GPIO4), 6 (GND) et 4(VCC) du RaspberryPi.
Lorsque l'on pose ou dcroche le combin, un interrupteur dclenche la fermeture du circuit de sonnerie.
Cet interrupteur arrive sur les pins 7 et 11 du S63 qui seront relis aux pins 39 (GND) et 40 (GPIO21) du RaspberryPi.
Voici un schema (naf) du montage final, ralis avec Fritzing
Raspberry Physical Pin | target | About |
---|---|---|
9 | rotary red | rotary pulse |
11 | rotary red/white | rotary pulse GND |
4 | RELAY VCC | alim relai sonnerie |
7 | RELAY R1 | active sonnerie |
5 | RELAY R2 | inutilis |
6 | RELAY GND | relai sonnerie GND |
40 | S63 7 | interr dcroch |
39 | S63 11 | interr dcroch GND |
Il n'y a plus que 4 cables connects sur le header du tlphone, les autres sont directement relis au RaspberryPi.
Et voil pour la partie hardware !
Il ne reste plus qu' orchestrer tout a avec un peu de JavaScript.
Maintenant que nous avons branch tous les organes au RaspberryPi, il ne nous reste plus qu' programmer le "cerveau" de notre tlphone, qui sera charg de ragir en fonction de ce que l'on fait sur le tlphone.
Commenons par dfinir exactement ce que nous voulons faire (les "specs") :
Pour orchestrer tout cela, nous allons utiliser l'excellent framework open-source johnny-five qui permet de contrller de nombreuses cartes hardware avec une API simple et haut-niveau.
Je vais dtailler une partie du code mais le projet complet est disponible sur GitHub : http://github.com/revolunet/s63
La premire chose faire est d'installer un systme linux sur la RaspberryPi. Tlchargez Raspbian Lite, installez-le sur une carte SD, puis installez NVM et johnny-five. Plus d'infos sur ma cheatsheet RaspberryPi
Johnny-five permet de grer les boards (Arduino, RaspberryPi...) mais aussi tous les modules/composants que l'on peut y connecter (relais, boutons, leds...). Trs bien document, ce framework permet de grer facilement la plupart des composants, et de le combiner avec les dizaines de milliers de librairies open-source disponibles dans l'ecosystme JavaScript/npm.
Exemple de code minimal :
// on importe les dpendances
const five = require("johnny-five");
const Raspi = require("raspi-io");
// on dclare la board
const board = new five.Board({ io: new Raspi() });
// Une fois la carte prte
board.on("ready", function() {
// configuration du button de raccrochage du combin
var hangupButton = new five.Button({
pin: "GPIO21",
isPullup: true,
holdtime: 10
});
// dclenchement lorsque l'on dcroche
hangupButton.on("up", function() {
board.info("Phone", "PICK UP");
});
// dclenchement lorsque l'on raccroche
hangupButton.on("down", function() {
board.info("Phone", "HANG UP");
});
});
Pour lancer ce code depuis la RaspberryPi, executez node index.js
dans le terminal.
Le script doit se lancer et lorsque vous dcrochez ou raccrochez le combin vous verrez apparaitre les messages de log dans le terminal.
NodeJS dispose de tout ce qu'il faut pour traiter de l'audio, et nous allons utiliser :
Nous allons donc produire de l'audio en NodeJS, lequel sera envoy dans le combin du tlphone via la sortie audio du RaspberryPi.
var Speaker = require("node-speaker");
var Oscillator = require("audio-oscillator");
var speaker = new Speaker();
return Oscillator({
frequency: 440,
detune: 0,
type: "sine",
normalize: true
}).pipe(speaker);
Ceci cre le son de la porteuse en La 440Hz et l'envoie directement sur l'audio avec pipe
.
Donc lorsqu'on dcrochera le tlphone, on recevra l'vnement hangupButton.on("up")
, et il faudra dclencher cette fonction pour entendre la porteuse dans le combin.
var fs = require("fs");
var lame = require("lame");
var Speaker = require("node-speaker");
var speaker = new Speaker();
var decoder = new lame.Decoder();
fs.createReadStream("/path/to/file.mp3").pipe(decoder).pipe(speaker);
Ceci permet de lire un fichier mp3, de le dcoder, et de l'envoyer directement sur l'audio avec pipe
.
Lorsque l'utilisateur composera un numro, on enverra simplement le fichier mp3 correspondant.
Comme expliqu plus haut, le cadran met autant d'impulsions que le numro compos. Il suffit donc de les compter.
A chaque numro compos, on attends 500ms voir si l'utilisateur compose un autre numro et sinon on dcide qu'il a termin.
Nous pouvons reprsenter notre Rotary sous forme de classe, qui gerera la logique de compte et temporisation des impulsions et enverra un event compositionend
lorsque la composition sera termine.
Voici un exemple d'implmentation (Rotary.js) :
var EventEmitter = require('events').EventEmitter;
// temps max entre deux nombres
const PULSE_TIMEOUT = 500;
// temps avant de dclencher l'venement final avec le numro complet
const COMPOSE_TIMEOUT = 2000;
const PULSE_VALUES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
class Rotary extends EventEmitter {
constructor() {
super()
this.value = '';
this.pulseCount = 0;
}
onPulse() {
// reception d'une impulsion
if (this.pulseCount === 0) {
this.emit('compositionstart');
}
this.pulseCount++;
if (this.pulseTimeout) {
clearTimeout(this.pulseTimeout);
}
if (this.composeTimeout) {
clearTimeout(this.composeTimeout);
}
this.pulseTimeout = setTimeout(this.onPulseTimeout.bind(this), PULSE_TIMEOUT)
}
onPulseTimeout() {
// last pulse received - store number
const num = PULSE_VALUES[this.pulseCount - 1];
this.value += num;
this.pulseCount = 0;
this.composeTimeout = setTimeout(this.onComposeTimeout.bind(this), COMPOSE_TIMEOUT)
}
onComposeTimeout() {
// composition timeout
this.emit('compositionend', this.value);
this.value = '';
this.pulseCount = 0;
}
}
module.exports = Rotary
Nous allons pouvoir utiliser cette classe dans notre programme principal :
var Rotary = require("./Rotary");
// creation d'une instance de notre classe
const rotary = new Rotary();
// declaration d'un bouton qui recevra les impulsions
var rotaryButton = new five.Button({
pin: "GPIO17",
isPullup: true,
holdtime: 10,
invert: true
});
// lorsqu'on detecte une impulsion, on envoie l'info notre instance de Rotary
rotaryButton.on("up", () => rotary.onPulse());
// lorsque le rotary nous indique que la composition est termine (timeout > 2000ms)
rotary.on("compositionend", number => {
board.info("Rotary", `COMPOSE ${number}`);
// on rcupre le "number" entier
// -> dclenchement du son en fonction du number
})
Il suffit maintenant de choisir le bon son en fonction du numro compos.
J'ai choisi de me baser sur le FileSystem pour dfinir les numros possibles et les sons envoyer; Comme a, pas besoin de modifier le code pour faire voluer le plan d'appel.
Dans un dossier "plan" (comme "dial plan") nous allons crer des sous-dossiers pour chaque numro possible. Notre code ira alors executer le fichier index.js
s'il est prsent, ou, dfaut, il ira choisir au hasard un des sons du dossier qui sera envoy sur le haut-parleur.
Voici un extrait du code en question :
// verifies si le dossier existe, sinon utilise le dossier "default"
const numberPath = (SOUNDS[number] && number) || "default";
const sounds = SOUNDS[numberPath];
// chemin vers un ventuel index.js
const modulePath = path.join(PLAN_PATH, number, 'index.js');
// execution du fichier index.js si prsent
if (fs.existsSync(modulePath) && fs.statSync(modulePath).isFile()) {
board.info("Phone", `detected index.js, execute`);
const numberModule = require(modulePath)
numberModule({
playText: playText,
playSilence: playSilence,
playStream: playStream,
playSine: playSine,
hangupButton$: hangupButton$
})
} else {
// comportement par dfaut : choisit un des mp3 au hasard dans le dossier
const pickedSound = pickRandom(sounds);
const sound = numberPath + "/" + pickedSound;
if (pickedSound) {
board.info("Phone", `START Play ${sound}`);
// envoie le son
playLocalSound(sound);
} else {
board.error("Phone", `CANNOT Play sound ${sound}`);
}
}
Tout le code est disponible sur GitHub : http://github.com/revolunet/s63
Pour jour un fichier mp3 c'est simple, il suffit comme vu plus-haut de l'envoyer sur node-speaker
.
Pour le cas de la mto, des blagues ou des infos, c'est un petit peu plus compliqu : il va falloir d'abord crer le fichier audio partir de texte. Pour cela, on va d'abord rcuprer des infos mto exploitables via une API, par exemple avec https://github.com/revolunet/infoclimat puis les transformer en audio avec du Text-To-Speech, par exemple avec l'API google translate.
Voici un extrait du code en question :
const getUrlStream = url => fetch(url).then(res => res.body).catch(e => console.log(e));
const getTTSStream = text => getUrlStream(`http://translate.google.com/translate_tts?tl=fr&q=${encodeURIComponent(text)}&client=gtx&ie=UTF-8`);
infoclimat.getNextWeatherInFrench("48.856578,2.351828").then(text => {
getTTSStream(text).pipe(speaker);
})
Avec a vous pouvez donc crer la vole de l'audio partir de texte !
Exemples : meteo blagues ou infos
Il nous faut pour cela activer/dsactiver le relai qui laisse passer le courant (220v ) dans le circuit.
Le relai est branch en mode "Normalement Ouvert" (NO) et branch comme suit au RaspberryPi :
Il faudra un peu plus de logique pour grer l'alternance et le dcroch mais voici le fonctionnement basique :
var ringRelay = new five.Relay({
pin: "GPIO4",
type: "N0"
});
// ring
ringRelay.close();
// stop after 1000ms
setTimeout(ringRelay.open, 1000);
Voici un exemple d'implmentation de la logique de sonnerie avec xstream
NB : je cherche quelqu'un pour m'aider ce sujet sur xstream/rxjs/whateverRx :)
var xs = require('xstream').default;
var fromEvent = require('xstream/extra/fromEvent').default;
var delay = require('xstream/extra/delay').default;
const RING_INTERVAL = 1000;
const RING_TIMEOUT = 5000;
// pickup events stream
var pickup$ = fromEvent(hangupButton, 'up').mapTo('pickup');
// hangups events stream. drop the first one on program start;
var hangup$ = fromEvent(hangupButton, 'down').mapTo('hangup').drop(1);
// stream periodically until pickup or timeout
var ring$ = xs.periodic(RING_INTERVAL).startWith(0).endWhen(pickup$).endWhen(xs.periodic(RING_TIMEOUT).take(1));
Pour lancer le programme NodeJs au dmarrage du RaspberryPi, je vous conseille d'utiliser pm2.
Il est facile de recycler d'anciens appareils et les incorporer dans vos montages; Procdez tape par tape pour comprendre et prendre le contrle de chaque composant.
johnny-five est un outil de choix pour les makers, qui simplifie l'exploitation du RaspberryPi et permet d'accder au formidable cosystme JavaScript qui dispose dj de tous les outils ncessaires pour vos projets (audio, video, hardware, web...) et est parfait pour la gestion de l'asynchrone dans vos applications.