Archives Mensuelles: juin 2014

Surcharge de fonction javascript

Je suis actuellement sur une problématique de migration d’une application web de IE 8 (enfin plutôt IE5 😉 ) vers IE11.
L’un des problèmes rencontrés est une mauvaise façon de faire dans l’application. Il y a eu confusion entre l’attribut className javascript et la propriété « class » HTML.

Sous IE8 il y avait la possibilité de modifier la classe d’un objet HTML en faisant un .setAttribute(« className », « nouvelle classe »), or cela ne fonctionne plus en IE11 (même en activant le mode de compatibilité).

Plutot que de modifier le code dans tous les fichiers (près de 200 pages touchées, le copier coller quand c’est mal fait c’est mal fait 😉 ), je suis parti sur une approche de surcharge de la fonction javascript setAttribute().
Je sauvegarde les pointeurs vers les fonctions de base pour les appeler dans le cas « normal »

Voici une page exemple contenant le

<html>
<body>
<style>
.style1{ background-color:#00FFFF; }
.style2{ background-color:#FF0000; }
</style>

<div id="div1" class="style1"> Div 1 </div>
<div id="div2" class="style2"> Div 2 </div>

<script>
function setAttribute( element, valeur ) {
    if ( element == "className" )
    { this.className = valeur; }
    else
    { this._setAttribute( element, valeur ); }
}

function getAttribute( element ) {
    if ( element == "className" )
    { return this.className ; }
    else
    { return this._getAttribute( element ); }
}

try {
	HTMLElement.prototype._setAttribute = HTMLElement.prototype.setAttribute; 
	HTMLElement.prototype.setAttribute = setAttribute; 
	HTMLElement.prototype._getAttribute = HTMLElement.prototype.getAttribute; 
	HTMLElement.prototype.getAttribute = getAttribute; 
}
catch( err ) { }
</script>

<script>
document.getElementById( "div1" ).setAttribute("className" ,"style2");
alert( document.getElementById( "div1" ).getAttribute( "className" ));
</script>
</body>
</html>

Si vous supprimez les lignes du bloc try le code ne devrait plus fonctionner dans IE11.

Publicités

Bookmark WordPress – poster du code

Comment poster du code dans worpress :
http://en.support.wordpress.com/code/posting-source-code/

Cloner une télécommande Radio Fréquence (433MHz) – Part 3 Le cas Blyss

3615 Ma Life ou chronique d’une débâcle annoncée

Ce vendredi je suis passé chez Casto***** et j’ai acheté un pack de 3 prises radio commandées « Blyss » dans le but de les commandes à distance par le montage précédent.
Après avoir pris connaissance du fonctionnement normal des prises avec le montage d’acquisition (via Audacity) je me suis rendu compte que les trames envoyées changeaient à chaque envoi (tout du moins une partie …). Aye !

La cavallerie

Heureusement de bienveillants hackers (que leur noms soient glorifiés !) avaient déjà fait l’analyse du protocole :
http://skyduino.wordpress.com/2012/07/17/hack-partie-1-reverse-engineering-des-interrupteurs-domotique-blyss/
http://skyduino.wordpress.com/2012/07/19/hack-partie-2-reverse-engineering-des-interrupteurs-domotique-blyss/
http://forum.arduino.cc/index.php?topic=110677.0

Tout est dit dans les articles sur la méthode d’analyse et sur les format de données :

Analyse de protocole

Je copie ici le format de la trame :

- entête 2.4ms HIGH,
- code fixe : 0xFE (8 bits),
- canal global (4 bits), ( canal A = 0, canal B = 1, canal C = 2, canal D = 3 )
- adresse (16 bits), (qui identifie le bouton de la télécommande)
- sous canal (4 bits), (canal 1 = 8, canal 2 = 4, canal 3 = 2, canal 4 = 1, canal 5 = 3, tout les canaux = 0)
- état lumière (4 bits), (0 = éteins, 1 = allumé )
- rolling code (8 bits), ( 0x98 -> 0xDA -> 0x1E -> 0xE6 -> 0x67, à chaque nouvelle trame on passe au rolling code suivant  )
- timestamp (0 ~ 255), (8 bits), (en mettant n'importe quoi dedans ça semble marcher quand même, ce qui va simplifier la chose)
- footer 24ms LOW

Du Code !

Voici donc le code python correspondant pour commander une prise Blyss à partir de mon Pi.
J’ai nommé le script ci dessous Blyss.py, mais maintenant vous faites comme vous voulez 😉

#!/usr/bin/python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time
import sys

from os.path import expanduser

class Blyss :

        # Table des identifiants programmés dans les prises
        tableauBoutons = { "1" : "1000010111011010"
                                          , "2": "1000010111011011"
                                          , "3": "1000010111011100"
                                          }
        # Table des codes roulants trouvés par les hackers d'arduino.cc
        # http://skyduino.wordpress.com/2012/07/17/hack-partie-1-reverse-engineering-des-interrupteurs-domotique-blyss/
        # http://skyduino.wordpress.com/2012/07/19/hack-partie-2-reverse-engineering-des-interrupteurs-domotique-blyss/
        tableRollingCode = [ 0x67, 0x98, 0xDA, 0x1E, 0xE6]
        echantillonnage = 44100
        paramBits =     { 0 : [ [ 25, 0 ], [ 12, 1 ] ] ,
                                  1 : [ [ 12, 0 ], [ 25, 1 ] ] }
        dataPin    = 17
        nb_retry   = 7
        # Nombre de secondes d'attente entre deux retry
        attenteEntrePaquets = 0.024

        path2FileIdxLastRollingCode = expanduser("~") + "/.lastRCBlyss.idx"
        idRollingCode = 0

        def __init__(self, dataPin ):
                self.dataPin = dataPin

                # A stocker dans un fichier
                try :
                        idRollingCodeFile = open( self.path2FileIdxLastRollingCode , 'r')
                        self.idRollingCode     = int ( idRollingCodeFile.readline() )
                        self.idRollingCode    += 1
                        if self.idRollingCode >= len( self.tableRollingCode ):
                                self.idRollingCode = 0
                        idRollingCodeFile.close()
                except :
                        self.idRollingCode = 0

                GPIO.setwarnings(False)
                GPIO.setmode(GPIO.BCM)
                GPIO.setup(self.dataPin, GPIO.OUT)

        def sendDataPulse( self, dataPin, duration, value ):
                if value == 1 :
                        GPIO.output(dataPin, GPIO.HIGH)
                else:
                        GPIO.output(dataPin, GPIO.LOW)
                time.sleep( duration )

       def genereTrame( self, identifiantTelecommande, canal, sousCanal, etatLumiere ):
                trame = ""
                # empreinte 0xFE (8 bits),
                trame += "11111110"
                # canal global (4 bits),
                trame += canal
                # adresse (16 bits),
                trame += identifiantTelecommande
                # sous canal (4 bits),
                trame += sousCanal
                # état lumière (état logique) (4 bits),
                trame += etatLumiere
                # rolling code, MSBFIRST (8 bits),
                trame += "{0:08b}".format( self.tableRollingCode[ self.idRollingCode ] )
                # timestamp incrémentiel (0 ~ 255), MSBFIRST (8 bits),
                trame += "00000011"
                return trame

        def send( self, button, action ):

                if button in self.tableauBoutons:
                        identifiantTelecommande = self.tableauBoutons[ button ];
                else:
                        print "Boutton non défini"
                        exit( 1)
                # canal : a paramètrer ?
                canal = "0000"
                # sous canal : a paramètrer ?
                sousCanal = "1000"

                if action == "ON" :
                        etatLumiere = "0000"
                elif action == "OFF" :
                        etatLumiere = "0001"
                else :
                        print "l'action doit être ON ou OFF"
                        exit( 1)

                trame = self.genereTrame( identifiantTelecommande, canal, sousCanal, etatLumiere )
                print trame

                for i in range( 0, self.nb_retry ) :
                        # Envoi d'un HIGH pendant 2.4ms
                        self.sendDataPulse( self.dataPin, 0.0024 , 1 )

                        for bit in trame:
                                for paramBit in self.paramBits[ int( bit ) ]:
                                        self.sendDataPulse ( self.dataPin, 1.0 * paramBit[0]/self.echantillonnage, paramBit[1])
                        # Envoi d'un LOW pendant 0.24ms
                        self.sendDataPulse( self.dataPin, 0.00024 , 0 )

                        time.sleep( self.attenteEntrePaquets )

        def __del__(self):
                GPIO.cleanup()
                path2FileIdxLastRollingCode = open( self.path2FileIdxLastRollingCode , 'w')
                path2FileIdxLastRollingCode.writelines( str( self.idRollingCode ) )
                path2FileIdxLastRollingCode.close()

if __name__ == "__main__" :
        button = "1"
        action = "ON"

        if len(sys.argv) > 1:
                button = sys.argv[1]
                action = sys.argv[2]
        else :
                print "arguments incorrects :"
                print sys.argv[0] + "<id> <ON|OFF>"
                sys.exit(1)
        rf = Blyss( 17 )
        rf.send( button, action )

Ce programme doit être lancé en tant que root (comme toujours dés qu’on utilise le gpio sur le Pi).
Il stocke le dernier rolling code utilisé dans un fichier de config dans /root.

D’après mes tests, l’adresse est libre (vous pouvez mettre ce que vous voulez), il faut juste l’apparier avec la prise (comme pour une télécommande « normale »). Dans le code c’est « tableauBoutons » qu’il faut modifier si vous voulez mettre l’identifiant de votre télécommande (c’est de cette manière que j’ai commencé mes tests).
Utiliser plusieurs télécommandes pour une seule prise fonctionne, du coup je penses que le rolling code semble géré par adresse (d’ailleurs il y a peut être une limite au nombres de télécommandes d’une prise ?). Je ne m’explique cependant tout les tenants et les aboutissants de ce rolling code, si une prise est hors tension ou hors portée elle peut, amha, être désynchronisée (ça expliquerait pourquoi on a que 5 codes, quelques appuis sur une touche permettent de resynchroniser les compteurs).
La zone timestamp est inutile, je l’ai fixée à 00000010 et je n’y touche pas ( j’ai cru a une défaillance à cause d’une valorisation à 0, mais le problème venait d’ailleurs, mais j’ai laissé 0000010, ça marche chez moi 😉 )
La trame d’envoi doit être répétée ( la télécommande analysée le fait 7 fois !) et le temps entre deux répétitions (24ms) n’est pas neutre. Je l’ai modifié et du coup ça ne marchait plus. Donc on reste sur 24ms

Travail sous Licence Demerdenzizich

C’est un travail « brut de fonderie » que je livre ici, je ne trouve pas le système très fiable pour l’instant.
J’ai eu des trames tres déformées à un moment de la journée, sans être capable de savoir si ça venait du raspberry, du transmetteur RF ou de l’antenne.
Le fait que cela fonctionne mal si on a une attente de plus de 24ms n’est pas très rassurante, il suffit que le raspi rame pour que les temps s’envolent.
A voir si j’arrive a faire communiquer un attiny avec un raspi (envoi de la trame à envoyer via GPIO, l’attiny s’occupant de l’envoi).

L’antenne

D’ailleurs en parlant de l’antenne j’ai testé avec un fil de 17 cm. J’ai fait des tests avec une antenne d’un mettre qui n’a pas semblé améliorer les perfs, mais il faut que je refasse des tests à ce sujet, c’est peut être tombé en même temps que les problèmes généré en modifiant le temps d’attente entre réémission de deux trames (cf ci dessus).

Domoticz

Pour piloter tout cela j’ai installé le logiciel libre domoticz. Après avoir tâtonné dans leur interface( pas forcement très simple de premier abord, mais ça semble très puissant).
J’ai pu demander l’allumage et l’extinction des interrupteurs en lui faisant appeler les scripts déjà réalisés (Blyss.py et RF.py déjà décrit dans la partie 2). J’ai aussi vu pour spécifier des appels

Cloner une télécommande Radio Fréquence (433MHz) – Part 2

Après avoir vu comment récupérer une trame émise par une télécommande Radio Fréquence je vais vous montrer comment remplacer la télécommande avec un raspberry Pi.

Montage pour envoi (simplisme)

Le montage est ultra simple, le VCC de l’emetteur vers le 3,3V du raspi, le GND de l’emetteur vers le GND du raspberry et enfin le port DATA de l’emetteur vers le port 17 du raspi.
Je remets ici le diagramme des ports GPIO et leur notation (tout du moins pour la version 2 du raspberry pi)

Diagramme du port GPIO pour le Raspberry Pi version 2

Diagramme du port GPIO pour le Raspberry Pi version 2

Programme Python pour envoi code

La librairie RPi.GPIO est nécessaire, elle est dans les dépots de la raspian, pour les autres distrib veuillez vous adresser auprès de sa communauté.

 apt-get install python-rpi.gpio

La doc de cette librairie est ici : http://pythonhosted.org/RPIO/rpio_py.html#ref-rpio-py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time
import sys



class RF :
	
	tableau = { "AVIDSEN" : 
				{ "echantillonnage" : 44100,
				  "bits" : 
					{ 0 : [ [ 52, 0 ], [ 22, 1 ] ] ,
					  1 : [ [ 22, 0 ], [ 52, 1 ] ] },
				  "buttons" : 
					{ "1 ON"  : "011111111111100101101",
					  "1 OFF" : "011111111111100111100",
					  "2 ON"  : "011111111111110101111",
					  "2 OFF" : "011111111111110111110",
					  "3 ON"  : "011111111111111101110",
					  "3 OFF" : "011111111111111111111",
					  "4 ON"  : "011111111111100001111",
					  "4 OFF" : "011111111111100011110",
					  
					}
				}
			}
	
	paramBits = {}
	dataPin    = 17
	tempsPerdu = 0
	nb_retry   = 4
	# Nombre de secondes d'attente entre deux retry
	attenteEntrePaquets = 0.075
	
	def __init__(self, dataPin ):
		self.dataPin = dataPin
		GPIO.setwarnings(False) 
		GPIO.setmode(GPIO.BCM)
		GPIO.setup(self.dataPin, GPIO.OUT)

	def sendDataPulse( self, dataPin, duration, value ):
		if value == 1 :
			GPIO.output(dataPin, GPIO.HIGH)
		else:
			GPIO.output(dataPin, GPIO.LOW)
		time.sleep( duration )

	def send( self, marque, button ):
		
		# Précalcul les attentes pour chaque type de bits
		for valeur in range(0, 2):
			self.paramBits[ valeur ] = [] 
			for paramBit in self.tableau[marque]["bits"][valeur]:
				tempsAttente  = 1.0 * ( paramBit[0] - self.tempsPerdu) / self.tableau[marque]["echantillonnage"]
				self.paramBits[valeur].append( [ tempsAttente , paramBit[1] ] )
		
		self.sendDataPulse( self.dataPin, 0.01 , 0 )
		for i in range( 0, self.nb_retry ) :
			for bit in self.tableau[marque]["buttons"][ button ]:
				for paramBit in self.paramBits[ int( bit ) ]:
					self.sendDataPulse ( self.dataPin, paramBit[0], paramBit[1])
			self.sendDataPulse( self.dataPin, 0.01 , 0 )
			time.sleep( self.attenteEntrePaquets )
		self.sendDataPulse( self.dataPin, 0.01 , 0 )

	def __del__(self):
		GPIO.cleanup()

if __name__ == "__main__" :
	marque = "AVIDSEN"
	button = "1 ON"
	
	if len(sys.argv) > 2:
		marque = sys.argv[1]
		button = sys.argv[2] + " " + sys.argv[3]
	else :
		print "arguments incorrects :"
		print sys.argv[0] + " <marque> <id> <position>"
		sys.exit(1)

	rf = RF( 17 )
	rf.send( marque, button )

Quelques petites choses sont à adapter pour votre télécommande, j’ai essayé de les regrouper dans la variable « tableau » ( ouh que ce nom est mal choisi !).

tableau = { "AVIDSEN" : 

AVIDSEN est le petit nom de la télécommande, j’ai donné le nom de la marque, je n’ai aucune imagination pour les noms 😉

				{ "echantillonnage" : 44100,

Le taux d’échantillonnage utilisé dans Audacity est noté ici, les temps seront notés en « samples » (il est facil d’adapter cette partie pour mettre des secondes à la place, je me tâte d’ailleurs de mettre un nombre de µs à la place).

				  "bits" : 
					{ 0 : [ [ 52, 0 ], [ 22, 1 ] ] ,
					  1 : [ [ 22, 0 ], [ 52, 1 ] ] },

C’est la partie qui me dérange le plus, car partant de bases théoriques que je pensais sûres (un nombre de points sur un enregistrement Audacity), cela ne marchait pas. Le raspberry pi n’est pas capable d’attendre un nombre de µs fiable, l’attente de 55 samples était trop long, j’ai du descendre à 52 de façon empirique (et les autres valeurs aussi). En effet nous ne sommes pas sur un système temps réel, c’est à dire que le programme d’envoi n’est pas seul à tourner sur la machine, d’autres vont le ralentir et ce de façon non prédictive. Ajoutons à ça des lib pythons qui ne sont pas forcement aussi rapides que du C du coup j’espère que le service sera viable.

				  "buttons" : 
					{ "1 ON"  : "011111111111100101101",
					  "1 OFF" : "011111111111100111100",
					}

La trame capturée à l’aide d’Audacity est stockée ici, brute de fonderie. Je n’ai pas trouvé le logique entre le bouton ON et le bouton OFF, alors j’ai pris le parti de stocker la trame complète.

Pour utiliser ce programme il fonctionne en ligne de commande

 python RF.py "AVIDSEN" 1 ON 

, mais il est aussi possible de l’utiliser comme une librairie pour un autre programme python.

Comme d’habitude cet article est un travail empirique qui peut ne pas fonctionner chez les autres (en somme il est sous licence Demerdenzizich que les Anglo Saxon appelle As-Is)

La troisième partie concernera l’utilisation d’une télécommande Blyss avec un code roulant.

Cloner une télécommande Radio Fréquence (433MHz) – Part 1 – Acquisition

La domotique arrive de plus en plus dans nos foyers, depuis des années j’avais des prises commandées par radio fréquence mais je ne les utilisaient pas car la télécommande n’était pas assez puissante (et pourtant je n’ai pas un château 😦 ). De plus j’ai toujours trouvé inconfortable de chercher la télécommande a chance fois que je veux allumer la lumière (n’avez vous jamais remarqué la timidité de ces dernières ? La preuve elles adorent se cacher derrière les coussins 😉 )

Bref j’avais dans l’idée de cloner ces télécommandes, voir d’automatiser tout ça (avec des capteurs de présence, via des « crontab », via commande vocale, …).

Les télécommandes auxquelles j’ai décidé de m’attaquer sont basées sur la fréquence 433MHz.
Coté matériel, j’ai trouvé des kits émetteur + récepteur pour un prix dérisoire via e-bay (4€ les 5 kits).
[pour ceux qui cherchent la référence on doit en trouver avec ce genre de recherche « 433Mhz RF transmitter receiver kit » ]

433Mhz emetteur-recepteur

433Mhz emetteur-recepteur

Première étape : voir à quoi on a affaire

montage

J’ai trouvé un montage d’une grand simplicité qui permet de « lire » le signal à partir d’un bête PC sans matériel coûteux.
Pour ce faire j’utilise un simple montage trouvé sur http://davehouston.net/learn.htm.
Le schéma à utiliser est le suivant :

Recepteur RF vers Line-In

Recepteur RF vers Line-In

Au niveau de l’alimentation j’utilise une alimentation variable. Du coup j’ai pu tester la réception entre 5 et 9V, je me suis fixé vers 8V ce qui affaibli le nombre de parasites reçus. Pour mes tests je n’ai pas branché d’antenne sur le récepteur, je préfères éviter de récupérer les signaux de tout le quartier 😉

Dans mes recherches je suis tombé sur une phrase concernant les jack, le fil Rouge serait toujours la voie de Droite (Right Red), si ça se confirme ça peut éviter de faire des tests pour trouver le bon câblage. (sur celui que j’ai récupéré Jaune : GND, Rouge : Droite, Bleu : Gauche. À voir si l’axiome RedRight est vérifié ailleurs ). Chez moi les deux fils (rouge et bleu) ont été joints, certains considèrent qu’on peut enregistrer deux signaux en parallèle (deux sources), moi je n’en ai pas besoin, donc on joint ;).

Audacity

Une fois le cablage vérifié (vérifiez deux fois, une boulette est si vite arrivée, je nierais avoir eu connaissance de vos actes en cas d’explosion de votre carte son) et le jack branché sur l’entré Ligne ( Line-In PAS le microphone, je ne sais pas si ça fait tout exploser mais mes sources étaient claires, ce montage est pour l’entrée Line-In !), lancez Audacity;
Ce programme est un enregistreur de son avec la possibilité de visualiser le son sous forme d’ondes. Et c’est exactement ce dont nous avons besoin ici !

Donc lançons l’enregistrement (je répète pour les deux du fonds qui discutent : en sélectionnant l’entrée Ligne ) et appuyons sur une touche de notre télécommande.
Un signal devrait apparaître, zoomez dessus :
Audacity_049

J’ai mis des 0 et des 1 pour bien comprendre ce qu’on voit. La télécommande utilise un codage (Manchester ?) où chaque bit est représenté sous forme d’un « escalier » entre un front haut et un front bas. La durée de ce front haut défini si c’est un « 1 » (front haut long) ou si c’est un « 0 » (front haut court).
Dans l’exemple pris ici nous avons une trame binaire 011111111111100101101, chaque bouton de ma télécommande envoi une trame différente.

Zoomons encore, cette fois en prenant un « 1 » et un « 0 » (idéalement, mais ça m’étonnerais que vous n’ayez pas un 1 à coté d’un 0 au moins une fois).
Le but cette fois va être de comprendre le protocole Radio Fréquence utilisé ici. Je n’ai pas la savoir pour expliquer ici les différentes méthodes existantes, mais j’ai compris qu’au moins ici c’est la durée de chaque front qui doit être mesuré.

Pour facilité l’analyse j’ai demandé à Audacity d’afficher par « sample » et non en millisecondes, un front durant quelques centaines de µs.

Mesure des temps de chaque front

Mesure des temps de chaque front


Dans l’exemple ci-dessus j’ai sélectionné le front haut du « 1 » et l’ai mesuré à 55 « échantillons ». Sachant que j’ai échantillonné à 44100Hz, il y a donc 44100 échantillons dans une seconde. Sortez vos calculettes, ce front haut dure donc ? Allez un effort … 55/44100 soit 0,001247166s soit 1.247ms. J’ai recommencé les mesures pour le front bas du « 1 » et les deux fronts du « 0 ». J’ai noté ces résultats précieusement ils seront utiles dans la partie suivante où l’on va se mettre à émettre des trames.

Suite dans la partie 2

Documentation :
http://davehouston.net/learn.htm
http://rayshobby.net/?p=3381
http://www.homautomation.org/2014/03/02/433mhtz-rf-communication-between-arduino-and-raspberry-pi-arduino-as-receiver/