Station météo avec affichage ePaper (Dashboard) pour Jeedom sur Raspberry Pi (via l'API JSON RPC) • Domotique et objets connectés à faire soi-même

Meilleur deal à : amazon.fr

Dans le tutoriel précédent, nous avons pu tester l’écran ePaper (eInk) 2,7 pouces du fabricant chinois Waveshare. Nous n’étions pas allé très loins dans la présentation…le temps de maitriser la librairie Python Imaging Library (ou le fork Pillow). Dans ce nouveau tutoriel d’aller, nous allons aller beaucoup plus loin et réaliser une station météo connectée à un serveur domotique Jeedom.

Le plugin météo (gratuit) de Jeedom permet de récupérer très facilement les prévisions sur 5 jours sur le site  openweathermap.org. Bien évidemment, ce n’est qu’un prétexte. L’idée étant de montrer les différents mécanismes à mettre en place pour interroger le serveur Jeedom, récupérer les données d’un appareil et comment générer l’affichage ePaper.

Matériel utilisé pour la station météo ePaper

Pour ce projet, nous allons utiliser un écran mesurant 2,7 pouces de diagonale avec une résolution de 264 x 176 pixels. Il est assez grand pour afficher les prévisions météo en détail. Il possède 4 boutons sur le coté que l’on va utiliser pour naviguer entre les écrans. On pourrait également les utiliser pour déclencher un scénario domotique ou afficher d’autres états.

Vous aurez également besoin d’un serveur Jeedom fonctionnel avec le plugin météo installé et configuré. Voir comment faire plus bas dans le tutoriel si ce n’est pas le cas.

Vous pouvez également choisir un autre écran dans la gamme Waveshare

Ecran

Couleurs

Niveau de gris

Résolution (pixels)

Taille écran (mm)

Taille totale (mm)

Temps de rafraîchissement (s)

Interface

Compatible HAT(1)

1.54 pouces

noir, blanc

2

200×200

27.60 × 27.60

48.0 × 33.0

2

SPI

x

1.54 pouces (B)

rouge, noir, blanc

2

200×200

27.60 × 27.60

48.0 × 33.0

8

SPI

x

2.13 pouces

noir, blanc

2

250×122

48.55 × 23.71

65.0 × 30.2

2

SPI

2.13 pouces (B)

rouge, noir, blanc

2

212×104

48.55 × 23.71

65.0 × 30.2

15

SPI

2.7 pouces

noir, blanc

2

264×176

57.29 × 38.19

85.0 × 56.0

6

SPI

2.7 pouces (B)

rouge, noir, blanc

2

264×176

57.29 × 38.19

85.0 × 56.0

15

SPI

2.9 pouces

noir, blanc

2

296×128

66.89 × 29.05

89.5 × 38.0

2

SPI

x

2.9 pouces (B)

rouge, noir, blanc

2

296×128

66.89 × 29.05

89.5 × 38.0

15

SPI

x

4.2 pouces

noir, blanc

2

400×300

84.80 × 63.60

103.0 × 78.5

4

SPI

x

4.2 pouces (B)

rouge, noir, blanc

2

400×300

84.80 × 63.60

103.0 × 78.5

15

SPI

x

4.3 pouces

noir, blanc

4

800×600

88.00 × 66.00

118.0 × 75.0

1.5

UART

x

7.5 pouces

noir, blanc

2

640×384

163.20×97.92

170.2×111.2

6

SPI

7.5 pouces (B)

rouge, noir, blanc

2

640×384

163.20×97.92

170.2×111.2

31

SPI

(1) Connecteur compatible avec tous les modèles de Raspberry Pi. Les autres écrans doivent être connectés à l’aide de Jumpers.

Si vous débutez avec les écrans ePaper du fabricant Waveshare, je vous conseille de lire cet article qui explique comment les utiliser avec un ou deux exemples simples.

Installer le driver epd sur Raspbian

Avant de pouvoir utiliser l’écran, il est nécessaire d’installer le driver pour Raspbian.

Il n’y a pas de drivers universel. Waveshare a développé (et configuré) un drivers pour chaque écran. Celui-ci est livré avec les exemples. Allez sur le WiKi et allez sur la page qui correspond à votre écran. La page de l’écran 2,7 pouce est ici. En bas de chaque page, on trouve un lieu vers la page des exemples (Demo code). Ouvrez la page de l’exemple pour récupérer le lien vers le fichier compressé à l’aide d’un clic droit sur le lien.

La liste complète des drivers se trouve ici

Depuis le Terminal sur le Raspberry Pi, créer un répertoire de travail, par exemple mkdir epaper puis ouvre celui-ci cd epaper. Ensuite, à l’aide de la commande wget, on va récupérer le fichier d’exemple.

wget https://www.waveshare.com/w/upload/f/f5/2.7inch-e-paper-hat-code.7z

Pour le décompresser, nous allons utiliser l’outil p7zip. Exécutez la commande suivante pour l’installer

sudo apt-get install p7zip-full

Il ne nous reste plus qu’à décompresser l’archive avec la commande7z x nom_du_fichier.7z comme ceci

7za e 2.7inch-e-paper-hat-code.7z

Maintenant lancez l’outil de configuration avec la commande sudo raspi-config. Allez à l’option 5 Interfacing Option puis activez successivement le bus SPI et I2C.

Acceptez le redémarrage lorsque vous quittez raspi-config pour activer les bus.

Une fois redémarré, ouvrez le fichier de configuration des drivers avec la commande sudo nano /etc/modules. Vérifiez que les drivers suivants sont bien présents. Redémarrez Raspbian pour recharger les drivers (sudo reboot).

i2c-bcm2708
i2c-dev

Installer la librairie Python Pil

Le driver epd (pour EPaper Display j’imagine) ne fait “que” d’afficher une image. On va donc devoir la générer en Python au préalable. La librairie Python Imaging (Pil) ou son fork Pillow semble la plus puissante dans le domaine. Attention, Pil et Pillow ne peuvent pas cohabiter sur la même machine.

Pour installer Pil vous pouvez utiliser la commande pip. Vous aurez peut être besoin de faire précéder la commande pip d’un sudo.

pip install python-pil request

Sur macOS, le plus facile est d’utiliser le gestionnaire de paquets brew

brew install pil request

Découverte rapide de la librairie Python Pil / Pillow

Les librairies Pil ou Pillow permettent de manipuler des images (convertir, redimensionner, orienter…), ajouter des formes géométriques (ligne, ellipse, polygone, arc de cercle…) ou du texte sur une image existante. Ici nous allons créer un masque ayant comme dimension celle de l’écran ePaper 2,7”.

mask = Image.new('1', (EPD_HEIGHT,EPD_WIDTH), 255)

Comme nous l’avons vu dans l’article précédent, l’image produite est verticale. Si on veut un affichage horizontal, l’astuce consiste à créer une image dans les dimensions horizontales et verticales sont inversée puis la tourner de 90° avant de l’afficher sur l’écran ePaper. Créez un nouveau script nommé demopill.py et collez le code suivant

#Librairies nécessaires
from PIL import Image
from PIL import ImageDraw

#Dimensions de l'image
EPD_WIDTH       = 176
EPD_HEIGHT      = 264
# Créé un masque avec un fond blanc
mask = Image.new('1', (EPD_HEIGHT,EPD_WIDTH), 255)   
#Créé un objet Draw qui va permettre d'ajouter des éléments sur le masque
draw = ImageDraw.Draw(mask)
#En exemple de texte
draw.text((EPD_HEIGHT/4,EPD_WIDTH/2), 'Demo Python PILL', fill = 0)
#Une ligne horizontale
draw.line((0,EPD_WIDTH/2 + 12, EPD_HEIGHT, EPD_WIDTH/2 + 12), fill = 0)
#On enregistre l'image générée
mask.save('demopill.bmp',"bmp")

Enregistrez le script et exécutez le avec la commande python demopill.py.

Voici l’image générée par le script. Comme vous pouvez le voir, elle est horizontale car j’ai inversé la hauteur (Height) et la largeur (Width). Ici, il n’est pas nécessaire de tourner l’image.

Récupérer des images et des fonts Open Source

J’ai utilisé des images gratuites et Open Source pour ce projet. Vous pouvez en trouver un peu partout sur internet. Voici les sites que j’ai utilisé pour ce projet :

  • Les icônes Open Source de Kickstand Apps disponibles sur Github pour les symboles météo. Pratique, elles sont déjà au format PNG. Utilisez plutôt la version épaisse pour un meilleur rendu sur l’écran ePaper
  • icones8.fr pour les pictogrammes température, humidité, pression atmosphérique, direction du vent
  • La police FreeMonoBold.ttf peut être récupérée un peu partout sur internet comme ici sur GitHub

Installer et configurer le plugin météo sur Jeedom

Ouvrez le gestionnaire de plugin de Jeedom et recherchez le plugin Meteo officiel

Activez le plugin et configurez votre ville et attribuez un objet

Activer l’API JSON RPC et récupérer la clé API

Jeedom dispose de deux interfaces de communication. La première permet d’interroger Jeedom avec des requêtes HTTP. C’est cette interface qui avait été utilisé pour réaliser ce mini affichage déporté.

Pour varier les plaisirs (et aussi parce que l’API HTTP offre moins de possibilités), nous allons utiliser l’API JSON RPC pour ce projet. Le JSON RPC est un standard qui est documenté ici. Toutes les commandes exposées par Jeedom sont expliquées en détail sur la documentation en ligne.

Pour récupérer votre clé API et vérifier que les API sont activées, allez dans le menu de configuration (roue crantée) puis configuration. Ouvrez l’onglet API. Votre clé est la grande chaine de caractère. Vous pouvez en générée une nouvelle si vous pensez que la sécurité de votre serveur domotique a été corrompue. L’accès JSONPRC doit être activé. N’oubliez pas de sauvegarder si vous faites une modification.

Comment récupérer les prévisions météo avec l’API JSON RPC de Jeedom ?

Maintenant que tout est prêt, il est temps de commencer. Il existe (très grossièrement) deux types d’objets Jeedom qui vont nous intéresser ici. Les commandes et les équipements. Pour faire simple, le widget météo est un équipement. Un équipement est lui même attaché à un objet (le terme de groupe aurait été mieux adapté mais ce n’est pas très important). Ensuite chaque équipement peut posséder des commandes. Pour récupérer les identifiants de l’équipement et des commandes, il suffit d’ouvrir le panneau de Configuration avancée qui se trouve systématiquement dans le coins supérieur droit.

Ici l’équipement widget météo a l’IDentifiant 3.

Cliquez ensuite sur la roue crantée de chaque commande pour récupérer son identifiant (ici 17).

On peut aussi interroger l’API pour récupérer tous les identifiants mais la réponse renvoyée est très (trop) détaillée. Si vous avez de nombreux équipements vous risquez vite de vous noyer.

Créez un nouveau script python (par exemple jeedomrpc.py) et collez le code ci-dessous. Avant de l’exécuter vous devez modifier les paramètres suivants :

  • ip_jeedom l’adresse IP du serveur Jeedom
  • Api_key votre clé API récupérée précédemment
  • conditiontxt l’IDentifiant de la commande condition. Elle renvoi la prévision sous la forme d’une chaine
  • condition l’IDentifiant de la commande condition_numero. On récupère le code de la prévision d’OpenWeatherMap. On l’utilisera pour définir l’icône de la prévision à afficher
# coding: utf-8
import requests
import json

ip_jeedom = 'XXX.XXX.XXX.XXX'                                                                                                         
Api_key = 'XXXX_JEEDOM_API_KEY_XXXX'   
url = "http://%s/core/api/jeeApi.php"% ( ip_jeedom)
headers = {'content-type': 'application/json'}

#Dictionnaire qui contiendra les prévisions récupérées sur Jeedom
prevision = {}

def updateParameter(id, method):
  # Toutes les méthodes json rpc de Jeedom sont disponibles ici
  # https://jeedom.github.io/core/fr_FR/jsonrpc_api#tocAnchor-1-30-2
  parameters = {                                                                
    "jsonrpc" : "2.0",                                                    
    "method" : method,                                                                                                              
    "params": {                                                           
        "apikey": Api_key,
        "id" : id                                      
        }                                                                 
  }  
  return parameters 

def getDataFromJeedom():
  #Identifiants de commandes à récupérer
  idCmd = {
      "conditiontxt": "17",
      "condition"   : "18"
  }

  #On interroge l'API JSON RPC de Jeedom pour chaque commande 
  for key, value in idCmd.iteritems(): 

    param = updateParameter(value, "cmd::byId")
    response = requests.post(url, data=json.dumps(param), headers=headers).json()
    #Affichage la réponse renvoyée par Jeedom
    print response
    #Ajoute les valeur actuelle et l'unité dans le dictionnaire
    prevision[key] = {
      'value' : response['result']['currentValue'],
      'unit' : response['result']['unite'],
    }
    #Affichage les données récupérées et stockées dans le dictionnaire
    print prevision

getDataFromJeedom()

Exécutez le script (commande python jeedomrpc.py ). La réponse d’affiche directement dans le Terminal (ou l’invite de commande sous Windows). Pour “déplier” la réponse au format JSON et la rendre plus lisible pour nous pauvres humains, vous pouvez utiliser jsonlint.com par exemple.

{
    u 'jsonrpc': u '2.0', u 'id': None, u 'result': {
        u 'generic_type': u 'WEATHER_CONDITION',
        u 'currentValue': u 'L\xe9g\xe8res chutes de neige',
        u 'configuration': None,
        u 'name': u 'Condition',
        u 'display': {
            u 'generic_type': u 'WEATHER_CONDITION'
        },
        u 'isHistorized': u '0',
        u 'eqLogic_id': u '3',
        u 'unite': u '',
        u 'id': u '17',
        u 'subType': u 'string',
        u 'html': None,
        u 'alert': None,
        u 'value': None,
        u 'template': None,
        u 'isVisible': u '1',
        u 'eqType': u 'weather',
        u 'logicalId': u 'condition',
        u 'type': u 'info',
        u 'order': u '0'
    }
} {
    'conditiontxt': {
        'unit': u '',
        'value': u 'L\xe9g\xe8res chutes de neige'
    }
}

Il ne faut pas s’inquiéter des ‘u’ devant chaque clé. Par contre ce qui est plus gênant c’est l’encodage des chaînes. Jeedom n’encode pas en utf-8 mais en latin-1. Cala risque de vous poser pas mal de problèmes. Voici quelques astuces :

  • Forcer l’encodage en utf-8 au début du script en ajoutant le paramètre # coding: utf-8
  • Créez une variable intermédiaire qui contient la condition avant de créer un objet texte avec la librairie Pill.

Pour info, voici également le dictionnaire (une variable) qui a été créé par ce script. On y retrouve la prévision et le code correspondant.

{
    'conditiontxt': {
        'unit': u '',
        'value': u 'L\xe9g\xe8res chutes de neige'
    },
    'condition': {
        'unit': u '',
        'value': 600
    }
}

Code python complet

Voilà, maintenant il ne reste plus qu’à tout assembler. Nous savons interroger l’API JSON RPC de Jeedom pour récupérer des états et des informations sur des équipements. Nous savons comment générer une image à l’aide de la libraire Python Pil. Enfin, nous savons intercepter les boutons Key1 à Key2 sur le coté de l’écran pour modifier l’affichage ou déclencher un scénario.

Créez un nouveau script et collez le code ci-dessous. Modifiez les paramètres suivants dans le code

  • ip_jeedom l’adresse IP du serveur Jeedom
  • Api_key votre clé API
  • Dictionnaire idCmd changez les identifiants des différentes commandes pour celles de votre équipement météo
  • modeTest permet de générer l’image sur un ordinateur (PC Windows, Linux, Mac, Raspberry Pi) sans utiliser le GPIO et l’écran ePaper. Le script génère juste une image en sortie
  • Toutes les images et la police FreeMonoBold.ttf doivent être stockées dans le dossier image.

Téléchargez les images et la police FreeMonoBold.ttf depuis le blog en cliquant sur ce lien. Décompressez l’archive et placez le dossier à la racine du code python. La variable folder_img permet de modifier le dossier des ressources.

# coding: utf-8
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
import requests
import json
import math
import time
from datetime import datetime 
from datetime import timedelta 
import locale
locale.setlocale(locale.LC_TIME,'')

ip_jeedom = 'xxxx.xxx.xxx.xxx'                                                                                                         
Api_key = 'XXXX_JEEDOM_API_KEY_XXXX'   
url = "http://%s/core/api/jeeApi.php"% ( ip_jeedom)
headers = {'content-type': 'application/json'}
folder_img = 'images/'
H_condition = 100
W_condition = 100
H_Big = 15
H_icone = 25
W_icone = 25
Bord = 5
Col1 = 66
Col2 = 132
Col3 = 198
LiBaCd = 140
DiamPastille = 10
modeTest = False
prevision = {}

if modeTest:
  EPD_WIDTH       = 176
  EPD_HEIGHT      = 264
else:
  print 'mode epd2in7'
  import RPi.GPIO as GPIO
  import epd2in7
  epd = epd2in7.EPD()
  epd.init()
  EPD_WIDTH       = epd2in7.EPD_WIDTH
  EPD_HEIGHT      = epd2in7.EPD_HEIGHT
  GPIO.setmode(GPIO.BCM)
  key1 = 5
  key2 = 6
  key3 = 13
  key4 = 19
  
  GPIO.setup(key1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  GPIO.setup(key2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  GPIO.setup(key3, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  GPIO.setup(key4, GPIO.IN, pull_up_down=GPIO.PUD_UP)

#Charge les fonts et les images
fontSmall = ImageFont.truetype(folder_img + 'FreeMonoBold.ttf', 9)
fontMedium = ImageFont.truetype(folder_img + 'FreeMonoBold.ttf', 12)
fontBig = ImageFont.truetype(folder_img + 'FreeMonoBold.ttf', H_Big)
temperature = Image.open(folder_img + 'temperature.png')
humidity = Image.open(folder_img + 'humidity.png')
pressure = Image.open(folder_img + 'pressure.png')
direction = Image.open(folder_img + 'direction.png')
lever = Image.open(folder_img + 'lever.png')
coucher = Image.open(folder_img + 'coucher.png')

#Redimensionnement des images
temperature = temperature.resize((H_icone,W_icone))
humidity = humidity.resize((H_icone,W_icone))
pressure = pressure.resize((H_icone,W_icone))
direction = direction.resize((H_icone,W_icone))
lever = lever.resize((H_icone ,W_icone ))
coucher = coucher.resize((H_icone ,W_icone ))
#w,h = condition.size

def updateParameter(id, method):
  # Toutes les méthodes json rpc Jeedom disponibles https://jeedom.github.io/core/fr_FR/jsonrpc_api#tocAnchor-1-30-2
  parameters = {                                                                
    "jsonrpc" : "2.0",                                                    
    "method" : method,                                                                                                              
    "params": {                                                           
        "apikey": Api_key,
        "id" : id                                      
        }                                                                 
  }  
  return parameters

def getSunTime(timestring):
  if len(timestring) == 3:
   #timestring[1:] +  
   return  timestring[0:1] + 'h' + timestring[-2:]
  else:
    return timestring[0:2] + 'h' + timestring[-2:]  

def getDirWind(WindDir):
  #D'après https://www.campbellsci.com/blog/convert-wind-directions
  Direction = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW","N"]
  CompassDir = int(round((math.fmod(WindDir,360))/ 22.5,0)+1)
  return Direction[CompassDir]

def findIcone(condition_id):
  
  # Toutes les conditions renvoyees par le plugin météo de Jeedom https://github.com/jeedom/plugin-weather/blob/beta/core/class/weather.class.php
  # Prevision d apres le code openweathermap.org
  # Icones Open Source https://github.com/kickstandapps/WeatherIcons
  # Icones température, humidité, pression atmosphérique récupérées sur https://icones8.fr
  if condition_id >= 200 and condition_id = 300 and condition_id = 500 and condition_id = 520 and condition_id = 600 and condition_id = 700 and condition_id = 800 and condition_id