248 lines
8.3 KiB
Python
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":
|
|
payload = msg.payload.decode()
|
|
val = json.loads(payload)
|
|
|
|
#Is value consistent ?
|
|
try:
|
|
val['value'] = float(val['value'] )
|
|
except:
|
|
print("Value error: {}".format(val['value']))
|
|
val['value'] = float('nan')
|
|
|
|
#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:
|
|
#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
|
|
update_display()
|
|
|
|
# Print feather's date and time on epaper display
|
|
elif subtopics[0] == "SYS":
|
|
payload = msg.payload.decode()
|
|
val = json.loads(payload)
|
|
if 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
|
|
update_display()
|
|
|
|
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()
|