s63

Ressusciter un téléphone à cadran S63 avec un RaspberryPi et NodeJS

MIT License

Stars
23

Ressusciter un tlphone cadran S63 avec un RaspberryPi et NodeJS

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

Objectifs

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 :

  • La partie audio ( gauche) est isole, srement pour les interfrences ?
  • Les pins 13 et 17 permettent d'alimenter la sonnerie
  • Les pins 7 et 11 permettent de se connecter l'interrupteur de dcrochage
  • Le cadran peut etre connect avec ses pins 1 et 2

Pour plus de dtails vous pouvez consulter ces sites : alain.levasseur et jla1313

RaspberryPi

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)

Le cadran

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

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

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.

L'interrupteur de dcroch

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.

Schema du montage

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.

Software

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") :

  • quand je dcroche, je veux entendre la fameuse porteuse (c'est par convention un La 440Hz)
  • en fonction du numro compos sur le cadran, je veux dclencher des sons particuliers :
    • 1 : mto du jour
    • 2 : une blague
    • 3 : les news
    • autre : un son au hasard
  • lorsque je raccroche, les sons doivent se couper

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

Prparation de la RaspberryPi

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.

Audio

NodeJS dispose de tout ce qu'il faut pour traiter de l'audio, et nous allons utiliser :

  • node-speaker qui permet d'envoyer un stream audio PCM sur votre carte son.
  • lame play des fichiers mp3 destination de node-speaker.
  • node-oscillator gnre des sons pour node-speaker (ex: notre porteuse).

Nous allons donc produire de l'audio en NodeJS, lequel sera envoy dans le combin du tlphone via la sortie audio du RaspberryPi.

Son de la porteuse

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.

Lecture fichiers son

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.

Gestion du cadran

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.

  • 5 -> 5 impulsions -> 5
  • pause < 500ms
  • 3 -> 3 impulsions -> 53
  • pause < 500ms
  • 7 -> 7 impulsions -> 537
  • pause > 500ms -> termin, numro compos = 537

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
})

Affectation de sons un numro compos

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

Exemple d'audio dynamique

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

Faire sonner le tlphone

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 :

  • VCC : pin #4
  • R1 : pin #7
  • GND : pin #6

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));

Lancement au dmarrage

Pour lancer le programme NodeJs au dmarrage du RaspberryPi, je vous conseille d'utiliser pm2.

Conclusion

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.