Our window opening monitor will not have a screen or graphic interface, so we need a means to be able to adjust the position of the sensor in the installation or on demand.
We have used a 4 digit 7 segment display to be able to monitor 4 windows at the same time. Each digit represents a window with its four corners and a moving part.
The detected marker corners are shown by turning on the LEDs corresponding to the same corner of the digit on the display. The middle segment represents the marker for the moving part of the window.
Multi-window driver 4x7 segment display
Bill of Materials
| Product Name | Manufacturer | Quantity | Buy KitBuy Kit |
|---|---|---|---|
| LDQ-N514RILDQ-N514RI 7 Segment LED Display, InfoVue, Red, 10 mA, 2 V, 3.9 mcd, 4, 14.22 mm | LUMEX | 1 | Buy NowBuy Now |
| PN2222Bipolar (BJT) Single Transistor, NPN, 40 V, 1 A, 625 mW, TO-226AA, Through Hole | ON SEMICONDUCTOR/FAIRCHILD | 4 | Buy NowBuy Now |
| 220Ω Resistor | VISHAY | 8 | Buy NowBuy Now |
| 4.7kΩ Resistor | VISHAY | 4 | Buy NowBuy Now |
| Single Board Computer, Raspberry Pi 4 Model B, BCM2711 SoC, 4GB DDR4 RAM, Bulk Pack | RASPBERRY-PI | 1 | Buy NowBuy Now |
| SN74HC595NSN74HC595N | TEXAS INSTRUMENTS | 1 | Buy NowBuy Now |
Breadboard
Schematic
LDQ-N514RILDQ-N514RI 7 Segment LED Display
The LDQ-N514RILDQ-N514RI is a 0.56-inch quad digit 7-segment Display with 635mm red chip. Grey font/white segments. Common cathode
Multiplexed. We will use 4 Raspberry Pi digital outputs to select the digit. The four digital outputs are connected to 4 PN2222 transistors to drive the leds to ground.
SN74HC595NSN74HC595N shift register
We will control send data to the display serially using the clocking of a Raspberry Pi SPI interface
How it works
Each digit represents a window with its four corners and a moving part.
The detected marker corners are shown by turning on the LEDs corresponding to the same corner of the digit on the display.
The middle segment represents the marker for the moving part of the window.
Windows 2 & 3 fully detected
Sheet with Windows ID 2 to 4.
Windows 2, 3 & 4 fully detected. Window 1 not detected
Display python code
This class drives a 4x7 segment display using an SN74HC595 shift register clocked by spi clock and 4 digital lines to switch between digits.
Works in its own thread
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""wom_display_4x7_spi.py: 4 digits x7 segment display
drives a 4x7 segment display using an SN74HC595 shift register clocked by spi clock
and 4 digital lines to switch digits. Works in its own thread
"""
__author__ = "Enrique Albertos"
__license__ = "GPL"
import RPi.GPIO as GPIO
import sys
import time
import threading
from threading import Thread
import spidev
import atexit
class Display4x7(threading.Thread):
# PIN definitions GPIO.BCM
# Connect to 74HC595 8-bit serial-in, parallel-out shift
__bus = 0 # MOSI GPIO 10 (PIN 21) - 74HC595 pin 14 DS
# SCLK GPIO 11 - 74HC595 pin 11 SHCP
__device = 0
__spiSpeedDefault = 3900000
__latchPinDefault = 25 # GPIO 8 (CEO) 74HC595 pin 12 STCP
# HS42056 1K-32 digit selection
__digit0PinDefault = 14 # 7-Segment pin D4
__digit1PinDefault = 15 # 7-Segment pin D3
__digit2PinDefault = 18 # 7-Segment pin D2
__digit3PinDefault = 23 # 7-Segment pin D1
MARKERS = ( 0x03, # Top Left
0x05, # Top Right
0x50, # Bottom Right
0x18, # Bottom Left
0x80, # Center
0x00 # blank
)
HEX_DIGITS = (0x5F, # = 0
0x44, # = 1
0x9D, # = 2
0xD5, # = 3
0xC6, # = 4
0xD3, # = 5
0xDB, # = 6
0x45, # = 7
0xDF, # = 8
0xC7, # = 9
0xCF, # = A
0xDA, # = b
0x1B, # = C
0xDC, # = d
0x9B, # = E
0x8B, # = F
0x00 # blank
)
def __init__(self, initialContent = (0,0,0,0), bus=0, device=0, digit0 = __digit0PinDefault, digit1 = __digit1PinDefault, digit2 = __digit2PinDefault, digit3 = __digit3PinDefault, latchPin = __latchPinDefault, speedHz = __spiSpeedDefault):
self.__displayContent = initialContent
self.__latchPin = latchPin
self.__digit3 = digit3
self.__digit2 = digit2
self.__digit1 = digit1
self.__digit0 = digit0
self.__shifRegisterPins = (latchPin)
self.__controlDigitsPins = ( digit3, digit2, digit1, digit0 )
self.__lock = threading.Lock()
self.__bus = bus
self.__device = device
self.__speedHz = speedHz
atexit.register(self.cleanup)
self.__setup()
threading.Thread.__init__(self)
def __initPinsAsOutputs(self, pins) :
for pin in pins:
GPIO.setup(pin, GPIO.OUT, initial = GPIO.LOW)
def __lowPins(self, pins) :
for pin in pins:
GPIO.output(pin, GPIO.LOW)
def __setup(self):
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
# init display control digits pins
self.__initPinsAsOutputs(self.__controlDigitsPins)
# init serial *** register pins
GPIO.setup(self.__latchPin, GPIO.OUT, initial = GPIO.LOW)
self.__spiDisplay= spidev.SpiDev()
self.__spiDisplay.open(self.__bus,self.__device)
self.__spiDisplay.max_speed_hz = self.__speedHz
self.__spiDisplay.mode = 0
self.__spiDisplay.bits_per_word = 8
self.__spiDisplay.no_cs = True
def __shiftout(self, byte):
GPIO.output(self.__latchPin, 1)
time.sleep(0.00000005)
GPIO.output(self.__latchPin, 0)
self.__spiDisplay.xfer([byte])
GPIO.output(self.__latchPin, 1)
time.sleep(0.00000005)
GPIO.output(self.__latchPin, 0)
def run(self):
# overrides thread run
while True:
i=0
for pin in self.__controlDigitsPins:
self.__lowPins(self.__controlDigitsPins)
with self.__lock:
self.__shiftout(self.__displayContent[i])
GPIO.output(pin, GPIO.HIGH)
time.sleep(0.00000001)
i=i+1
def display(self, displayContent = (0,0,0,0)) :
with self.__lock:
self.__displayContent = displayContent
def displayInt(self, number = 0) :
self.display((self.HEX_DIGITS[(number // 1000)%10], self.HEX_DIGITS[(number // 100)%10],self.HEX_DIGITS[(number // 10)%10],self.HEX_DIGITS[number %10]))
def displayWindowCorners(self, iterable) :
content = [0,0,0,0]
digit=0
for element in iterable:
for i in range(5) :
if element[i]:
content[digit] |= Display4x7.MARKERS[i]
digit = digit + 1
self.display(content)
def __enter__(self) :
return self
def __exit__(self, exc_type, exc_value, traceback) :
self.cleanUp()
def cleanup() :
self.__dislay.closeSPI(self.spiDevice)
GPIO.cleanup()
if __name__ == '__main__':
try:
display = Display4x7()
display.start()
i=0
while True :
display.displayWindowCorners([[True, True, True, True, True], [False, False, False, False, False], [False, False, False, False, False], [False, False, False, False, False]])
#display.display((Display4x7.HEX_DIGITS[i%16],Display4x7.HEX_DIGITS[(i+1)%16],Display4x7.HEX_DIGITS[(i+2)%16],Display4x7.HEX_DIGITS[(i+3)%16]))
i = i + 1
time.sleep(.5)
except KeyboardInterrupt:
print('interrupted!')
GPIO.cleanup()
sys.exit()
Window detector python code
Window Detector as shown in the video demo
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""wom_window_detector.py: Window detector. Detects up to 4 windows
marked with 5 ArUco markers each
Results are sent to a 4x7 Led Display
"""
__author__ = "Enrique Albertos"
__license__ = "GPL"
from imutils.video import VideoStream
import imutils
import time
import cv2
import numpy as np
from collections import deque
from _functools import reduce
from wom_display_4x7_spi import Display4x7
import atexit
class WindowDetector() :
__WINDOW1_MARKERS = ( 1, 2, 3, 4, 0)
__WINDOW2_MARKERS = (11, 12, 13, 14, 10)
__WINDOW3_MARKERS = (21, 22, 23, 24, 20)
__WINDOW4_MARKERS = (31, 32, 33, 34, 30)
__WINDOW_MARKERS = (__WINDOW1_MARKERS, __WINDOW2_MARKERS, __WINDOW3_MARKERS, __WINDOW4_MARKERS)
__NO_MARKER_DETECTED = (False,False,False,False,False)
__NO_WINDOW_DETECTED = (__NO_MARKER_DETECTED,__NO_MARKER_DETECTED,__NO_MARKER_DETECTED,__NO_MARKER_DETECTED)
__BUFFER_LENGTH = 40
__FRAME_RATE = 4
__IMAGE_SIZE = 600
def __init__(self, display = Display4x7()):
self.display = display
atexit.register(self.cleanup)
def __movingDetector (self, iterable):
# iterates the buffer deque and ors the lists of booleans
return (reduce(lambda x, y: np.bitwise_or(list(x),list(y)), iterable)).tolist()
def __markersInWindow(self, windowMarkers, ids) :
# creates a tuple of booleans correspondig to the detection of the window markers
# top left corner, top right corner, bottom right corner, left right corner, moving part
list = []
for element in windowMarkers :
list.append(element in ids)
return tuple(list)
def __markersIn(self, windowMarkers, ids) :
# creates a tuple of tuples for the different markers found in window
list = []
for window in windowMarkers :
list.append(self.__markersInWindow(window, ids))
return tuple(list)
def start(self):
# starts the detector, grab images and display markers found
detectorBuffer = deque((), maxlen= WindowDetector.__BUFFER_LENGTH)
detectorBuffer.append(WindowDetector.__NO_WINDOW_DETECTED)
self.display.start()
arucoDict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_50)
arucoParams = cv2.aruco.DetectorParameters_create()
vs = VideoStream(src=0, framerate=WindowDetector.__FRAME_RATE).start()
# loop over the frames from the video stream
while True:
# grab the frame from the threaded video stream and resize it
frame = vs.read()
frame = imutils.resize(frame, width=WindowDetector.__IMAGE_SIZE)
# detect ArUco markers in the input frame
(mcorners, ids, rejected) = cv2.aruco.detectMarkers(frame, arucoDict, parameters=arucoParams)
# verify *at least* one ArUco marker was detected
if len(mcorners) > 0:
flatid = ids.flatten();
if len(detectorBuffer) >= WindowDetector.__BUFFER_LENGTH:
detectorBuffer.popleft()
detectorBuffer.append( self.__markersIn(self.__WINDOW_MARKERS, flatid))
else:
detectorBuffer.append(self.__NO_WINDOW_DETECTED)
self.display.displayWindowCorners(self.__movingDetector(detectorBuffer))
def __enter__(self) :
return self
def __exit__(self, exc_type, exc_value, traceback) :
self.cleanUp()
def cleanup() :
GPIO.cleanup()
cv2.destroyAllWindows()
vs.stop()
if __name__ == '__main__':
try:
windowDetector = WindowDetector()
windowDetector.start()
except KeyboardInterrupt:
print('interrupted!')
# do a bit of cleanup
sys.exit()
| Previous | Next |
|---|---|
| Window Opening Monitor with ArUco - Tracking window movements | Window Opening Monitor with ArUco - Final device |








