Création d'un prototype de centrale domotique

Depuis un certain temps, je souhaite domotiser mon logement, mais comme il est trop facile d'acheter des modules tout prêts, j'ai choisi de créer ma propre centrale de domotique.

Après avoir lu l'article publié sur framboise314.fr, j'ai souhaité reprendre le mode de fonctionnement fondé sur des cartes à relais pilotant des appareils conventionnels. L'inconvénient est que le câblage de ce système doit être prévu dès le départ pour permettre cette installation. Chaque équipement doit être câblé directement et individuellement sur la carte à relais.

Ce système permet d'utiliser des interrupteurs conventionnels et autorise, moyennant un recâblage, de repasser aisément dans un mode de fonctionnement plus classique.

Habitant dans un appartement en location, j'ai donc fabriqué un prototype dans une boîte de rangement me permettant de créer la partie logicielle et d'expérimenter le système en attendant de pouvoir câbler mon logement à ma guise.

Vous pourriez me dire que ce serait plus facile en utilisant des modules sans-fil, mais non, j'aime le câblage, alors que cela ne facilite pas la mise en œuvre.

Le but de ce prototype est de simuler l'installation cible que je souhaiterais avoir.

Système de domotique

Voici les caractéristiques de mon prototype :

Fonctionnement de la centrale domotique

Le système est séparé en plusieurs briques :

L'espace de stockage partagé
Il s'agit d'un système de fichiers partagé avec les différents Raspberry Pi. L'état de chaque entrée et sortie est traduite par la présence ou non d'un fichier dans cet espace de stockage. Ce système permet d'avoir une installation pouvant grandir en ajoutant des Raspberry Pi supplémentaires afin d'augmenter le nombre d'entrées/sorties gérées. Plus précisément, il s'agit d'un tmpfs afin de limiter le nombre de cycles d'écriture sur les cartes SD. Il est partagé en NFS.
L'API
Écrite en PHP, elle assure toute l'intelligence du système. Elle est appelée par la brique d'entrée/sortie lorsqu'une entrée est activée et par une tâche cron toutes les minutes afin de déclencher les tâches planifiées. Chaque élément (étage, pièce, équipement, script) peut être appelé par l'API avec une URL dédiée.
Le démon d'entrée/sortie
Écrit en Python, ce démon pilote les périphériques (relais, bandeau LED, interrupteurs, etc.) et l'espace de stockage. Il crée ou supprime les fichiers dans le répertoire in en fonction de l'état des entrées et active ou désactive les sorties en fonction de la présence ou non des fichiers dans le répertoire out.
Le client Web
Ecrit en HTML, CSS et JS, il interroge l'API et propose une interface utilisateur simple et intuitive.

L'architecture du système se schématise comme suit :

Architecture du système

Chaque équipement est piloté en central, ce qui implique un câblage spécifique dans le cas où le système est déployé sur un logement :

Lampes et prises de courant

Chaque lampe et chaque prise de courant sont pilotées par un relais dédié. La phase part du bornier et entre dans la borne COM du relais. La borne NO (normalement ouvert) part vers la lampe ou la prise de courant.

Le câblage obtenu est le suivant :

Câblage des prises

Volets roulants

Un volet roulant filaire dispose de 4 fils :

Chaque phase est raccordée individuellement à un relais. Le neutre et la terre sont directement raccordés aux borniers.

L'allumage exclusif de chacune des phases est assuré logiciellement. Lorsque la fermeture est demandée, le relais d'ouverture est éteint, puis le relais de fermeture est allumé.

Le câblage obtenu est le suivant :

Câblage des volets

Thermostat

Le contact du thermostat est assuré par un relais. La fermeture et l'ouverture du contact sont déclenchées par la température de consigne à laquelle est ajoutée ou soustraite une marge de 1°C :

Thermostat à hystérésis

La sonde de température est assurée par l'un des capteurs météo de la maison.

Le câblage obtenu est le suivant :

Câblage du thermostat

Lecteur audio

Le lecteur audio est un client pour un serveur MPD. Dans ce prototype, le serveur MPD est le Raspberry Pi lui-même.

