# Gráficos da Matplotlib aparecerem "em linha" no Jupyter Notebook
%matplotlib inline
# Importação dos módulos para gerar relatório em PDF a partir de um Template em HTML
try:
import os # módulo do sistema operacional
import numpy as np # https://www.numpy.org/
import pandas as pd # https://pandas.pydata.org/
import matplotlib.pyplot as plt # https://matplotlib.org/
import jinja2 # https://palletsprojects.com/p/jinja/
import pdfkit # https://pdfkit.org/docs/guide.pdf
import datetime # módulo para tratar objetos do tipo data-hora
import base64 # módulo para converter imagem carregada do arquivo em base64
import io # módulo para converter imagem em memória em base64
from collections import OrderedDict # Dicionário ordenado para usar o EPM Server (OPC UA)
import epmwebapi as epm # módulo para conecão com o EPM Server (via EPM Web Server)
plt.style.use('ggplot') # Estilo a ser utilizado nos gráficos da Matplotlib
print('Módulos importados com sucesso!')
except ImportError as error:
print('Problemas na importação de algum(ns) (dos) módulo(s)!')
print(error.__class__.__name__ + ': ' + error.message)
except Exception as exception:
print(exception.__class__.__name__ + ': ' + exception.message)
# ***** FUNÇÕES AUXILIARES *****
# Função para converter para a data-hora Local (Brasília -> -3) e remover tzinfo (Matplotlib)
_TZ_3_ = datetime.timezone(datetime.timedelta(hours=-3)) # Desta forma se utiliza explicitamente o Off-set!
def utc2tz(dt, tz):
""" Converte date-time em UTC para timezone informada - removendo tzinfo
"""
return dt.astimezone(tz).replace(tzinfo=None)
# Função que organiza os dados de Séries Temporais do EPM Server (OPC UA) em um Dataframe do Pandas
_DF_ = None # Variável Global
def epm2pandas_global(timestamp_index=None, **kwargs):
# É necessário que todos tenham o mesmo timestamp para usar como index
# ex: _DF_ = epm2pandas(var1=epmDataVar1, var2=epmDataVar2, var3=epmDataVar3) # pode ter qtas variáveis quiser! :)
#_DF_ = None
global _DF_
def newByteOrderAndVectorize(epm):
# Reordena os bytes para o pandas funcionar corretamente numpay array do EPM
v = epm['Value'].byteswap().newbyteorder()
return v.reshape(len(v),1)
if len(kwargs)>0:
columnNames = []
notFirst = False
for key, value in kwargs.items():
columnNames.append(key)
if notFirst:
v = newByteOrderAndVectorize(value)
data = np.hstack((data, v))
else: # primeira coluna de timestamp é utilizada para todos as demais variáveis
notFirst = True
data = newByteOrderAndVectorize(value)
if timestamp_index is None:
timestamp_index = value['Timestamp']
_DF_ = pd.DataFrame(data, index=timestamp_index, columns = columnNames)
# Função para converter imagem em base64 para incorporar diretamente no código HTML
# ref.: https://stackoverflow.com/questions/38329909/pdfkit-not-converting-image-to-pdf
def image_file_path_to_base64_string(filepath: str) -> str:
""""
Takes a filepath and converts the image saved there to its base64 encoding,
then decodes that into a string.
"""
with open(filepath, 'rb') as f:
return base64.b64encode(f.read()).decode()
print('Funções auxiliares definidas com sucesso!!!')
# Configurações gerais para usar o PDFKIT
# Para converter o relatório de HTML em PDF, é necessário instalar:
# * wk<html>toPDF (executável): https://wkhtmltopdf.org/
# * pdfkit (módulo Python): https://pypi.org/project/pdfkit/
_WKTHTMLTOPDF_ = r'E:\Programas\wkhtmltopdf\bin\wkhtmltopdf.exe' # caminho do executável
report_path = r'C:\temp' # local para salvar os relatórios em HTML e PDF
report_date = datetime.datetime.now().strftime('%Y_%m_%d') # data para compor o nome do relatório
report_file_html = report_path + '\\HTMLReport_' + report_date + '.html' # nome do relatório em HTML
report_file_pdf = report_path + '\\PDFReport_' + report_date + '.pdf' # nome do relatório em PDF
options = {
'quiet': '',
'page-size': 'A4',
'margin-top': '1.0cm',
'margin-right': '1.5cm',
'margin-bottom': '1.0cm',
'margin-left': '1.5cm',
'encoding': "UTF-8",
}
config = pdfkit.configuration(wkhtmltopdf=_WKTHTMLTOPDF_)
print(f'Relatórios serão dispostos no seguinte local: {report_path}')
# Conectando ao servidor de dados de processo (OPC UA Server)
_EPMUSERNAME_ = os.environ.get(key='EPMUser', default='DummyUser')
_EPMUSERPASS_ = os.environ.get(key='EPMUserPassword', default='DummyUserPassword')
print(f'Usuário lido da variável ambiente: {_EPMUSERNAME_}')
# Estabelece uma conexão com o Servidor OPC UA para fazer as consultas às temperaturas das salas
epm_auth = 'http://ecc.elipse.com.br:44333'
epm_web = 'http://ecc.elipse.com.br:44332'
# NOTA:
# Para efetuar testes com este servidor, encaminhar e-mail para: epm@elipse.com.br solicitando
# um usuário/senha para testes e avaliação do sistema.
# Criação de uma conexão informando os endereços do EPM Webserver(Authentication Port e WEB API Port), usuário e senha.
try:
epmConn = epm.EpmConnection(epm_auth, epm_web, _EPMUSERNAME_, _EPMUSERPASS_)
# A forma mais recomendada (fácil) para usar o comando print é com fstring
print(f'Conexão com {epm_web} criada com sucesso para o usuário {_EPMUSERNAME_}.')
except Exception as exception:
print(f'Falha no estabelecimento da conexão com {epm_web} para o usuário {_EPMUSERNAME}.')
print(exception.__class__.__name__ + ': ' + exception.message)
# Lê os dados das temperaturas das salas armazenadas no Servidor OPC UA
bv_list = ['ERSF10R1_Temp1', 'ERSF10R2_Temp1', 'ERSF10R3_Temp1', 'ERSF10R4_Temp1',
'ERSF11R1_Temp1', 'ERSF11R2_Temp1', 'ERSF11R3_Temp1', 'ERSF11R4_Temp1',
'ERSF12R1_Temp1', 'ERSF12R2_Temp1', 'ERSF12R3_Temp1', 'ERSF12R4_Temp1']
try:
bv_dic = epmConn.getDataObjects(bv_list)
print('Variáveis criadas com sucesso!')
except Exception as exception:
print(f'Falha na criação das variáveis!')
print(exception.__class__.__name__ + ': ' + exception.message)
# Consulta da média ponderada ao longo do tempo a cada 10 minutos
ini_date = datetime.datetime(2019, 4, 1, 3, tzinfo=datetime.timezone.utc) # local-time: 2019-04-01 00:00:00
end_date = datetime.datetime(2019, 5, 1, 3, tzinfo=datetime.timezone.utc) # local-time: 2019-05 00:00:00
query_period = epm.QueryPeriod(ini_date, end_date)
process_interval = datetime.timedelta(hours=1)
# Dica: usar TAB após "epm.AggregateType." para ver métodos disponíveis (intelisense)
aggregate_details = epm.AggregateDetails(process_interval, epm.AggregateType.TimeAverage)
data = []
data_arg = ""
i = 0
try:
for k, v in bv_dic.items():
data.append(v.historyReadAggregate(aggregate_details, query_period))
data_arg += ',' + k + '=' + f'data[{i}]'
i += 1
print(f'Leitura concluída para o tag: {k}')
timestamp = data[0]["Timestamp"]
print(f'Primeiro timestamp está em UTC: {timestamp[0]}')
except Exception as exception:
print(exception.__class__.__name__ + ': ' + exception.message)
# Converte de UTC para hora local
local_timestamp = list(map(utc2tz, timestamp, [_TZ_3_]*len(timestamp)))
print(f'Primeiro timestamp em hora local: {local_timestamp[0]}')
# Monta a string com os argumentos para criar um Dataframe do módulo Pandas
epm2pandas_args = 'timestamp_index=local_timestamp' + data_arg
epm2pandas_exec = 'epm2pandas_global(' + epm2pandas_args + ')'
print(f'Comando para converter dados do EPM em Dataframe: {epm2pandas_exec}')
# Executa a String com o comando para criar um Dataframe do Pandas
exec(epm2pandas_exec)
_DF_.info()
# Informações estatísticas do Dataframe criado
_DF_.describe()
# Contagem de todos valores com temperaturas entre 23 e 26 °C
l_crit = _DF_ >= 23.0
h_crit = _DF_ <= 26.0
all_crit = l_crit & h_crit
_DF_[all_crit].count()
# Gerando o gráfico de tendência das temperaturas das salas
#fig, axes = plt.subplots(nrows=12, ncols=1, figsize=(18, 10))
nplots = 1 # ou 12!
fig_t, axes_t = plt.subplots(nrows=nplots, ncols=1, figsize=(14, 8))
_DF_.plot(drawstyle = 'steps', subplots=False, ax=axes_t)
# Gera Trendchart em base64 para vincular ao HTM
buffer_trend = io.BytesIO()
fig_t.savefig(buffer_trend, format='png', dpi=100)
buffer_trend.seek(0)
buffer_trend_base64 = base64.b64encode(buffer_trend.read()).decode()
print('Gráfico de tendência convertido para base64!!!')
# Gerando o boxplot das temperaturas das salas
#fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(18, 10))
fig_b, axes_b = plt.subplots(nrows=1, ncols=1, figsize=(14, 8))
_DF_.boxplot(ax=axes_b)
# Gera Boxplot em base64 para vincular ao HTM
buffer_boxplot = io.BytesIO()
fig_b.savefig(buffer_boxplot, format='png', dpi=100)
buffer_boxplot.seek(0)
buffer_boxplot_base64 = base64.b64encode(buffer_boxplot.read()).decode()
print('Boxplot convertido para base64!!!')
# GERA o Relatório em HTML - com a imagens vinculadas
file_loader = jinja2.FileSystemLoader('templates') # informa o diretório com os templates HTML
env = jinja2.Environment(loader=file_loader)
template_report = env.get_template('report.html') # carrega o template HTML
content_report = 'REPORT: Elipse Plant Manager - EPM Processor PDF Report example'
# Gera o relatório em HTML
html_report = template_report.render(content=content_report,
img_string=image_file_path_to_base64_string('images/epmbanner.png'),
img_trend=buffer_trend_base64,
img_boxplot=buffer_boxplot_base64)
# Salva o relatório HTML no caminho previamente definido (em utf-8 -> caracteres especiais)
with open(report_file_html, 'w', encoding='utf-8') as html_file:
html_file.write(html_report)
print(f'Relatório HTML gerado com sucesso: {report_file_html}')
# Converte o relatório de HTML para PDF - salvando na máquina local
pdf_file = pdfkit.from_string(html_report,
report_file_pdf,
configuration=config,
options=options, css='templates/paper_pdf.css')
if(pdf_file):
print(f'Relatório PDF gerado com sucesso: {report_file_pdf}')
# SEMPRE deve-se encerrar a conexão estabelecida com o EPM Server, pois isso irá encerrar a sessão e
# liberar a licença de EPM Client para que outros, eventualmente, possam utilizá-la.
epmConn.close()
print('Sessão encerrada com o EPM Server!!!')
# Vídeo explicando este exemplo (https://www.youtube.com/c/MauricioPosser)
from IPython.lib.display import YouTubeVideo
YouTubeVideo('UET051DJ8hA')