Affichage OLED SSD1306 I2C sur Raspberry Pi. Code Python d’une mini station météo connectée à Jeedom avec la librairie Adafruit

Aujourd’hui, je vous propose de reprendre le code python de la station météo connectée à Jeedom réalisée avec l’écran ePaper de Waveshare et l’adapter à un écran OLED SSD1306. Comme nous allons le voir, la programmation est très similaire. On trouve ces écrans miniature de 0,96” pour moins de 5€. Choisissez de préférence un écran de type I2C qui nécessite moins de câblage (4 fils) que le bus SPI. Pour ce tutoriel, nous allons utiliser la librairie Python pour les micro-contrôleurs SSD1306 (tous les tutoriels sur le SSD1306) par Adafruit. Dans son principe, elle est très similaire au driver pour écran ePaper de Waveshare. La librairie s’occupe d’initialiser et de rafraichir l’écran. A nous de construire l’image aux dimensions de l’écran à l’aide de la librairie Python Imaging (ou son fork Pillow). Ce n’est pas la librairie la plus avancée mais elle fait le job sans trop de compétences à acquérir.

Matériel utilisé pour le tutoriel

Pour ce tutoriel, vous aurez besoin d’un écran OLED 0,96” de 128×64 pixels sur bus I2C ainsi que d’un Raspberry Pi (modèle 2 ou 3).

7,31€
1 nouveau de 7,31€
Relevé le 10 décembre 2018 14 h 58 min
Amazon.fr
Livraison gratuite
12,27€
2 nouveau de 12,27€
Relevé le 10 décembre 2018 14 h 58 min
Amazon.fr
Livraison gratuite

Vérifier que Python est installé sur Raspbian

Le code de ce tutoriel a été écrit en Python 2.7. Ouvrez le Terminal ou connectez vous en SSH et exécutez la commande python –version pour vérifier que vous disposez de la bonne version. Python 2.7 est pré-installé sur Raspbian. Vous devriez obtenir quelque chose de similaire.

python --version
Python 2.7.9

Câblage de l’écran OLED I2C

ssd1306 oled i2c circuit raspberry pi3 python adafruit

Installer les librairies Python Adafruit SSD1306 et PIL

La librairie Python d’Adafruit est disponible sur GitHub. On va la récupérer à l’aide de la commande git. La commande git permet de récupérer ou de synchroniser du code source sur GitHub. Commencez par installer la commande git sur Raspbian

sudo apt-get install git

Ensuite on récupère le code source et on installe la librairie. Elle sera ainsi accessible depuis n’importe quel projet Python

git clone https://github.com/adafruit/Adafruit_Python_SSD1306.git
cd Adafruit_Python_SSD1306
sudo python setup.py install

Enfin, si vous l’avez pas encore fait, installez les librairies Python-Imaging (PIL). Pour installer Pil, utilisez la commande pip. Vous aurez peut être besoin de faire précéder la commande pip d’un sudo.

sudo pip install python-pil request

Fonctionnement de la librairie Python Adafruit SSD1306

Créez un nouveau script python (nano testssd1306.py  par exemple). Pour utiliser un écran OLED, on doit déjà importer les deux librairies Adafruit_GPIO et Adafruit_SSD1306. On en profite pour indiquer à Python que les caractères spéciaux seront encodés en UTF-8 au début du script (coding: UTF-8). Cela va nous permettre d’envoyer et d’afficher des caractères accentués sur l’écran OLED.

# coding: UTF-8
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306

Pour générer l’image qui sera ensuite affichée sur l’écran OLED, on va avoir besoin des plusieurs méthodes de la librairie Python-Imaging. Image

  • Image, pour créer, manipuler les images et les masques
  • ImageDraw, pour dessiner du texte, des formes géométriques…
  • ImageFont, pour charger des polices TrueType

Maintenant, on peut initialiser l’écran. Si votre écran OLED ne dispose pas de broche RST (Reset, indiquez 0 comme dans mon cas). La méthode Adafruit_SSD1306.SSD1306_128_64 permet de créer un objet. On doit choisir la méthode qui correspond à la résolution de l’écran utilisé. La résolution est codée ‘en dure’ dans le driver d’Adafruit. La librairie d’Adafruit propose les dimensions suivantes :

  • 128×64 pixels, SSD1306_128_64
  • 128×32 pixels, SSD1306_128_32
  • 96×16 pixels, SSD1306_96_16

Pour ce tutoriel, j’ai utilisé un écran très habituel de 128×64 pixels sans broche de reset

# Créé un objet display - Create display object 
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

# Initialise l'écran - init display
disp.begin()

# Efface l'écran - Clear display.
disp.clear()
disp.display()

Maintenant, on va récupérer la dimension de l’écran et créer un masque sur lequel on va écrire le classique “Hello World”.

width = disp.width
height = disp.height
image = Image.new('1', (width, height))

# On créé un objet sur lequel on va dessiner - Get drawing object to draw on image.
draw = ImageDraw.Draw(image)
# Charge la font par défaut - load default font
font = ImageFont.load_default()