Bandeau LED

Le bandeau LED est composé de plusieurs LED RGB réglables individuellement. Chaque LED est associée à une puce WS2812B pilotable depuis un port GPIO du Raspberry Pi.

Station météo

La sonde météo BME280 connectée en I2C permet d'obtenir des informations concernant la température, l'humidité et la pression atmosphérique. Pour ce prototype, elle est déportée afin que les mesures ne soient pas faussées par la chaleur dégagée par le Raspberry Pi. Pour cela, j'ai utilisé une prise RJ45 sur laquelle est brassé le bus I2C pour faciliter la connexion :

Broche Code couleur T568B Usage
1blanc orangeInutilisée
2orangeInutilisée
3blanc vertVCC
4bleuGND
5blanc bleuSDA
6vertSCL
7blanc brunInutilisée
8brunInutilisée
Câblage du capteur météo

Configuration de l'API

La configuration de l'API se fait dans un fichier JSON. Elle décrit la configuration des étages, des pièces et des équipements. On y retrouve également les scripts et les tâches planifiées.

Voici un exemple de configuration :

{
	"floors":[
		{
			"name":"Rez-de-chaussée",
			"rooms":[
				{
					"name":"Cuisine",
					"icon":"kitchen",
					"color":"red",
					"items":[
						{
							"type":"Lamp",
							"name":"Spots",
							"relay":{
								"no":"spotsCuisine"
							}
						},
						{
							"type":"Lamp",
							"name":"Hotte",
							"relay":{
								"no":"lampeCuisine"
							}
						},
						{
							"type":"PowerPlug",
							"name":"Lave-vaisselle",
							"relay":{
								"no":"laveVaisselle"
							}
						},
						{
							"type":"PowerPlug",
							"name":"Cafetière",
							"relay":{
								"no":"cafetiere"
							}
						}
					]
				},
				{
					"name":"Salon",
					"icon":"livingroom",
					"color":"yellow",
					"items":[
						{
							"type":"Lamp",
							"name":"Spots",
							"relay":{
								"no":"spotsSalon"
							}
						},
						{
							"type":"Lamp",
							"name":"Lampadaire",
							"relay":{
								"no":"lampeSalon"
							}
						},
						{
							"type":"Shutter",
							"name":"Volet",
							"relayUp":{
								"no":"voletSalonOuverture"
							},
							"relayDown":{
								"no":"voletSalonFermeture"
							}
						},
						{
							"type":"RoomWeather",
							"name":"Capteur météo",
							"no":"meteoSalon"
						}
					]
				}
			]
		},
		{
			"name":"Premier étage",
			"rooms":[
				{
					"name":"Chambre",
					"icon":"bedroom",
					"color":"blue",
					"items":[
						{
							"type":"Lamp",
							"name":"Lampe de chevet",
							"relay":{
								"no":"lampeChevetChambre"
							}
						},
						{
							"type":"Shutter",
							"name":"Volet",
							"relayUp":{
								"no":"voletChambreOuverture"
							},
							"relayDown":{
								"no":"voletChambreFermeture"
							}
						},
						{
							"type":"LedStrip",
							"name":"Bandeau LED",
							"label":"bandeauLED"
						}
					]
				}
			]
		}
	],
	"inputs":{
		"interSpotsCuisine":{
			"call":"/0/0/0/tooglePower"
		},
		"interLampeCuisine":{
			"call":"/0/0/1/tooglePower"
		},
		"interScenarioNuit":{
			"call":"/nuit"
		},
		"interOuvertureVoletsSalon":{
			"call":"/0/1/2/open"
		},
		"interFermetureVoletsSalon":{
			"call":"/0/1/2/close"
		},
		"interScenarioMatin":{
			"call":"/matin"
		},
		"interLampeSalon":{
			"call":"/0/1/1/tooglePower"
		},
		"interSpotsSalon":{
			"call":"/0/1/0/tooglePower"
		}
	},
	"scripts":{
		"matin":{
			"name":"Scénario matin",
			"steps":[
				{"action":"run","object":"/0/0/3","method":"powerOn","args":{}},
				{"action":"run","object":"/0/1/2","method":"open","args":{}},
				{"action":"run","object":"/1/0/0","method":"powerOn","args":{}},
				{"action":"run","object":"/1/0/1","method":"open","args":{}}
			],
			"hidden":false
		},
		"nuit":{
			"name":"Scénario nuit",
			"steps":[
				{"action":"run","object":"/0/0/0","method":"powerOff","args":{}},
				{"action":"run","object":"/0/0/1","method":"powerOff","args":{}},
				{"action":"run","object":"/0/0/2","method":"powerOn","args":{}},
				{"action":"run","object":"/0/0/3","method":"powerOff","args":{}},
				{"action":"run","object":"/0/1/0","method":"powerOff","args":{}},
				{"action":"run","object":"/0/1/1","method":"powerOff","args":{}},
				{"action":"run","object":"/0/1/2","method":"close","args":{}},
				{"action":"run","object":"/1/0/0","method":"powerOn","args":{}},
				{"action":"run","object":"/1/0/1","method":"close","args":{}}
			],
			"hidden":false
		}
	},
	"plannedTasks":{
		"arretLaveVaisselle":{
			"trigger":{
				"and":[
					{"==":["hour",23]},
					{"==":["minute",0]}
				]
			},
			"call":"/0/0/2/powerOff"
		},
		"reveil":{
			"trigger":{
				"and":[
					{"==":["holyday",false]},
					{"==":["hour",7]},
					{"==":["minute",0]}
				]
			},
			"call":"/matin/"
		}
	},
	"items":[
		{
			"type":"Thermostat",
			"name":"thermostatChaudiere",
			"relay":{
				"no":"thermostatChaudiere"
			},
			"temperatureSensorUrl":"/0/1/3"
		}
	],
	"latitude":48.855,
	"longitude":2.2944,
	"timezone":1
}

