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) |
noir, blanc |
2 |
200×200 |
27.60 × 27.60 |
48.0 × 33.0 |
2 |
SPI |
x |
|
rouge, noir, blanc |
2 |
200×200 |
27.60 × 27.60 |
48.0 × 33.0 |
8 |
SPI |
x |
|
noir, blanc |
2 |
250×122 |
48.55 × 23.71 |
65.0 × 30.2 |
2 |
SPI |
√ |
|
rouge, noir, blanc |
2 |
212×104 |
48.55 × 23.71 |
65.0 × 30.2 |
15 |
SPI |
√ |
|
noir, blanc |
2 |
264×176 |
57.29 × 38.19 |
85.0 × 56.0 |
6 |
SPI |
√ |
|
rouge, noir, blanc |
2 |
264×176 |
57.29 × 38.19 |
85.0 × 56.0 |
15 |
SPI |
√ |
|
noir, blanc |
2 |
296×128 |
66.89 × 29.05 |
89.5 × 38.0 |
2 |
SPI |
x |
|
rouge, noir, blanc |
2 |
296×128 |
66.89 × 29.05 |
89.5 × 38.0 |
15 |
SPI |
x |
|
noir, blanc |
2 |
400×300 |
84.80 × 63.60 |
103.0 × 78.5 |
4 |
SPI |
x |
|
rouge, noir, blanc |
2 |
400×300 |
84.80 × 63.60 |
103.0 × 78.5 |
15 |
SPI |
x |
|
noir, blanc |
4 |
800×600 |
88.00 × 66.00 |
118.0 × 75.0 |
1.5 |
UART |
x |
|
noir, blanc |
2 |
640×384 |
163.20×97.92 |
170.2×111.2 |
6 |
SPI |
√ |
|
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 <= 299:
return 'Storm'
if condition_id >= 300 and condition_id <= 399:
return 'Haze'
if condition_id >= 500 and condition_id <= 510:
return 'PartlySunny'
if condition_id >= 520 and condition_id <= 599:
return 'Rain'
if condition_id >= 600 and condition_id <= 699 or condition_id == 511:
return 'Snow'
if condition_id >= 700 and condition_id <= 799:
return 'wind'
if condition_id >= 800 and condition_id <= 899:
return 'Cloud'
if condition_id == 800:
return 'Sun'
def getDataFromJeedom():
# ID des commandes de l'équipement météo
idCmd = {
"conditiontxt": "17",
"condition" : "18",
"leverSoleil" : "14",
"coucherSoleil" : "13",
"pa" : "10",
"humidite" : "9",
"tempMin" : "15",
"tempMax" : "16",
"vitVent" : "11",
"dirVent" : "12",
"conditionJ1" : "28",
"conditionJ2" : "30",
"conditionJ3" : "32",
"conditionJ4" : "34",
"condJ1Txt" : "27",
"condJ2Txt" : "29",
"condJ3Txt" : "31",
"condJ4Txt" : "33",
"tempMinJ1" : "19",
"tempMinJ2" : "21",
"tempMinJ3" : "23",
"tempMinJ4" : "25",
"tempMaxJ1" : "20",
"tempMaxJ2" : "22",
"tempMaxJ3" : "24",
"tempMaxJ4" : "26"
}
#Récupère le nom de la ville, modifier le 3 par l'ID de l'équipement météo
_parameters = updateParameter(3, "eqLogic::byId")
response = requests.post(url, data=json.dumps(_parameters), headers=headers).json()
prevision['city'] = response['result']['name']
#Récupère les prévisions
for key, value in idCmd.iteritems():
_parameters = updateParameter(value, "cmd::byId")
response = requests.post(url, data=json.dumps(_parameters), headers=headers).json()
#print response
if ( key == 'condition' or key == 'conditionJ1' or key == 'conditionJ2' or key == 'conditionJ3' or key == 'conditionJ4'):
prevision[key] = findIcone(response['result']['currentValue'])
print 'condition_id '+ str(response['result']['currentValue']) + " => " + str(prevision[key])
else:
prevision[key] = {
'value' : response['result']['currentValue'],
'unit' : response['result']['unite'],
}
print prevision
# Dessine l'écran du jour
def updateFrame1():
mask = Image.new('1', (EPD_HEIGHT,EPD_WIDTH), 255)
draw = ImageDraw.Draw(mask)
#Format date heure en Python https://www.cyberciti.biz/faq/howto-get-current-date-time-in-python/
#Entete
condition = Image.open(folder_img + prevision['condition'] + '.png')
condition = condition.resize((H_condition,W_condition))
mask.paste(condition, (0,0), condition)
mask.paste(lever, (W_condition + Bord,70), lever)
mask.paste(coucher, (W_condition + 90,70), coucher)
date = unicode(time.strftime("%a %d %B") + " " + time.strftime("%H:%M"),'UTF-8')
draw.text((W_condition + Bord,5), date, font = fontMedium, fill = 0)
condGauche = prevision['conditiontxt']['value'][0:16]
condDroite = prevision['conditiontxt']['value'][16:]
draw.text((W_condition + Bord,25), condGauche, font = fontBig, fill = 0)
draw.text((W_condition + Bord,45), condDroite, font = fontBig, fill = 0)
draw.text((W_condition + 45,75), getSunTime(str(prevision['leverSoleil']['value'])), font = fontSmall, fill = 0)
draw.text((W_condition + 125,75), getSunTime(str(prevision['coucherSoleil']['value'])), font = fontSmall, fill = 0)
#Prévision du jour en détail
#Tourne la bousole dans la direction du vent
direction.rotate(float(prevision['dirVent']['value']))
mask.paste(temperature, (Bord,110), temperature)
mask.paste(humidity, (Col1,110), humidity)
mask.paste(pressure, (Col2,110), pressure)
mask.paste(direction, (Col3,110), direction)
draw.text((Bord,LiBaCd), str(prevision['tempMax']['value']) + '%C', font = fontMedium, fill = 0)
draw.text((Col1,LiBaCd), str(prevision['humidite']['value'])+'%', font = fontMedium, fill = 0)
draw.text((Col2,LiBaCd), str(prevision['pa']['value'])+str(prevision['pa']['unit']), font = fontMedium, fill = 0)
draw.text((Col3 + 35,110), getDirWind(float(prevision['dirVent']['value'])), font = fontMedium, fill = 0)
draw.text((Col3,LiBaCd), str(prevision['vitVent']['value'])+str(prevision['vitVent']['unit']),font = fontMedium, fill = 0)
#Lignes
draw.line((0,H_condition,EPD_HEIGHT,H_condition), fill=0)
draw.line((0,LiBaCd + 20,EPD_HEIGHT,LiBaCd + 20), fill=0)
draw.line((W_condition,0,W_condition,H_condition), fill=0)
#Bas de page
draw.text((Bord,EPD_WIDTH - 18), str(prevision['city']),font = fontMedium, fill = 0)
draw.text((180,EPD_WIDTH - 17), "projetsdiy.fr",font = fontSmall, fill = 0)
draw.ellipse((97,EPD_WIDTH - DiamPastille - 2,107,EPD_WIDTH - 2), fill=0, outline=0)
draw.ellipse((117,EPD_WIDTH - DiamPastille - 2,127,EPD_WIDTH - 2), fill=255, outline=0)
draw.ellipse((137,EPD_WIDTH - DiamPastille - 2,147,EPD_WIDTH - 2), fill=255, outline=0)
draw.ellipse((157,EPD_WIDTH - DiamPastille - 2,167,EPD_WIDTH - 2), fill=255, outline=0)
out = mask.rotate(90)
out.save('frame1.bmp',"bmp")
if modeTest == False:
epd.display_frame(epd.get_frame_buffer(out))
#Prévisions des 4 prochains jours
def updateFrame2():
mask = Image.new('1', (EPD_HEIGHT,EPD_WIDTH), 255)
date = datetime.now()
draw = ImageDraw.Draw(mask)
pasHoriz = 16
#Format date heure en Python https://www.cyberciti.biz/faq/howto-get-current-date-time-in-python/
#Entete
draw.text((Bord,0), unicode("Prévisions à 4 jours",'utf-8'), font = fontBig, fill = 0)
draw.line((0,2*Bord + H_Big,EPD_HEIGHT,2*Bord + H_Big), fill=0)
draw.line((Col1, 2*Bord + H_Big, Col1, EPD_WIDTH - 15), fill=0)
draw.line((Col2, 2*Bord + H_Big, Col2, EPD_WIDTH - 20), fill=0)
draw.line((Col3, 2*Bord + H_Big, Col3, EPD_WIDTH - 20), fill=0)
draw.line((0,EPD_WIDTH - 15,EPD_HEIGHT,EPD_WIDTH - 15), fill=0)
#J+1
date = datetime.now() + timedelta(days=1)
draw.text((Bord, 3*Bord + H_Big), unicode(date.strftime("%A"),'utf-8'), font = fontMedium, fill = 0)
c1 = Image.open(folder_img + prevision['conditionJ1'] + '.png')
c1 = c1.resize((H_icone * 2,W_icone * 2))
mask.paste(c1, (Bord, 5*Bord + H_Big), c1)
prev1 = prevision['condJ1Txt']['value'][0:12]
prev2 = prevision['condJ1Txt']['value'][12:24]
print prev2
draw.text((Bord, 5 * pasHoriz), prev1, font = fontSmall, fill = 0)
draw.text((Bord, 6 * pasHoriz), prev2, font = fontSmall, fill = 0)
draw.text((Bord, 7 * pasHoriz), unicode(str(prevision['tempMinJ1']['value'])+'°C','utf-8'), font = fontMedium, fill = 0)
draw.text((Bord, 8 * pasHoriz), unicode(str(prevision['tempMaxJ1']['value'])+'°C','utf-8'), font = fontMedium, fill = 0)
#J+2
date = datetime.now() + timedelta(days=2)
draw.text((Bord + Col1, 3*Bord + H_Big), unicode(date.strftime("%A"),'utf-8'), font = fontMedium, fill = 0)
c1 = Image.open(folder_img + prevision['conditionJ2'] + '.png')
c1 = c1.resize((H_icone * 2,W_icone * 2))
mask.paste(c1, (Bord + Col1, 5*Bord + H_Big), c1)
prev1 = prevision['condJ2Txt']['value'][0:12]
prev2 = prevision['condJ2Txt']['value'][12:24]
draw.text((Bord + Col1, 5 * pasHoriz), prev1, font = fontSmall, fill = 0)
draw.text((Bord + Col1, 6 * pasHoriz), prev2, font = fontSmall, fill = 0)
draw.text((Bord + Col1, 7 * pasHoriz), unicode(str(prevision['tempMinJ2']['value'])+'°C','utf-8'), font = fontMedium, fill = 0)
draw.text((Bord + Col1, 8 * pasHoriz), unicode(str(prevision['tempMaxJ2']['value'])+'°C','utf-8'), font = fontMedium, fill = 0)
#J+3
date = datetime.now() + timedelta(days=3)
draw.text((Bord + Col2, 3*Bord + H_Big), unicode(date.strftime("%A"),'utf-8'), font = fontMedium, fill = 0)
c1 = Image.open(folder_img + prevision['conditionJ3'] + '.png')
c1 = c1.resize((H_icone * 2,W_icone * 2))
mask.paste(c1, (Bord + Col2, 5*Bord + H_Big), c1)
prev1 = prevision['condJ3Txt']['value'][0:12]
prev2 = prevision['condJ3Txt']['value'][12:24]
draw.text((Bord + Col2, 5 * pasHoriz), prev1, font = fontSmall, fill = 0)
draw.text((Bord + Col2, 6 * pasHoriz), prev2, font = fontSmall, fill = 0)
draw.text((Bord + Col2, 7 * pasHoriz), unicode(str(prevision['tempMinJ3']['value'])+'°C','utf-8'), font = fontMedium, fill = 0)
draw.text((Bord + Col2, 8 * pasHoriz), unicode(str(prevision['tempMaxJ3']['value'])+'°C','utf-8'), font = fontMedium, fill = 0)
#J+4
date = datetime.now() + timedelta(days=4)
draw.text((Bord + Col3, 3*Bord + H_Big), unicode(date.strftime("%A"),'utf-8'), font = fontMedium, fill = 0)
c1 = Image.open(folder_img + prevision['conditionJ4'] + '.png')
c1 = c1.resize((H_icone * 2,W_icone * 2))
mask.paste(c1, (Bord + Col3, 5*Bord + H_Big), c1)
prev1 = prevision['condJ4Txt']['value'][0:12]
prev2 = prevision['condJ4Txt']['value'][12:24]
draw.text((Bord + Col3, 5 * pasHoriz), prev1, font = fontSmall, fill = 0)
draw.text((Bord + Col3, 6 * pasHoriz), prev2, font = fontSmall, fill = 0)
#draw.text((Bord + Col1, 6 * pasHoriz), str(prevision['condJ4Txt']['value'][0:10]), font = fontSmall, fill = 0)
draw.text((Bord + Col3, 7 * pasHoriz), unicode(str(prevision['tempMinJ4']['value'])+'°C','utf-8'), font = fontMedium, fill = 0)
draw.text((Bord + Col3, 8 * pasHoriz), unicode(str(prevision['tempMaxJ4']['value'])+'°C','utf-8'), font = fontMedium, fill = 0)
#Bas de page
draw.text((Bord,EPD_WIDTH - 18), str(prevision['city']),font = fontMedium, fill = 0)
draw.text((180,EPD_WIDTH - 17), "projetsdiy.fr",font = fontSmall, fill = 0)
draw.ellipse((97,EPD_WIDTH - DiamPastille - 2,107,EPD_WIDTH - 2), fill=255, outline=0)
draw.ellipse((117,EPD_WIDTH - DiamPastille - 2,127,EPD_WIDTH - 2), fill=0, outline=0)
draw.ellipse((137,EPD_WIDTH - DiamPastille - 2,147,EPD_WIDTH - 2), fill=255, outline=0)
draw.ellipse((157,EPD_WIDTH - DiamPastille - 2,167,EPD_WIDTH - 2), fill=255, outline=0)
out = mask.rotate(90)
out.save('frame2.bmp',"bmp")
if modeTest==False:
epd.display_frame(epd.get_frame_buffer(out))
def main():
if modeTest:
getDataFromJeedom()
updateFrame1()
#updateFrame2()
else:
while True:
key1state = GPIO.input(key1)
key2state = GPIO.input(key2)
key3state = GPIO.input(key3)
key4state = GPIO.input(key4)
if key1state == False:
print('Update frame 1')
getDataFromJeedom()
updateFrame1()
time.sleep(0.5)
if key2state == False:
print('Update frame 2')
getDataFromJeedom()
updateFrame1()
time.sleep(0.5)
if key3state == False:
print('Key3 Pressed')
time.sleep(0.2)
if key4state == False:
print('Key4 Pressed')
if __name__ == "__main__":
#Met à jour l'écran au démarrage
getDataFromJeedom()
#updateFrame2()
updateFrame1()
#puis attend un événement sur les touches Key1 à Key4 (sur un Raspberry Pi uniquement)
main()
Remarque. J’ai juste un doute sur l’orientation correcte de la boussole en fonction de la direction du vent. Je compte sur vous pour le vérifier.
Démo du dashboard météo ePaper pour Jeedom
Il ne reste plus qu’à lancer le script. Au premier lancement, le script génère et affiche immédiatement la première page de la station météo. Pour afficher le second panneau, appuyer sur le bouton Key2. Les autres écrans sont disponibles pour afficher d’autres informations en provenance de Jeedom. Etat des lampes, des portes, niveau de CO2, température, consommation électrique…
La première page (Key1) récapitule la météo du jour
Le second écran (key2) permet d’afficher les prévisions à 4 jours. Et oui, c’est l’hivers 🙁
La même chose en vrai avec l’écran Waveshare 2,7” installé sur un Raspbery Pi3 qui fait tourner le serveur Jeedom !
Voilà, il ne plus qu’à faire la même chose avec Domoticz ! J’espère que ce projet vous donnera plein de nouvelles idées. Pour le moment, j’ai réalisé les deux tutoriels avec un Raspberry Pi 3. L’intérêt principal des écrans ePaper étant l’absence de consommation électrique après le rafraichissement de l’affichage, il est temps de le tester avec un micro-contrôleur ESP8266 ou ESP32.
- Flask + Bootstrap. Interface HTML pour projets Python sans effort
- Node-RED. Ajouter un widget météo au Dashboard connecté à OpenWeatherMap avec du code HTML / Angular
- Node-RED. Créer un dashboard pour objet connecté. Bouton, liste, interrupteur, slider, formulaire (Partie 1)
- Node-RED. Dashboard pour objet connecté. Jauges, graphiques, notifications, template HTML (Partie 2)
- Dashboard Grafana. Monitoring d’un panneau solaire en WiFi avec un ESP8266
- Installer Grafana sur macOS et Raspbian pour Raspberry Pi. Exemple de dashboard pour objet connecté MySensors avec Node-RED et InfluxDB
J’ai quasiment terminé ma station, encore merci pour ce tuto. Finalement je suis parti sur l’utilisation de l’api de météo France pour avoir les prevision sur 6 jours avec en plus la possibilité d’avoi Matin/Midi/Soir/Nuit. J’utilise Jeedom pour les plugins virtuel de mes capteurs de température à base de wemos d1 mini pro + DHT22 ou AM2302.
Sinon question qui n’a rien à voir, quel logiciel utilises-tu pour créer les schémas avec des Breadboard sur tes autres tutos ?
Merci beaucoup pour ton retour. J’utilise Fritzing pour les schémas https://fritzing.org/download
Bonjour,
Est ce qu’il est possible de modifier l’écran du jour ou celui des prévisions afin d’ajouter les températures interieure/extérieure récupérées via un Gateway MySensors ou xaomi ?
Merci
Bonjour Philipe, oui absolument, tout est possible. Inspirez vous de la méthode getDataFromJeedom qui permet de récupérer les données de l’équipements qui vous intéresse (quel que soit l’accessoire). Pour mieux comprendre le fonctionnement de l’API JSON RPC de Jeedom, je vous conseille de lire cet article également. N’hésitez pas à partager votre projet. A très bientôt !
Finalement j’ai trouvé, il suffit de faire comme ci dessous. Attention ça ne marche qu’en python2.X :
if ( key == ‘condition’ or key == ‘conditionJ1’ or key == ‘conditionJ2’ or key == ‘conditionJ3’ or key == ‘conditionJ4’):
prevision[key] = findIcone(response[‘result’][‘currentValue’])
if str(type(response[‘result’][‘currentValue’])) == “”:
reponse_utf8 = response[‘result’][‘currentValue’].encode(“utf-8″)
print ‘condition_id ‘+ str(reponse_utf8) + ” => ” + str(prevision[key])
else:
print ‘condition_id ‘+ str(response[‘result’][‘currentValue’]) + ” => ” + str(prevision[key])
Bah, tu as été plus rapide que moi 😉 Merci pour l’info !
Bonjour, pas de soucis une fois que j’aurais terminé mon projet, je partagerai des photos et le code associé si ça interese quelqu’un. Je suis parti sur un ecran 7.5′ avec des boutons deportés branchés sur les GPIOs directement, le 2.7′ me semblait un peu petit pour mon projet 😉
Par contre je rencontre un problème avec le code, j’ai l’erreur suivante qui apparait :
File “weatherstation.py”, line 349, in
getDataFromJeedom()
File “weatherstation.py”, line 174, in getDataFromJeedom
print ‘condition_id ‘+ str(response[‘result’][‘currentValue’]) + ” => ” + str(prevision[key])
UnicodeEncodeError: ‘ascii’ codec can’t encode character u’\xe9′ in position 6: ordinal not in range(128)
As-tu trouvé une solution ?
Quand tu propose de passer par une variable intermèdiaire, c’est du genre ‘toto = response[‘result’][‘currentValue’]’ et après de placer toto dans le ‘str(…)’ ?
Merci pour ton retour.
Bonjour,
merci pour ce beau projet .
j’ai tout assemblé (écran 2.7 + raspberry pi 3 sous raspbian) mais lorsque je lance le programme complet j’ai une erreur :
mode epd2in7
condition_id 500 => PartlySunny
condition_id 500 => PartlySunny
condition_id 500 => PartlySunny
condition_id 500 => PartlySunny
condition_id 500 => PartlySunny
{‘dirVent’: {‘unit’: u”, ‘value’: u’220′}, ‘condJ4Txt’: {‘unit’: u”, ‘value’: u’Lxe9gxe8re pluie’}, ‘city’: u’det fumxe9es salon’, ‘condJ3Txt’: {‘unit’: u”, ‘value’: u’Lxe9gxe8re pluie’}, ‘vitVent’: {‘unit’: u’km/h’, ‘value’: 11.16}, ‘pa’: {‘unit’: u’Pa’, ‘value’: 993}, ‘conditiontxt’: {‘unit’: u”, ‘value’: u’Lxe9gxe8re pluie’}, ‘leverSoleil’: {‘unit’: u”, ‘value’: u’730′}, ‘tempMin’: {‘unit’: u’xb0C’, ‘value’: 4.9}, ‘coucherSoleil’: {‘unit’: u”, ‘value’: u’1844′}, ‘condJ1Txt’: {‘unit’: u”, ‘value’: u’Lxe9gxe8re pluie’}, ‘conditionJ3’: ‘PartlySunny’, ‘conditionJ1’: ‘PartlySunny’, ‘condition’: ‘PartlySunny’, ‘tempMax’: {‘unit’: u’xb0C’, ‘value’: 7.7}, ‘tempMinJ1’: {‘unit’: u’xb0C’, ‘value’: 2.7}, ‘tempMinJ3’: {‘unit’: u’xb0C’, ‘value’: 2.8}, ‘tempMinJ2’: {‘unit’: u’xb0C’, ‘value’: 5.4}, ‘tempMinJ4’: {‘unit’: u’xb0C’, ‘value’: 2.6}, ‘conditionJ4’: ‘PartlySunny’, ‘humidite’: {‘unit’: u’%’, ‘value’: 94}, ‘conditionJ2’: ‘PartlySunny’, ‘tempMaxJ4’: {‘unit’: u’xb0C’, ‘value’: 7.8}, ‘tempMaxJ3’: {‘unit’: u’xb0C’, ‘value’: 5.9}, ‘tempMaxJ2’: {‘unit’: u’xb0C’, ‘value’: 8.2}, ‘tempMaxJ1’: {‘unit’: u’xb0C’, ‘value’: 9.8}, ‘condJ2Txt’: {‘unit’: u”, ‘value’: u’Lxe9gxe8re pluie’}}
Traceback (most recent call last):
File “meteo.py”, line 343, in
updateFrame2()
File “meteo.py”, line 299, in updateFrame2
draw.text((Bord,EPD_WIDTH – 18), str(prevision[‘city’]),font = fontMedium, fill = 0)
UnicodeEncodeError: ‘ascii’ codec can’t encode character u’xe9′ in position 7: ordinal not in range(128)
Et puis rien .
merci beaucoup par avance
Bonjour Fred et merci beaucoup. Le problème ne vient pas du code (enfin pas directement) mais de la manière dont Jeedom renvoi les données. Les données ne sont renvoyées avec un encodage UTF8 standard ce qui pose des problèmes. Normalement en forçant l’encodage au début du code Python (# coding: utf-8) ainsi qu’en indiquant l’encodage UTF8 pour construire les chaines ça ne devrait pas se produire. Je vais essayer de reproduire et trouver une solution.
bonjour,
j’ai bien mis au début du code “# coding: utf-8”, mais quand tu dit “ainsi qu’en indiquant l’encodage UTF8 pour construire les chaines..” y a t-il quelque chose à faire dans Jeedom ?
Merci
Non tout est bon, il faudrait intervenir directement dans le code de Jeedom. Il faut que je reprenne mon code et que je trouve une solution plus fiable pour gérer les caractères ascii accentués. Le problème, c’est que j’ai testé le code sur quelques jours, le temps d’écrire le tuto, donc j’ai pas été confronté à tous les cas 😀
Bonjour,
Super projet, cependant après quelques déboires pour la librairie python_pil, impossible de faire fonctionner le code. J’ai le beau message d’erreur qui suit:
“Traceback (most recent call last):
File “jeedom.py”, line 342, in
getDataFromJeedom()
File “jeedom.py”, line 158, in getDataFromJeedom
prevision[‘city’] = response[‘result’][‘name’]
KeyError: ‘result’
Une petite idée ?
@projetsdiy:disqus Bon j’ai beau eu chercher, je n’ai rien trouver. Alors je revient vers vous. Auriez-vous une idée ?