Cameteo/raspberry/python/mqtt2epaper.py

248 lines
8.3 KiB
Python

#!/usr/bin/python3
# -*- coding: UTF8 -*-
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
Display data from MQTT broker on ePaper screen
@author: arofarn
"""
__version__ = 0.3
###########
# IMPORTS #
###########
from cameteo_conf import *
import paho.mqtt.client as mqtt
import time
import json
import netifaces, socket
import inkyphat
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
##########
# Config #
##########
mqtt_client_id = "epaper_display"
# Colors codes
white = inkyphat.WHITE
black = inkyphat.BLACK
grey = inkyphat.RED
new_data_flag = False
#############
# CALLBACKS #
#############
def on_connect(client, userdata, flags, rc):
"""On connection callback : topics subscription"""
print(mqtt.connack_string(rc))
if rc == 0:
print("Subscribing to %s ..." % mqtt_topic)
client.subscribe([(mqtt_topic, 0),
(camera_mqtt_topic + "/#", 0)]
)
print("OK")
def on_message(client, userdata, msg):
"""Callback for managing data sent by MQTT broker"""
global new_data_flag
top=msg.topic[len(mqtt_topic)-1:].strip()
#List of the subtopics
subtopics = top.split("/")
#Display atmospheric data from BME280 sensor
if subtopics[0] == "BME" or subtopics[0] == "SYS":
payload = msg.payload.decode()
val = json.loads(payload)
#Manage special character "degree"
val['unit'] = val['unit'].replace('deg', '°')
#List of 'displayable' data types and their coordinate on screen
coord_type = { 'AT' : (1, data1_line),
'RH' : (inkyphat.WIDTH / 2 + 12, data1_line),
'AP' : (1, data2_line),
'VBAT' : (inkyphat.WIDTH / 2 + 12, data2_line),
}
print(val)
if val['type'] in coord_type:
#Manage special character "degree"
val['unit'] = val['unit'].replace('deg', '°')
#Is value consistent ?
try:
val['value'] = float(val['value'] )
except:
print("Value error: {}".format(val['value']))
val['value'] = float('nan')
#Erase old data
inkyphat.rectangle(coord_type[val['type']] + (coord_type[val['type']][0] + inkyphat.WIDTH / 2 - 2,
coord_type[val['type']][1] + 16),
fill=white)
#Draw new values
inkyphat.text(coord_type[val['type']],
"{:6.1f}{} ".format(val['value'], val['unit']),
font=data_font,
fill=black)
new_data_flag = True
# Print feather's date and time on epaper display
elif val['type'] == "TIME":
try:
#Check the date and time and offset to the right timezone
dt_texte = datetime.strftime(datetime.strptime(val['value'], "%Y/%m/%d_%H:%M:%S") + TimeZone.utcoffset(None),
"%d/%m/%Y %H:%M:%S")
except:
dt_texte = payload
print(dt_texte)
# First erase old text
inkyphat.rectangle((0, date_line, inkyphat.WIDTH, data1_line - 2), fill=white)
# and draw the new date
inkyphat.text((0, 31), dt_texte, font=font12, fill=black)
new_data_flag = True
def on_message_camera(client, userdata, msg):
"""Update display with info from camera (camera shooting new picture or name
of the last photo)"""
global new_data_flag
pl = msg.payload.decode()
print("{} : {} ({})".format(msg.topic, pl, type(pl)))
# If a picture is been taken
if pl == "1" and msg.topic == camera_mqtt_topic + "/shooting" :
text = "Shooting photo... "
inkyphat.rectangle((0, inkyphat.HEIGHT - 12, inkyphat.WIDTH, inkyphat.HEIGHT), fill=white)
inkyphat.text((0, inkyphat.HEIGHT - 12), text, font=font12, fill=black)
new_data_flag = True
#Last picture name (with date/time)
if msg.topic == camera_mqtt_topic + "/last_photo":
text = "Last: {}".format(pl)
new_data_flag = True
inkyphat.rectangle((0, inkyphat.HEIGHT - 12, inkyphat.WIDTH, inkyphat.HEIGHT), fill=white)
inkyphat.text((0, inkyphat.HEIGHT - 12), text, font=font12, fill=black)
def on_disconnect(client, userdata, msg):
"""Disconnection callback"""
if msg != 0:
print("Unexpected disconnection : {}".format(msg))
exit()
############
# Function #
############
def update_display():
global new_data_flag
global refresh_timer
global refresh_rate
if new_data_flag == True:
if refresh_timer < 0:
# New data just arrived : wait for other data for a short period
print("new data...")
refresh_timer = time.time() + refresh_rate
elif time.time() >= refresh_timer :
# No new data to display : update the screen and reset the flags!
print("Refresh eink screen with new data ! ({} / {})".format(refresh_timer, time.time()))
inkyphat.show()
new_data_flag = False
refresh_timer = -1
print("OK")
return
########
# Main #
########
#Screen init.
inkyphat.set_colour("black")
#List of TrueType fonts
font10 = inkyphat.ImageFont.truetype(epd_font_file, 10)
font11 = inkyphat.ImageFont.truetype(epd_font_file, 11)
font12 = inkyphat.ImageFont.truetype(epd_font_file, 12)
font14 = inkyphat.ImageFont.truetype(epd_font_file, 14)
font16 = inkyphat.ImageFont.truetype(epd_font_file, 16)
font20 = inkyphat.ImageFont.truetype(epd_font_file, 20)
font12_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 12)
font14_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 14)
font16_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 16)
font20_bold = inkyphat.ImageFont.truetype(epd_bold_font_file, 20)
#font12_obl = inkyphat.ImageFont.truetype(epd_italic_font_file, 12)
#font14_obl = inkyphat.ImageFont.truetype(epd_italic_font_file, 14)
#font16_obl = inkyphat.ImageFont.truetype(epd_italic_font_file, 16)
# Lines and fonts
title_line = 1
title_font = font14_bold
hostname_line = 19
hostname_font = font11
date_line = 31
date_font = font12
data1_line = 44
data2_line = 62
data_font = font16_bold
data_table_bottom_line = data2_line + 17
#Draw title in white on black
inkyphat.rectangle((0, 0, inkyphat.WIDTH, 17), fill=grey)
inkyphat.text((60, 1), "Projet Camétéo", font=title_font, fill=white)
#Write hostname and IP address
inkyphat.text((0, 19),
"{} : {}".format(socket.gethostname(),
netifaces.ifaddresses('wlan0')[2][0]['addr']),
font = hostname_font,
fill = black )
# Draw the black lines for data table
inkyphat.line((0, data1_line - 1, inkyphat.WIDTH, data1_line - 1), fill=black)
inkyphat.line((0, data2_line - 1, inkyphat.WIDTH, data2_line - 1), fill=black)
inkyphat.line((0, data_table_bottom_line, inkyphat.WIDTH, data_table_bottom_line), fill=black)
inkyphat.line((inkyphat.WIDTH / 2, data1_line - 1, inkyphat.WIDTH / 2, data_table_bottom_line), fill=black)
#Connect to MQTT broker and loop...
mqtt_client = mqtt.Client(mqtt_client_id, clean_session=True)
mqtt_client.username_pw_set(mqtt_user, mqtt_pass)
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.on_disconnect = on_disconnect
#Special callback for camera
mqtt_client.message_callback_add(camera_mqtt_topic + "/#", on_message_camera)
print(mqtt_host + ":" + str(mqtt_port))
mqtt_client.connect(mqtt_host, int(mqtt_port), 60)
refresh_rate = 5
refresh_timer = -1
while True:
mqtt_client.loop()
update_display()