Les scripts

Les scripts permettent d'appeler séquentiellement différentes adresses de l'API. Il est également possible de définir une pause entre deux appels.

Programmation d'événements

Dans la configuration du serveur, il est possible de créer des tâches planifiées selon la configuration du système. Les paramètres pouvant être utilisés sont les suivants :

Il est possible de combiner les conditions d'exécution avec les opérateurs ET et OU, et également de comparer les valeurs avec les opérateurs "égal", "différent", >, >=, <, <=, incluses ou exclues d'une liste de valeurs.

Voici un exemple de configuration permettant d'allumer la lampe de chevet (selon la configuration ci-dessus) à 7:00 tous les jours ouvrés :

"reveil":{
	"trigger":{
		"and":[
			{"==":["holyday",false]},
			{"==":["hour",7]},
			{"==":["minute",0]}
		]
	},
	"call":"/1/0/0/powerOn"
}

Calcul de l'heure de lever, d'azimut et de coucher de Soleil

N'ayant pas de box Internet et souhaitant un système le plus indépendant possible des API externes, j'ai donc voulu implémenter un calcul de l'heure de lever, d'azimut et de coucher du Soleil pour, par exemple, fermer les volets en fin de journée.

Pour cela, je me suis basé sur la feuille de calcul fournie par l'Agence américaine d'observation océanique et atmosphérique qui permets de calculer ces heures à partir de la latitude, la longitude et la date du jour.

Le calcul s'effectue comme suit. Les paramètres choisis sont les heures de lever, d'azimut et de coucher du Soleil pour aujourd'hui à la Tour Eiffel, exprimés en heure locale :

Tout d'abord, le calcul se fait avec les jours julien et les siècles juliens. Pour cela, on effectue la conversion en appliquant les règles suivantes :

Le calcul des heures se fait comme suit. Soit la la latitude, lo la longitude et fh le fuseau horaire du point à calculer. La lettre des variables correspond à la colonne de la feuille de calcul d'origine :

