La semaine dernière, 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. Il me fallait un peu de temps pour apprendre à maitriser la librairie Python Imaging Library (ou le fork Pillow). Je vous propose dans ce nouveau tutoriel d’aller beaucoup plus loin. Nous allons 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.
Sommaire
- 1 Matériel utilisé pour la station météo ePaper
- 2 Librairies Python à installer
- 3 La librairie Python Pil / Pillow rapidement
- 4 Récupérer des images et des fonts Open Source
- 5 Installer et configurer le plugin météo sur Jeedom
- 6 Activer l’API JSON RPC et récupérer la clé API
- 7 Comment récupérer les prévisions météo avec l’API JSON RPC de Jeedom ?
- 8 Code python complet
- 9 Le dashboard météo ePaper pour Jeedom obtenu
Matériel utilisé pour la station météo ePaper
Pour ce projet, j’ai utilisé le matériel suivant. L’écran mesure 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 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 Raspberry Pi 2B/3B/Zero/Zero W. Les autres écrans doivent être connectés à l’aide de Jumpers.
Librairies Python à installer
Si vous débutez avec les écrans ePaper de Waveshare, je vous conseille de lire l’article précédent qui explique comment les utiliser avec un ou deux exemples simples.
Le driver epd (pour EPaper Display j’imagine) ne fait “que” l’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 brew
brew install pil request
La librairie Python Pil / Pillow rapidement
La librairie 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" } #Recupè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.
Le dashboard météo ePaper pour Jeedom obtenu
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.
- Affichage OLED SSD1306 I2C sur Raspberry Pi. Code Python d’une mini station météo connectée à Jeedom avec la librairie Adafruit
- #Test de l’écran LCD 3.5” HDMI tactile (via le GPIO) avec boitier acrylique pour Raspberry Pi 3 sous Raspbian (480×320 à 1920×1080 pixels)
- #Test de l’écran LCD tactile 7 pouces 1024×600 pixels Waveshare avec support acrylique pour Raspberry Pi
- Station météo avec affichage ePaper (Dashboard) pour Jeedom sur Raspberry Pi (via l’API JSON RPC)
- #Test de l’écran ePaper (eInk) Waveshare 2,7 pouces sur Raspberry Pi en Python
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 ?