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