Explication Formule Valeur arrondie
Jour julien Voir ci-dessus JJ = 2459150,5
Siècle julien Voir ci-dessus SJ = 0,21
Moyenne géométrique de la longitude du Soleil (en degrés) i = (280,46646+SJ×(36000,76983+SJ×0,0003032)) mod 360 i = 216°
Moyenne géométrique de l'anomalie du Soleil (en degrés) j = 357,52911+SJ×(35999,05029-0,0001537×SJ) j = 7853,51°
Excentricité de l'orbite terrestre k = 0,016708634-SJ×(0,000042037+0,0000001267×SJ) k = 0,02
Équation du centre solaire l = sin(j×π180)×(1,914602-SJ×(0,004817+0,000014×SJ))+sin(2j×π180)×(0,019993-0,000101×SJ)+sin(3j×π180)×0,000289 l = -1,77
Longitude solaire réelle (en degrés) m = i+l m = 214,23°
Anomalie solaire réelle (en degrés) n = j+l n = 7851,74°
Vecteur du rayon du Soleil (UA) o = (1,000001018×(1-k2))/(1+k×cos(n×π180)) o = 0,99
Longitude apparente du Soleil (en degrés) p = m-0,00569-0,00478×sin((125,04-1934,136×SJπ180)) p = 214,22°
Écliptique de l'obliquité moyenne (en degrés) q = 23+(26+(21,448-SJ×(46,815+SJ×(0,00059-SJ×0,001813)))/60)/60 q = 23,44°
Correction de l'obliquité (en degrés) r = q+0,00256×cos((125,04-1934,136×SJπ180) r = 23,44°
Ascention solaire réelle (en degrés) s = (atan2(cos(p×π180),cos(r×π180)×sin(p×π180)))×180π s = -121,96°
Déclinaison du Soleil (en degrés) t = (asin(sin(r×π180)×sin(p×π180)))×180π t = -12,93°
var y u = tan((r/2)×π180)×tan((r/2)×π180) u = 0,04
Équation du temps (en minutes) v = 4(u×sin(2i×π180)-2k×sin(j×π180)+4k u×sin(j×π180)×cos(2i×π180)-12u2×sin(4i×π180)-54k2×sin(2j×π180))×180π v = 16,14 minutes
Angle horaire de lever du Soleil (en degrés) w = (acos(cos(90,833×π180)/(cos(la×π180)×cos(t×π180))-tan(la×π180)×tan(t×π180)))×180π w = 76,11°
Azimut (en fraction de jour julien) x = (720-4lo-v+fh×60)/1440 x = 0,52
Lever (en fraction de jour julien) y = (1440x-4w)/1440 y = 0,31
Coucher (en fraction de jour julien) z = (1440x+4w)/1440 z = 0,74

La conversion d'une fraction de jour julien (noté fJJ) en heure lisible se fait comme suit :

Pour la journée du mercredi 28 octobre 2020, les heures sont les suivantes :

Lever du Soleil
07:30:13
Azimut du Soleil
12:34:40
Coucher du Soleil
17:39:07

L'utilisation de ces heures dans les scripts se fait en vérifiant que les booléens sunrise (lever de Soleil), sunnoon (azimut) et sunset (coucher de Soleil) soient vrais.

Calcul des jours fériés mobiles

En plus des fêtes fixes (8 mai, 14 juillet, 11 novembre, etc.), nous avons des fêtes dites mobiles (car leurs dates varient chaque année). C'est le cas pour Pâques, le Lundi de Pâques, l'Ascension et la Pentecôte. Cependant, il est possible de déterminer, à partir de la date de Pâques, la date du Lundi de Pâques (le lendemain), de l'Ascension (40 jours après Pâques) et de la Pentecôte (50 jours après Pâques).

Pour calculer la date de Pâques, on utilise l'algorithme de Butcher-Meeus. Pour cela, on applique les opérations suivantes. Les valeurs données sont celles pour l'année 2020 :

