| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- #!/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 <https://www.gnu.org/licenses/>.
- #
- # 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('<Control-c>', handler)
- root.configure(bg='#000')
- root.mainloop()
|