[Python] Rapport quotidien en pdf et transmission par mail

Vous avez créé un script LUA dont vous êtes fier, un .sh génial, un programme Python hors du commun, un Tuto, c'est ici que vous pouvez les partager.
Jose
Messages : 63
Inscription : 25 sept. 2014, 21:40

[Python] Rapport quotidien en pdf et transmission par mail

Message par Jose »

Bonjour à tous,

Je reprends un article que j'ai écris un peu à l'arrache aujourd'hui sur mon blog. :roll:

Regarder tous les jours, les beaux graphiques réalisés pour nous par domoticz ça va bien un moment. Recevoir des alertes par sms en cas de problème c’est du déjà vu. Je recherchais le moyen de recevoir par mail un rapport en pdf sur l’état de santé (à un instant T) de mon serveur domoticz ainsi qu'un certain nombre d'informations utiles pour moi.

Les utilisateurs domoticz trouveront ici l'information sur la date et l'heure de la mise en route du service ainsi que son pid et la conso mémoire.

Je laisse libre à chacun de juger l'utilité de ce type de rapport. L'idée de ce partage est de permettre d'explorer de nouvelles pistes.

Le rapport est un document pdf, tramis par mail via des tâches planifiées (cron).
rapport.JPG
rapport.JPG (102.87 Kio) Consulté 16221 fois
Les goûts et les couleurs ne se discutent pas. Y'a forcément mieux, l'idée ici je le répète le partage.

Le programme principal se nomme rapport.py (source plus bas - attention à certain décalage de lignes).

Dans la première partie, on retrouve des infos sur la mémoire, cpu, disque etc.
Dans la seconde, je place des informations pratiques. D'ailleurs, si j'oublie de commander des granulés ça va être ma fête :)

ça marche comment ?

La partie système est mise en œuvre grâce au module psutil https://github.com/giampaolo/psutil de python dont l'emploi le plus connu est le logiciel glances http://blog.nicolargo.com/2011/12/prese ... ances.htmlde Nicolargo.

Un script python lance des requêtes json au serveur domoticz pour la partie granulés, edf, batterie des modules z-wave.

Enfin, le pdf est créé grâce à la librairie fpdf http://www.fpdf.org/

Le code du programme rapport.py

Code : Tout sélectionner

#!/usr/bin/env python
# -*- coding:Utf-8 -*-
#
# Ce programme python prépare, met en forme et envoi par mail un document pdf reprenant des informations du serveur domoticz
# Utilise les modules psutil, request fpdf
# Version 1 de février 2015 par José
#
# Basé sur le travail de Christophe CARON  http://www.caron.ws/ pour la partie supervision psutils https://github.com/giampaolo/psutil
# Utilise le module de recherche de Patrice du site easydomoticz.fr
# Le code de la partie mail vient du site http://astuces.absolacom.com/developpement/envoyer-un-mail-avec-pieces-jointes-en-python/
# Merci à eux.
# Les paramètres liés à domoticz se trouvent sur un fichier (config.py) situé dans le même répertoire
#
##########
#
# Il faut préalablement installer les modules suivants:
# installer python-pip
# par sudo apt-get install python-pip
#
# installer request
# sudo pip install requests
#
# installer psutils
# sudo pip install psutil
#
# installer fpdf
# sudo apt-get install pypdf
#
###########

from fpdf import FPDF
import data
import datetime
import sys
import config
import time
import requests

# Récupération et formatage de la date pour donner le nom du fichier
datejour = datetime.datetime.now()
datejour = datejour.strftime('%d-%m-%Y %H-%M-%S')
title=u"Rapport d'état du serveur Domoticz"