Dividende Diviseur Quotient Reste Explication
Année = 2020 19 n = 6 Cycle de Méton
Année = 2020 100 c = 20 u = 20 Centaine et rang de l'année
c 4 s = 5 t = 0 Siècle bissextile
c + 8 25 p = 1 Cycle de proemptose
c - p + 1 3 q = 6 Proemptose
19n + c - s - q + 15 30 e = 18 Epacte
u 4 b = 5 d = 0 Année bissextile
2t + 2b - e - d + 32 7 L = 3 Lettre dominicale
n + 11e + 22L 451 h = 0 Correction
e + L - 7h + 114 31 m = 4 j = 11 Mois et quantième du Samedi saint

Pour l'année 2020, les dates des jours fériés mobiles sont :

Pâques
Dimanche 12 avril 2020
Lundi de Pâques
Lundi 13 avril 2020
Ascension
Jeudi 21 mai 2020
Pentecôte
Dimanche 31 mai 2020

Matériel

Pour concevoir ce prototype, j'ai utilisé les éléments suivants. Je donne cette liste à titre indicatif et vous êtes libres de l'adapter à votre guise :

Produit Fournisseur PU Quantité Total
Boîte de rangement Leroy Merlin 3,95 € 1 3,95 €
Prise femelle Leroy Merlin 2,18 € 5 10,90 €
Câble électrique Leroy Merlin 9,90 € 1 9,90 €
Fil électrique Leroy Merlin 7,55 € 1 7,55 €
Bornier automatique 5 entrées Leroy Merlin 5,90 € 2 11,80 €
Raspberry Pi 3B Amazon 39,98 € 1 39,98 €
Carte SD 16 Go Amazon 9,07 € 1 9,07 €
Alimentation 5V 8A Amazon 20,27 € 1 20,27 €
Carte de 8 relais 5V avec opto-coupleurs Amazon 9,99 € 1 9,99 €
Modules I/O I2C PCF8574 (lot de 2) Amazon 6,38 € 1 6,38 €
Interrupteurs à bascule (lot de 10) Amazon 8,99 € 1 8,99 €
Fils Dupont (lot de 120) Amazon 6,99 € 1 6,99 €
Prises RJ45 femelles (lot de 5) Amazon 7,99 € 1 7,99 €
Capteur météo I2C (température, humidité, pression) BME280 Amazon 7,99 € 1 7,99 €
Horloge temps réel I2C DS3231 Amazon 6,29 € 1 6,29 €
Bandeau 60 LED RGB 1m WS2812B Amazon 13,99 € 1 13,99 €
Total 182,03 €

