#!/usr/bin/python3 # # Copyright (c) 2021 Clementine Computing LLC. # # This file is part of PopuFare. # # PopuFare is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # PopuFare 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with PopuFare. If not, see . # # This is the front end interface to the PIU that will run as # a Python application. # It displays the video stream and overlays the barcode, if it finds one. # It scans in the directory: # # /home/bus/queue # # for messages. If found, it then moves them to: # # /home/bus/log/messages # # Only the last message (after filename sorting) is chosen. # The first line in the message file is the status message. # The second line is the error message. # Either or both lines can be blank without consequence. # # QR Codes are logged in: # # /home/bus/log/qrcode.log # import sys import os import tkinter as tk from PIL import Image, ImageTk import cv2 import signal import tkinter.font as tkfont import numpy as np from pyzbar import pyzbar import datetime import imutils import time from imutils.video.pivideostream import PiVideoStream from imutils.video import FPS from picamera.array import PiRGBArray from picamera import PiCamera def handler(ev): root.destroy() def check(): root.after(500, check) class MainWindow(): def __init__(self, window, pistream): self.display_msg = "" self.display_msg_error = "" self.display_msg_idle = "READY" self.display_msg_t = 10000 self.display_msg_start = 0 self.display_msg_end = 0 self.message_dir = "/home/bus/queue" self.processed_message_dir = "/home/bus/log/message" self.log_dir = "/home/bus/log" self.barcode_ofn = os.path.join(self.log_dir, "qrcode.log") self.pistream = pistream pad = 0 self.disp_pad = pad self._orig_geom = "{0}x{1}+0+0".format( window.winfo_screenwidth()-pad, window.winfo_screenheight()-pad ) self.screen_width = window.winfo_screenwidth() self.screen_height = window.winfo_screenheight() window.attributes("-fullscreen", True) self.cap_width = 400 self.cap_height = 300 self.window = window self.interval = 30 self.canvas = tk.Canvas(self.window, width=self.screen_width, height=self.screen_height, bg='#000', bd=0, highlightthickness=0, relief='ridge') self.canvas.grid(row=0, column=0) self.RATE_LIMIT_MS = 1500.0 self.LAST_TOK = "" #self.barcode_ofp = open("/tmp/qrcode.log", "a") #self.barcode_ofp = open("/home/bus/log/qrcode.log", "a") self.t_prv = time.time()*1000.0 self.t_now = self.t_prv self.update_loop() def log_barcode(self, data): with open( self.barcode_ofn, "a" ) as ofp: ofp.write( data + "\n") ofp.flush() def message_scan(self): msg_fns = [] for rootdir, dirs, fns in os.walk( self.message_dir ): for fn in fns: msg_fns.append(fn) msg_fns.sort() for msg_fn in msg_fns: print(">>>", msg_fn) src_fq_msg_fn = os.path.join(self.message_dir, msg_fn) dst_fq_msg_fn = os.path.join(self.processed_message_dir, msg_fn) with open(src_fq_msg_fn) as fp: msg_a = fp.read().split("\n") self.display_msg = msg_a[0][0:10] if len(msg_a) > 1: self.display_msg_error = msg_a[1][0:10] self.display_msg_start = time.time() * 1000.0 self.display_msg_end = self.display_msg_start + self.display_msg_t os.replace(src_fq_msg_fn, dst_fq_msg_fn) def message_update(self): t = time.time() * 1000.0 if t > self.display_msg_end: self.display_msg = "" self.display_msg_error = "" self.display_msg = self.display_msg_idle def update_loop(self): self.message_scan() self.message_update() img_cv2 = None img_cv2 = self.pistream.read() img_cv2 = imutils.resize(img_cv2, width=self.cap_width) ## overlay barcode rectanges and text on cv2 image ## barcodes = pyzbar.decode(img_cv2) for barcode in barcodes: (x,y,w,h) = barcode.rect cv2.rectangle(img_cv2, (x,y), (x+w, y+h), (0, 0, 255), 2) barcode_data = barcode.data.decode("utf-8") barcode_type = barcode.type show_text = "{} ({})".format(barcode_data, barcode_type) cv2.putText(img_cv2, show_text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) self.t_now = time.time()*1000.0 if self.LAST_TOK != barcode_data: #self.barcode_ofp.write( str(int(time.time())) + ": " + barcode_data + "\n") #self.barcode_ofp.flush() self.log_barcode( str(int(time.time())) + ": " + barcode_data ) self.t_prv = self.t_now self.LAST_TOK = barcode_data elif (self.t_now - self.t_prv) >= self.RATE_LIMIT_MS: #self.barcode_ofp.write( str(int(time.time())) + ": " + barcode_data + "\n") #self.barcode_ofp.flush() self.log_barcode( str(int(time.time())) + ": " + barcode_data ) self.t_prv = self.t_now self.LAST_TOK = barcode_data else: pass # convert cv2 image to something that tkinter can display # #self.image = cv2.cvtColor(self.cap.read()[1], cv2.COLOR_BGR2RGB) self.image = cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB) self.image = cv2.flip(self.image, 1) self.image = Image.fromarray(self.image) self.image = ImageTk.PhotoImage(self.image) dy = int(self.screen_height - self.cap_height) dx = int((self.screen_width - self.cap_width)/2) dx -= 1 if dx<0: dx=0 midx = int(self.screen_width/2) _now = datetime.datetime.now() yyyymmdd = _now.strftime("%Y-%m-%d") time_str = _now.strftime("%a %I") + [":", " "][int(_now.strftime("%S"))%2] + _now.strftime("%M%p") char_width = 10 #text_msg = [ "2021-06-01", "Tue 10:53AM", "See Driver", "No Passes", "on Card" ] text_msg = [ yyyymmdd, time_str, self.display_msg, self.display_msg_error ] text_msg_dy = [ 60, 80, 80, 60 , 0 ] self.canvas.delete("all") txt_dy = 80 txt_x = midx txt_y = int(txt_dy/2) for idx,msg in enumerate(text_msg): font = None if idx==0: font = tkfont.Font(family="monospace", weight="bold", size=60) else: font = tkfont.Font(family="monospace", weight="bold", size=50) color = "#aaa" if idx == 2: color = "#6194fa" elif idx > 2: color = "#fa6171" self.canvas.create_text(txt_x, txt_y, anchor=tk.CENTER, text=msg, font=font, fill=color) #txt_y += txt_dy txt_y += text_msg_dy[idx] self.canvas.create_image(dx, dy, anchor=tk.NW, image=self.image) self.window.after(self.interval, self.update_loop) if __name__ == "__main__": root = tk.Tk() pistream = PiVideoStream().start() time.sleep(2.0) MainWindow(root, pistream) signal.signal(signal.SIGINT, lambda x,y : print('terminal ^C') or handler(None)) root.after(500, check) root.bind_all('', handler) root.configure(bg='#000') root.mainloop()