# Classe PDF de pypdf
class PDF(FPDF):
        def header(self):
                #Arial bold 15
                self.set_font('Arial','B',15)
                #Calculate width of title and position
                w=self.get_string_width(title)+6
                self.set_x((210-w)/2)
                #Colors of frame, background and text
                self.set_draw_color(0,0,204)
                self.set_fill_color(255,153,102)
                self.set_text_color(0,0,0)
                #Thickness of frame (1 mm)
                self.set_line_width(1)
                #Title avec logo
                self.image('img/logo.jpg',10,6,30);
                self.cell(w,9,title,1,1,'C',1)
                #Line break
                self.ln(10)

        def footer(self):
                #Position at 1.5 cm from bottom
                self.set_y(-15)
                #Arial italic 8
                self.set_font('Arial','I',8)
                #Text color in gray
                self.set_text_color(128)
                #Page number
                self.cell(0,10,'Rapport du '+ datejour + ' - P'+str(self.page_no()),0,0,'C')

        def chapter_title(self,num,label):
                #Arial 11
                self.set_font('Arial','',11)
                #Background color
                self.set_fill_color(255,255,255)
                #Title avec image du rapsberry pi
                self.cell(0,5,label,0,1,'L',1)
                self.image('img/pi.jpg',110,42,65);
                #Line break
                self.ln(2)

        def chapter_body(self,name):
                #Arial 11
                self.set_font('Arial','',11)
                #Output justified text
                self.multi_cell(0,5,name)
                #Line break
                self.ln()
                #Mention in italics
                self.set_font('','I')

        def print_chapter(self,num,title,name):
                self.chapter_title(num,title)
                self.chapter_body(name)

        def chapitre_general_titre(self,num,texte):
                #Arial 11
                self.set_font('Arial','',11)
                #Background color
                self.set_fill_color(255,255,255)
                # Placement suivant l'image
                if num == 20:
                        self.image('img/edf.jpg',10,135,20)
                        self.set_y(136)
                        self.set_x(36)
                        self.cell(0,5,texte,0,1,'L',1)

                if num == 21:
                        self.image('img/eo2.jpg',10,152,15)
                        self.set_y(153)
                        self.set_x(36)
                        self.cell(0,5,texte,0,1,'L',1)

                if num == 22:
                        self.image('img/cuve.jpg',10,187,20)
                        self.set_y(188)
                        self.set_x(36)
                        self.cell(0,5,texte,0,1,'L',1)

                if num == 23:
                        self.image('img/oregon.jpg',10,220,20)
                        self.set_y(220)
                        self.set_x(36)
                        self.cell(0,5,texte,0,1,'L',1)

                #Line break
                self.ln(2)
                self.set_x(36)

        def chapitre_general(self,texte):
                #Arial 11
                self.set_font('Arial','',11)
                #Background color
                self.set_fill_color(255,255,255)
                #Abcisse
                self.set_x(36)
                #Texte à mettre dans le pdf
                self.cell(0,5,texte,0,1,'L',1)
                #Line break
                self.ln(2)
                self.set_x(36)

# Module de recherche de Patrice
def recherche(idx, recherche, decal, nb_car):

        # URL Domoticz a interroger pour recuperer les infos est type=devices&rid=XXX

        json_url='/json.htm?type=devices&rid='
        requete='http://'+config.domoticz_ip+':'+config.domoticz_port+json_url+str(idx)

        r=requests.get (requete)
        ch= r.text
        lgterme=len(str(recherche))
        trouve_a= ch.find(str(recherche))
        debut=trouve_a+lgterme

        resultat=ch[debut+int(decal):debut+int(decal)+int(nb_car)]

        return resultat

# Lance les requêtes au moyen du programme collecte

# Récupération EDF
# recherche(idx,recherche,decal,nb_car)
edf=recherche('180', 'Data', '5', '4')

# Récupération du nombre de granulés en stock
# Calcul du nombre de sac par rapport à la variable du fichier config
granules=recherche('164', 'Counter', '5', '2')
humidite=recherche('171', 'Humidity', '4', '2')
stock=int(config.qte_granules_total) - int(granules)
poids=int(granules) * int(config.poids_sac_granules)

# Récupération du volume et de la hauteur de la cuve
volume=recherche('177', 'Counter', '5', '4')
hauteur=recherche('176', 'Data', '5', '3')
temp=recherche('101', 'Data', '5', '3')

# Récupération de l'état des batteries
batsdb=recherche('169', 'BatteryLevel', '4', '3')

# Création du PDF
pdf=PDF()
pdf.set_title(title)
pdf.set_author(u'José')
pdf.add_page()

# Mise en forme des données système
pdf.print_chapter(1,u'Information mémoire (Disponible ' + data.mpourcent + u" %)","Total = " + data.mtotal + u" - Utilisée = " + data.mutilise + " - Libre = " + data.mlibre)
pdf.print_chapter(2,u'Information mémoire swap (Utilisé ' + data.mspourcent + u" %)","Total = " + data.mstotal + u" - Utilisée = " + data.msutilise + " - Libre = " + data.mslibre)
pdf.print_chapter(3,u'Information CPU',"Charge = " + data.usagecpu + u" % - Température = "+str(data.cpu_temperature)+u" °C")
pdf.print_chapter(4,u'Espace disque carte SD (utilisée à '+ data.pourcent + "%)","Total = " + data.total + u" - Utilisé = " + data.utilise + " - Libre = " + data.libre)
pdf.print_chapter(5,u'Le module Domoticz (pid ' + str(data.pid) + ') fonctionne depuis le ' + str(data.boot) + ' - Status : ' + data.status,u'Ce soft utilise '+ str(data.mem) + u' de mémoire.')
pdf.print_chapter(6,u'Temps de fonctionnement du serveur : '+data.demarrage,'')