J'ai également utilisé un cordon d'alimentation standard pour tout alimenter. Le choix de ce modèle de Raspberry Pi est motivé par la présence du module WiFi inclus qui me permet de créer un réseau qui lui est propre (et aussi parce que j'en avais un inutilisé sous la main).

L'outillage nécessaire pour réaliser ce prototype est le suivant :

Câblage des composants

Voici le câblage final des différents composants du prototype :

Câblage des composants

Le code

Le système est composé de trois briques logicielles. La communication entre l'API et le démon d'entrée/sortie est assurée par le système de fichiers partagé. Cela permet de partager simplement l'état du système entre plusieurs Raspberry Pi et avoir ainsi un système extensible à volonté : on ajoute des Raspberry Pi avec des modules ad-hoc pour augmenter le nombre d’éléments pilotés.

Le client Web appelle l'API pour connaître l'état du système et envoyer des ordres. Cela me permet de créer différents systèmes pouvant dialoguer avec le système de domotique en appelant l'API pour ajouter des fonctionnalités.

Le démon d'entrée/sortie

Il s'agit d'une boucle qui lit l'état des entrées du module I/O I2C (adresse 0x21) et qui, en fonction de la configuration renseignée dans le fichier io.json, crée les fichiers adéquats dans le répertoire in. A l'inverse, si le fichier adéquat existe dans out, la sortie ad-hoc sur le second module (adresse 0x20) est activée. C'est également durant cette boucle que les valeurs de température, d'humidité et de pression atmosphérique sont lues, si le champ roomWeather est renseigné. Enfin, le bandeau LED est piloté en fonction des paramètres du fichier dans le répertoire out.

Voici un schéma de fonctionnement du démon d'entrée/sortie :

Fonctionnement du démon d'entrée/sortie

Voici un exemple de configuration :

{
	"in":{
		"1":"interSpotsCuisine",
		"2":"interLampeCuisine",
		"3":"interScenarioNuit",
		"4":"interOuvertureVoletsSalon",
		"5":"interFermetureVoletsSalon",
		"6":"interScenarioMatin",
		"7":"interLampeSalon",
		"8":"interSpotsSalon"
	},
	"out":{
		"1":"spotsCuisine",
		"2":"spotsSalon",
		"3":"laveVaisselle",
		"4":"lampeCuisine",
		"5":"cafetiere",
		"6":"lampeSalon",
		"7":"voletSalonOuverture",
		"8":"voletSalonFermeture"
	},
	"roomWeather":"meteoSalon",
	"ledStrip":{
		"label":"bandeauLED",
		"leds":60
	}
}

L'API

L'API représente chaque élément (objet, étages, pièces, tâches, etc.) sous la forme d'objets. Chaque objet est capable d’être appelé par une URL sous la forme suivante : /etage/piece/objet/methode (exemples : /0/0/0/powerOn pour allumer les spots de la cuisine, /1/0/powerOff pour éteindre tous les équipements de la chambre). Si la méthode n'est pas mentionnée, il s'agit de la méthode returnData qui est appelée. Elle permet de retourner l'état de l'objet.

Il est possible d'obtenir l'état de tous les objets enfants en appelant l'objet parent. Cela limite les appels de l'API.

Voici un exemple de retour fait par l'API. Ici, il s'agit de l'état de l'ensemble du système (/returnData) :

{
	"url": "/",
	"floors": [
		{
			"url": "/0",
			"id": 0,
			"name": "Rez-de-chaussée",
			"rooms": [
				{
					"url": "/0/0",
					"id": 0,
					"name": "Cuisine",
					"icon": "kitchen",
					"color": "red",
					"items": [
						{
							"url": "/0/0/0",
							"id": 0,
							"name": "Spots",
							"scripts": [],
							"relay": {
								"no": "spotsCuisine",
								"status": false
							},
							"status": false,
							"type": "Lamp",
							"defaultMethod": "tooglePower"
						},
						{
							"url": "/0/0/1",
							"id": 1,
							"name": "Hotte",
							"scripts": [],
							"relay": {
								"no": "lampeCuisine",
								"status": true
							},
							"status": true,
							"type": "Lamp",
							"defaultMethod": "tooglePower"
						},
						{
							"url": "/0/0/2",
							"id": 2,
							"name": "Lave-vaisselle",
							"scripts": [],
							"relay": {
								"no": "laveVaisselle",
								"status": true
							},
							"status": true,
							"type": "PowerPlug",
							"defaultMethod": "tooglePower"
						},
						{
							"url": "/0/0/3",
							"id": 3,
							"name": "Cafetière",
							"scripts": [],
							"relay": {
								"no": "cafetiere",
								"status": false
							},
							"status": false,
							"type": "PowerPlug",
							"defaultMethod": "tooglePower"
						}
					],
					"scripts": []
				},
				{
					"url": "/0/1",
					"id": 1,
					"name": "Salon",
					"icon": "livingroom",
					"color": "yellow",
					"items": [
						{
							"url": "/0/1/0",
							"id": 0,
							"name": "Spots",
							"scripts": [],
							"relay": {
								"no": "spotsSalon",
								"status": false
							},
							"status": false,
							"type": "Lamp",
							"defaultMethod": "tooglePower"
						},
						{
							"url": "/0/1/1",
							"id": 1,
							"name": "Lampadaire",
							"scripts": [],
							"relay": {
								"no": "lampeSalon",
								"status": false
							},
							"status": false,
							"type": "Lamp",
							"defaultMethod": "tooglePower"
						},
						{
							"url": "/0/1/2",
							"id": 2,
							"name": "Volet",
							"scripts": [],
							"relayUp": {
								"no": "voletSalonOuverture",
								"status": false
							},
							"relayDown": {
								"no": "voletSalonFermeture",
								"status": false
							},
							"type": "Shutter",
							"opened": true
						},
						{
							"url": "/0/1/3",
							"id": 3,
							"name": "Capteur météo",
							"scripts": [],
							"type": "RoomWeather",
							"temperature": 22.8,
							"humidity": 47.5,
							"pressure": 991.3,
							"call": "/0/1/3/updateWeather"
						}
					],
					"scripts": []
				}
			],
			"scripts": []
		},
		{
			"url": "/1",
			"id": 1,
			"name": "Premier étage",
			"rooms": [
				{
					"url": "/1/0",
					"id": 0,
					"name": "Chambre",
					"icon": "bedroom",
					"color": "blue",
					"items": [
						{
							"url": "/1/0/0",
							"id": 0,
							"name": "Lampe de chevet",
							"scripts": [],
							"relay": {
								"no": "lampeChevetChambre",
								"status": false
							},
							"status": false,
							"type": "Lamp",
							"defaultMethod": "tooglePower"
						},
						{
							"url": "/1/0/1",
							"id": 1,
							"name": "Volet",
							"scripts": [],
							"relayUp": {
								"no": "voletChambreOuverture",
								"status": false
							},
							"relayDown": {
								"no": "voletChambreFermeture",
								"status": false
							},
							"type": "Shutter",
							"opened": true
						},
						{
							"url": "/1/0/2",
							"id": 2,
							"name": "Bandeau LED",
							"scripts": [],
							"type": "LedStrip",
							"label": "bandeauLED",
							"colors": [],
							"mode": "plain"
						}
					],
					"scripts": []
				}
			],
			"scripts": []
		}
	],
	"scripts": [
		{
			"label": "matin",
			"name": "Scénario matin"
		},
		{
			"label": "nuit",
			"name": "Scénario nuit"
		}
	],
	"inputs": [
		{
			"no": "interSpotsCuisine",
			"status": false,
			"call": "/0/0/0/tooglePower"
		},
		{
			"no": "interLampeCuisine",
			"status": false,
			"call": "/0/0/1/tooglePower"
		},
		{
			"no": "interScenarioNuit",
			"status": false,
			"call": "/nuit"
		},
		{
			"no": "interOuvertureVoletsSalon",
			"status": false,
			"call": "/0/1/2/open"
		},
		{
			"no": "interFermetureVoletsSalon",
			"status": false,
			"call": "/0/1/2/close"
		},
		{
			"no": "interScenarioMatin",
			"status": false,
			"call": "/matin"
		},
		{
			"no": "interLampeSalon",
			"status": false,
			"call": "/0/1/1/tooglePower"
		},
		{
			"no": "interSpotsSalon",
			"status": false,
			"call": "/0/1/0/tooglePower"
		}
	],
	"items": {
		"2": {
			"type": "Thermostat",
			"relay": {
				"no": "thermostatChaudiere",
				"status": false
			},
			"temperatureSet": 21,
			"temperatureSensorUrl": "/0/1/3",
			"scripts": []
		}
	}
}

Ce mode de fonctionnement permet à n'importe quel script ou application de s'interfacer avec le système de domotique sans avoir à recréer d'interface particulière.

Une tâche cron est exécutée toutes les minutes et appelle l'API via une méthode particulière. Cette méthode vérifie les paramètres de chaque tâche planifiée décrite dans la configuration et les exécute si toutes les conditions sont remplies.

Le client Web

Le client Web est optimisé pour une utilisation sur mobile. La mise à jour de l'état des objets (allumage d'une lampe, fermeture d'un volet, etc.) est transmise à chaque client connecté à l'API par Server Side Events. Une mise à jour de l'état du système est également envoyée toutes les minutes pour maintenir la connexion.

Capture d'écran du client Web Capture d'écran du client Web Capture d'écran du client Web
Captures d'écran du client Web

Les icônes utilisées par le client sont de ma création. Elles sont disponibles au téléchargement au format SVG et sous licence CC BY-SA :

Icônes au format SVG