# On écrit du texte dans le coin de l'écran en blanc- Draw some text 
draw.text((0,0), 'Hello World', font=font, fill=255)

La méthode image de la librairie Adafruit permet d’actualiser le buffer avec l’image que l’on vient de créer avec la librairie PIL. Ensuite un appel de la méthode display() permet d’actualiser l’affichage de l’écran OLED.

# Actualise l'affichage - Update display
disp.image(image)
disp.display()

Et voilà ce qu’on obtient.

Pour aller plus vite, collez ce code dans le script.

# coding: UTF-8
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306

import Image
import ImageDraw
import ImageFont

RST = 0

# Créé un objet display - Create display object 
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)

# Initialise l'écran - init display
disp.begin()

# Efface l'écran - Clear display.
disp.clear()
disp.display()

# Créé un image avec un codage des couleurs sur 1 bit (noir et blanc)
# Create blank image for drawing with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))

# On créé un objet sur lequel on va dessiner 
# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)
# Charge la font par défaut - load default font
font = ImageFont.load_default()

# On écrit du texte - Draw some text 
draw.text((0,0), 'Hello World', font=font, fill=255)
draw.text((0,50), 'projetsdiy.fr', font=font, fill=255)

# Actualise l'affichage - Update display
disp.image(image)
disp.display()

# Enregistre l'image en local (optionnel) - Save the image (optional)
image.save("demo.bmp","bmp")

Pour pouvoir accéder au GPIO, il est nécessaire d’exécuter le code avec le droit root, il faudra donc faire précéder la commande d’un sudo comme ceci

sudo python testssd1306.py

Mini station météo connectée à Jeedom sur un Raspberry Pi

Reprenons maintenant le code de la mini station météo connectée à Jeedom développée pour l’affichage ePaper. Le principe va être très similaire. La grosse différence réside dans l’inversion des couleurs. Sur un écran ePaper, il est plus naturel d’avoir un fond blanc (bien que ce soit une question de goût plus que technique).

jeedom epaper waveshare display dashboard weather station

Avec l’écran OLED, on aura un meilleur rendu en laissant le fond noir (les pixels sont éteints). On va donc devoir soit disposer d’images blanches sur fond noir, soit inverser les couleurs en utilisant la librarie Python Imaging. C’est ce que nous allons faire ici avec la fonction ImageOps. Seul problème, la méthode ImageOps n’est pas capable de gérer des images ayant un fond transparent. je n’ai pas trouvé de fonction pour retirer la transparence de l’image (si quelqu’un connais, n’hésitez pas à utiliser les commentaires pour nous expliquer)

Je ne vais pas reprendre ici les explications sur la façon de récupérer les données météo à l’aide de l’API JSON RPC de Jeedom, je vous laisse lire l’article précédent. Vous aurez également besoin d’installer et de configurer le Widget météo de Jeedom sur votre ville.

Comme je l’ai annoncé dans l’introduction, il faut inverser les couleurs (noir/blanc). Pour cela, j’ai été obligé de supprimer la transparence (couche alpha) des pictogrammes. Pour inverser les couleurs, on dispose de la méthode ImageOps de PIL. Elle a besoin que d’un paramètre unique, l’image dont on veut inverser les couleurs.

#charge l'image - load image
condition = Image.open(folder_img + prevision['condition'] + '.png')
# Redimensionne l'image - Resize image
condition = condition.resize((H_condition,W_condition))
# Inverse les couleurs - Invert colors
condition = ImageOps.invert(condition)
# Colle l'image sur le maque - past image on the mask
mask.paste(condition, (0,0))

Autre remarque, pour pouvoir afficher des caractères accentués, il est nécessaire d’indiquer au début du script que les caractères sont encodés en UTF-8 avec l’option # coding: UTF-8. Ensuite si on veut pouvoir afficher des caractères spéciaux comme le degré ‘°’, il faut l’encoder manuellement lorsqu’on construit la chaine avec la méthode unicode(chaine_a_encoder,’utf-8′)

'Max.' + str(prevision['tempMax']['value']) + unicode("°C",'utf-8')

 

Créez un nouveau script (nano meteojeedomssd1306.py) et collez le code suivant. 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 doivent être stockées dans le dossier image.

Téléchargez les images 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 images.

# coding: UTF-8
import time
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306

import Image
import ImageOps 
import ImageDraw
import ImageFont

import requests
import json
import locale

prevision = {}
H_condition = 50
W_condition = 50
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/'

lever = Image.open(folder_img + 'lever.png')
coucher = Image.open(folder_img + 'coucher.png')

disp = Adafruit_SSD1306.SSD1306_128_64(rst = 0)

disp.begin()

# Clear display.
disp.clear()
disp.display()

# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
font = ImageFont.load_default()

locale.setlocale(locale.LC_TIME,'')

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 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():
    # Example echo method
    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
    _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