# Trace une ligne pour séparer les données système du reste
pdf.dashed_line(10,130,200,130,2,2)

# Mise en forme des données générales
# EDF
pdf.chapitre_general_titre(20,u"La valeur de la consommation EDF de la journée d'hier s'élève à " + edf + u" euros (hors abo).")

# Granulés
pdf.chapitre_general_titre(21,u"Il reste " + str(stock) + u" sacs de granulés en stock sur " + str(config.qte_granules_total) + '.')
pdf.chapitre_general(u"Représente une consommation de "+ str(granules) + u" sacs à ce jour, soit " + str(poids) + " kg." )
pdf.chapitre_general(u"Le niveau d'humidité ambiante du stockage est de " + humidite + u" %.")

# Ajoute un message d'alerte pour lancer la commande
if int(stock) < 20:
        # Place la couleur du texte en rouge
        pdf.set_text_color(204,0,0)
        pdf.chapitre_general(u"Attention le stock est trop bas. Il faut lancer une commande !")
        # Remet la couleur en noir
        pdf.set_text_color(0,0,0)

# Cuve
pdf.chapitre_general_titre(22,u'Le volume de la cuve est de '+ volume + " m3.")
pdf.chapitre_general(u"La hauteur d'eau est de " + hauteur + " cm.")
pdf.chapitre_general(u"La température de l'eau est de "+ temp + u"°C.")

# Batteries des sondes Oregon
pdf.chapitre_general_titre(23,u'Etat batterie sonde salle de bains: '+ batsdb + " %.")
pdf.chapitre_general(u"Etat batterie sonde cabane de jardin: "+ batsdb + " %.")
pdf.chapitre_general(u"Etat batterie sonde congélateur: "+ batsdb + " %.")

# Lancement de la création du rapport
pdf.output('Rapport.pdf','F')
Le programme d'envoi de mail en python avec pièce jointe. Attention à ne pas faire trop de tests car vous risquez le blacklistage :evil:

Code : Tout sélectionner

#!/usr/bin/env python
# -*- coding:Utf-8 -*-
#
# Récupération du code de la page
# http://astuces.absolacom.com/developpement/envoyer-un-mail-avec-pieces-jointes-en-python/
# Merci à son auteur
#
########

import smtplib
import os
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders

# Variables
files = ['Rapport.pdf'] # Pièces jointes avec leur chemin
destinataires = ['destinataire1@free.fr', 'destinataire2@free.fr'] # Mettre des [] Adresses mail séparées par des virgules
expediteur = 'expediteur@free.fr'
titre = u"Rapport journalier Domoticz"
message = u"Bonjour a tous, \nVoici votre rapport journalier du systeme domoticz. \nBonne journee."
smtp = 'smtp.free.fr'

def send_mail(send_from, send_to, subject, text, files=[], server='localhost'):

        assert type(send_to)==list
        assert type(files)==list
        msg = MIMEMultipart()
        msg['From'] = send_from
        msg['To'] = COMMASPACE.join(send_to)
        msg['Date'] = formatdate(localtime=True)
        msg['Subject'] = subject
        msg.attach( MIMEText(text) )

        for f in files:
                part = MIMEBase('application', "octet-stream")
        #print "Transmission du mail avec la pièce jointe : " + f
        part.set_payload( open(f,"rb").read())
        Encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))

        msg.attach(part)

        smtp = smtplib.SMTP(server)
        smtp.sendmail(send_from, send_to, msg.as_string())
        smtp.close()

# Module d'envoi de mail
send_mail(expediteur, destinataires, titre, message, files, smtp)
Enfin, le module de supervision data.py qui utilise la librairie psutil

Code : Tout sélectionner

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
#########
#
# Utilisation du travail de Christophe CARON
# http://www.caron.ws/
#
# Récupération des données pour le module rapport de domoticz
#
# Jose - Février 2015 -  Ajout de la partie domoticz
#
#########

import psutil
import os
import sys
import string
import datetime
from subprocess import PIPE, Popen
from datetime import timedelta

