#!/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 . """ 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()