def updateFrame1():
    mask = Image.new('1', (width,height))   
    
    draw = ImageDraw.Draw(mask)

    condition = Image.open(folder_img + prevision['condition'] + '.png')
    condition = condition.resize((H_condition,W_condition))
    condition = ImageOps.invert(condition)
    mask.paste(condition, (0,0))

    leverInv = lever.resize((W_condition / 3,H_condition / 3))
    leverInv = ImageOps.invert(leverInv)
    mask.paste(leverInv, (0,H_condition - 5))    

    coucherInv = coucher.resize((W_condition / 3,H_condition / 3))
    coucherInv = ImageOps.invert(coucherInv)
    mask.paste(coucherInv, (W_condition + 10,H_condition - 5))

    #date = unicode(time.strftime("%a %d %B") + "  " + time.strftime("%H:%M"),'UTF-8')
    draw.text((W_condition,0), prevision['conditiontxt']['value'][0:12], font = font, fill = 255)
    draw.text((W_condition,10), 'Max.' + str(prevision['tempMax']['value']) + unicode("°C",'utf-8'), font = font, fill = 255)
    draw.text((W_condition,20), 'Min.' + str(prevision['tempMin']['value']) + unicode("°C",'utf-8'), font = font, fill = 255)
    draw.text((W_condition,30), 'Hum.' + str(prevision['humidite']['value']) + unicode("%",'utf-8'), font = font, fill = 255)

    draw.text((W_condition / 2,H_condition), getSunTime(str(prevision['leverSoleil']['value'])), font = font, fill = 255)
    draw.text((W_condition * 1.8,H_condition), getSunTime(str(prevision['coucherSoleil']['value'])), font = font, fill = 255)

    disp.image(mask)
    disp.display()

    mask.save('frame1oled.bmp',"bmp")

def main():
    getDataFromJeedom()  
    updateFrame1()

if __name__ == "__main__":
    main()

Enregistrez avec la combinaison de touche CTRL + X puis O (ou Y). Exécutez le script avec la commande sudo python meteojeedomssd1306.py

Il y a plusieurs points de debogage dans le script qui permettent de visualiser les prévisions récupérées sur Jeedom

condition_id 801 => Cloud
condition_id 800 => Cloud
condition_id 800 => Cloud
condition_id 800 => Cloud
condition_id 500 => PartlySunny
{'dirVent': {'unit': u'', 'value': u'70'}, 'condJ4Txt': {'unit': u'', 'value': u'Peu nuageux'}, 'city': u'Paris', 'condJ3Txt': {'unit': u'', 'value': u'Ciel d\xe9gag\xe9'}, 'vitVent': {'unit': u'km/h', 'value': 9.36}, 'pa': {'unit': u'Pa', 'value': 1026}, 'conditiontxt': {'unit': u'', 'value': u'Ciel d\xe9gag\xe9'}, 'leverSoleil': {'unit': u'', 'value': u'756'}, 'tempMin': {'unit': u'\xb0C', 'value': 3.8}, 'coucherSoleil': {'unit': u'', 'value': u'1813'}, 'condJ1Txt': {'unit': u'', 'value': u'Ciel d\xe9gag\xe9'}, 'conditionJ3': 'Cloud', 'conditionJ1': 'Cloud', 'condition': 'Cloud', 'tempMax': {'unit': u'\xb0C', 'value': 4.9}, 'tempMinJ1': {'unit': u'\xb0C', 'value': -1.9}, 'tempMinJ3': {'unit': u'\xb0C', 'value': 0.9}, 'tempMinJ2': {'unit': u'\xb0C', 'value': -3.1}, 'tempMinJ4': {'unit': u'\xb0C', 'value': -1.1}, 'conditionJ4': 'Cloud', 'humidite': {'unit': u'%', 'value': 65}, 'conditionJ2': 'PartlySunny', 'tempMaxJ4': {'unit': u'\xb0C', 'value': 1.9}, 'tempMaxJ3': {'unit': u'\xb0C', 'value': 9.1}, 'tempMaxJ2': {'unit': u'\xb0C', 'value': 6.6}, 'tempMaxJ1': {'unit': u'\xb0C', 'value': 6.5}, 'condJ2Txt': {'unit': u'', 'value': u'L\xe9g\xe8re pluie'}}

Le script enregistre également l’image générée par le script et envoyée à l’écran OLED par la librairie Adafruit. C’est assez pratique pour la mise au point le temps de recevoir l’écran. Rien ne vous empêche également de mettre au point le script sur un autre ordinateur.

Et voilà le résultat obtenu

oled ssd1306 jeedom meteo adafruit raspberry pi

Ainsi que sur l’écran OLED relié en I2C au Raspberry Pi 3

 

7,31€
1 nouveau de 7,31€
Relevé le 10 décembre 2018 14 h 58 min
Amazon.fr
Livraison gratuite
12,27€
2 nouveau de 12,27€
Relevé le 10 décembre 2018 14 h 58 min
Amazon.fr
Livraison gratuite
Print Friendly, PDF & Email

Inscrivez-vous à la newsletter hebdomadaire

Aucun spam et aucun autre usage ne sera fait de votre email. Vous pouvez vous désinscrire à tout moment.

Comparateur de prix

Bons plans

Les offres suivantes se terminent bientôt. Utilisez le coupon indiqué pour profiter du prix promo

Domotique et objets connectés à faire soi-même