#convertion des unites en Ko,Go,To
def bytes2human(n):
    # http://code.activestate.com/recipes/578019
    # >>> bytes2human(10000)
    # '9.8K'
    # >>> bytes2human(100001221)
    # '95.4M'
    symbols = (' Ko', ' Mo', ' Go', ' To', ' Po', 'Eo', 'Zo', 'Yo')
    prefix = {}
    for i, s in enumerate(symbols):
        prefix[s] = 1 << (i+1)*10
    for s in reversed(symbols):
        if n >= prefix[s]:
            value = float(n) / prefix[s]
            return '%.1f%s' % (value, s)
    return "%sB" % n

# Lecture de la temperature
def get_cpu_temperature():
    process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
    output, _error = process.communicate()
    return float(output[output.index('=') + 1:output.rindex("'")])

# Détermine la date et l'heure du dernier reboot de domoticz
def domo():
        for p in psutil.process_iter():
                try:
                        pi = p.as_dict(attrs=['pid', 'name', 'create_time', 'memory_info', 'status'])
                except:
                        pass
                else:
                        if pi['name'] == 'domoticz':
                                pid = pi['pid']
                                #print 'Pid correspondant: ', pi['pid']
                                boot = datetime.datetime.fromtimestamp(pi['create_time']).strftime("%d-%m-%Y %H:%M:%S")
                                #print 'Heure de la mise en service domoticz', boot
                                mem = pi['memory_info'][0]+pi['memory_info'][1]
                                #print 'Mémoire :', mem
                                status = pi['status']
                                #print status

        return boot, pid, mem, status

# Uptime
with open('/proc/uptime', 'r') as f:
        uptime_seconds = round(float(f.readline().split()[0]),0)
        demarrage = str(timedelta(seconds = uptime_seconds))
        demarrage = demarrage.replace('days','Jours',1)
        demarrage = demarrage.replace(',',' et',1)
        demarrage = demarrage.replace('day','Jour',1)
        demarrage = demarrage.replace(':','h ',1)
        demarrage = demarrage.replace(':','mn ',1)
        demarrage = demarrage+ 's '

# Attribution des valeurs aux variables
# SD card
valeur = psutil.disk_usage('/')
part = psutil.disk_partitions(all=False)
infopart = (part[0])[2]        # extration de l'extension
total = bytes2human(valeur.total)
utilise = bytes2human(valeur.used)
libre = bytes2human(valeur.free)
pourcent = str(valeur.percent)

# CPU
usagecpu = str(psutil.cpu_percent(interval=1))
p = psutil.Process(os.getpid())

# Heure mise a jour
dateheure = datetime.datetime.now()
dateheure = dateheure.strftime('%d/%m/%Y')
#dateheure = datetime.datetime.fromtimestamp(p.create_time).strftime("%d-%m-%Y %H:%M")

# Temperature
cpu_temperature = get_cpu_temperature()
p = psutil.Process(os.getpid())

# Memoire Vive RAM
valeur = psutil.virtual_memory()
mtotal = bytes2human(valeur.total)
mdispo = bytes2human(valeur.available)
mutilise = bytes2human(valeur.used)
mlibre = bytes2human(valeur.free)
mpourcent = str(valeur.percent)

# Memoire SWAP
valeur = psutil.swap_memory()
mstotal = bytes2human(valeur.total)
msutilise = bytes2human(valeur.used)
mslibre = bytes2human(valeur.free)
mspourcent = str(valeur.percent)

# Date et heure de boot domoticz et pid système
boot, pid, mem, status = domo()

# Mise en forme de la mémoire
mem =  bytes2human(mem)
Maintenant que vous avez les sources, je détaille rapidement le contenu.

Les programmes data.py et config.py sont chargés comme modules du programme rapport.py. Ce dernier récupère toutes les données à mettre dans le rapport et procède à sa création grâce à la classe fpdf. Une tâche planifiée réalise l'opération puis transmet le rapport par mail (gare au spam, je le répète le blacklistage n'est pas loin).

Les images sont stockées dans un répertoire "img" afin que ça ne devienne pas le bazar.

Allez, je file commander mes granulés. Si je suis allé trop vite, n'hésitez pas.
patrice
Messages : 1720
Inscription : 22 sept. 2014, 21:51

Re: Rapport quotidien en pdf et transmission par mail

Message par patrice »

Bonsoir
Bonne idée , j'adopte.
Merci
Raspberry pi 3, Dongle RfxCom et Zigate +, plugin de pipiche, Volets Roulants profalux, contrôle d'éclairage, détecteurs de présence, alarme incendie, pilotage livebox ...
Objectif : WAF > 1
ben33880
Messages : 268
Inscription : 13 nov. 2014, 21:12

Re: Rapport quotidien en pdf et transmission par mail

Message par ben33880 »

Réponse hors sujet mais je tente quand meme....
Tu as un poele a granulé et tu consommes quasimment 10 € par jour en électricité :shock:

Sinon sympa le pdf
Jose
Messages : 63
Inscription : 25 sept. 2014, 21:40

Re: Rapport quotidien en pdf et transmission par mail

Message par Jose »

Pertinente remarque ben33880 :P

A force de faire des essais de rapports, j'ai flingué la carte sd. Du coup, cette valeur correspond à deux jours.
Malheureusement, le poël ne me permet de pas de chauffer toute la maison, j'ai donc conservé 6 convecteurs.
JC38
Messages : 286
Inscription : 29 déc. 2014, 20:47
Localisation : Lyon

Re: Rapport quotidien en pdf et transmission par mail

Message par JC38 »

Bonsoir José.

Quelle est l'url de ton blog ?

Je voudrais aussi voir comment tu gères les granulés dans Domoticz.
Raspberry Pi3 sous Jessie 8.3 + Domoticz latest beta + RFXComm Usb + Aeon Labs Z-Stick + Weather Underground
Windows Server 2012 R2 + Domoticz Windows (latest) + Open Hardware Monitor
Clients : ImperiHome avec Mdah / Domoticz for Android
Jose
Messages : 63
Inscription : 25 sept. 2014, 21:40

Re: Rapport quotidien en pdf et transmission par mail

Message par Jose »

Bonsoir JC38,

Tu trouveras normalement l'adresse du blog sur mon la partie site internet de mon profil. C'est plutôt un cahier de bouillon :o

Pour la partie granulés, je cherchais le moyen de connaitre le nombre de sacs restants car ils sont stockés dans la cabane de jardin. Comme je ne suis pas le seul à mettre des granulés dans le poël, chaque fois que quelqu’un vide un sac, il passe un tag rfid devant notre katotz (qui à dit geek ???).
Ce dernier lance un script python qui met à jour un compteur et m'envoie un sms avec la quantité restante et son équivalent en kilo.
granules.JPG
granules.JPG (18.56 Kio) Consulté 16158 fois
Status = nombre de sac consommés
Granulés = 1 je viens de mettre un sac

Si cela t'intéresse, je mettrai le script à disposition, son action pouvant être activée par un inter virtuel bien entendu.
JC38
Messages : 286
Inscription : 29 déc. 2014, 20:47
Localisation : Lyon

Re: Rapport quotidien en pdf et transmission par mail

Message par JC38 »

José,

Je suis preneur... Je donnerai le script à manger à mon lapin qui passe son temps à glander.
Tu gères aussi les entrées en stock ?
Raspberry Pi3 sous Jessie 8.3 + Domoticz latest beta + RFXComm Usb + Aeon Labs Z-Stick + Weather Underground
Windows Server 2012 R2 + Domoticz Windows (latest) + Open Hardware Monitor
Clients : ImperiHome avec Mdah / Domoticz for Android
Jose
Messages : 63
Inscription : 25 sept. 2014, 21:40

Re: Rapport quotidien en pdf et transmission par mail

Message par Jose »

Re,

Bien sur avec plaisir, je commencerai un nouveau topic.
Par contre, je ne gère pas les entrées mais cela ne serait pas trop difficile.
Je modifie le fichier de configuration en ajoutant le nombre de sacs.

Pour revenir un instant au lapin, ceux qui ne seront pas passés sous open-karotz verront leurs lapins mourir mercredi :geek:

@ +
JC38
Messages : 286
Inscription : 29 déc. 2014, 20:47
Localisation : Lyon

Re: Rapport quotidien en pdf et transmission par mail

Message par JC38 »

Ok merci.
Pour mon lapin, il est très open :D
Raspberry Pi3 sous Jessie 8.3 + Domoticz latest beta + RFXComm Usb + Aeon Labs Z-Stick + Weather Underground
Windows Server 2012 R2 + Domoticz Windows (latest) + Open Hardware Monitor
Clients : ImperiHome avec Mdah / Domoticz for Android
deennoo
Messages : 4036
Inscription : 25 janv. 2015, 02:00

Re: Rapport quotidien en pdf et transmission par mail

Message par deennoo »

Merci José pour ce Tuto !

J'aimerais l agrémenter un joli graphique volé de Domoticz pour la Conso hebdo EDF, sais tu si on peut le faire ?
De quoi se faire Plaisir et essayer d'aider...
http://www.domo-attitude.fr
Répondre