The History
I looked back at previous posts and it appears I have been working on this project for nearly 3 years!
The first part, being able to open the garage doors using wifi from a browser on my phone, has been up and running with various degrees of success for most of that time, but other areas have taken longer to get right.
So, what is this project? A few years ago I had our garage doors replaced with more modern sectional doors, fully insulated and much quieter than the old ones. The new doors came with a total of 6 wireless controls, 4 keyfob controls, two of which are on our key rings, and two of which are by the sunvisors of our two vehicles. There are also two wall mounted wireless controls. All 6 of these controls control both doors, so altogether a flexible package. However, just occasionally I will be outside the house without my keys and need to get into the garage to grab some tools or similar, which can mean a long walk around the house to get back in through the only unlocked door! The doors come with an optional WIFI ready controller, and an app for controlling the doors, but I felt the cost at the time was a bit prohibitive.
Feeling like an adventure and a foray into home automation, I decided to create a do it yourself solution. The garage doors come with a connector allowing a momentary switch to be wired across it, allowing the door to be opened without using the wireless remotes (just like in the old days!). I have some experience in working with Arduino and Raspberry PI processors, so I started planning a solution.
These are previous posts about the development
The Doors
I have no skills in writing phone apps, but I am experienced in quite technical web page design and writing, and if I was to use the web approach, I was probably better using a Raspberry PI.
I have already documented the development of this part of the project, the result of which was an Onion Omega2+ internet of things (IOT) computer, running a web server (a lite version that came with the Omega), and PHP and Python. The Omega was plugged into an expansion board, and a 2 relay hat attached.
The Web Page
The web page has evolved into a single piece of HTML with a few buttons, using CSS to give a consistent simple look. It can live on any web server on the network, but is not on the door opener Omega any longer. When a button to open a door is pressed, a Javascript function is called, which makes an asynchronous call to a PHP script on the web server on the door control Omega. This PHP script executes a Python script, which activates one of the two relays (very briefly), toggling the door open or closed. The web page can be used from any device with a web browser connected to the same wifi network, so two smart phones, a multitude of Windows 10 and Ubuntu computers, and even a Panasonic Smart TV can all control the automation!
This screen shot shows the complete webpage. The driveway camera is selected, and the inside lights switch has been activated.
The Cameras
Sounds great, but to make it a bit safer, I needed to be able to see that no one and no thing was close to the doors when opening or shutting. So I set up a Logitech C525 webcam on the Omega, pointing at the inside of the two doors. MJPG-Streamer was used to stream the image over the wifi system. The web page described above has a button that toggles the streamed image on and off on the web page. A second camera was attached to a second Omega2+ to show the driveway outside of the garage. This was an identical set up to the previous camera and Omega, just without the relays. Sadly, this second Omega gave up the ghost a few days ago and has been replaced, more about this later.
The Lights
So two garage doors controlled, and two cameras to make it safer…
But inside the garage is dark most of the time, and outside is also dark some of the time, and the cameras are not much use then. What we needed was to be able to turn the inside and outside lights on and off. Sounds like a job for some more relays. When I swapped the Raspberry PI with its 4 relay hat out, and replaced it with the Omega2+ and its 2 relay hat, I was left with a spare set of PI and relays – how convenient! The 4 relay hat worked beautifully with the low voltage low amperage current used to trigger the manual door opening, and the relays were rated up to 10 amps at 230v, so were perfect to switch lights on and off. The Seeed relay hat had some other benefits, the main one being the ability to be interrogated to find the present state of any of the relays. This would prove useful.
The two lights that need to be turned on and off are already switched on and off via normal switches, in fact they each can be turned on and off in two places using a clever bit of wiring. I didn’t want to lose any of this functionality, but I wanted to add a third switch in the circuit, controlled by the PI which can override, and be overridden by, the existing switches.

Our hallway lights are controlled by no less than 5 switches, any one of which can turn the lights on if they are off, or turn them off if they are on, regardless of which switch previously switched them off or on. This is achieved by an interesting, mind blowingly simple but at the same time just mindblowing piece of wiring. In the diagram below, the Crossover Switch can be repeated as many times as you like between the Main Switch and the Remote Switch, and each switch will reverse the current state of the circuit when operated.

The Crossover Switch in its manual form is a two gang, double throw switch, in other words two switches connected together, so a single rocker moves both switches at the same time. This can be replicated with two relays, activated together within the program code. With 4 relays in the hat, it gives two pairs to insert into each two switch circuit currently controlling the lights. Problem solved.
I could describe setting up the Raspberry Pi Model B and the SEEED Relay hat, but I won’t. I went through the process, got it working on the bench top and installed it into lighting circuits. Success – lights switch on and off as expected – for a while. After a few hours of use, the SEEED relay hat froze, causing the Raspberry PI to freeze in sympathy, and in extreme circumstances freezing my WIFI router!A few weeks investigation and research later, I discovered others were having the same problem. It appears that when mains voltage passes through the relay contacts. induction is set up in the relay board, which inteferes with the operation of the electronics. SEEED know about this, and very generously offerred to replace the relay board when I raised it with them. I didn’t take them up on the offer, they are based in China, and postage back to them made it not worthwhile.
What I did do was order a different, simpler more robust PI Relay Hat, from a company called BC Robotics in Canada. This one works using the GPIO pins rather than IC2 communication. I soldered on the 40 pin GPIO header connection ready to install on the Pi Model B, before I realised the PI Model B uses a 26 pin GPIO header! Bugger, the 3.5mm AV socket interfered with any possibilty of just using the first 26 pins. Fortunately, I had a Raspberry PI Zero Wifi on hand, intended for a later phase of the project, so that was duly set up with a web server, python and PHP, and RPi-GPIO, and all my code rewritten to work with GPIO instead of the IC2 interface. As mentioned above, the SEEED relay hat has this nice feature that allows the current state of any relay to be interrogated. I used this to keep a visual track of which lights were activated using the relays, showing the buttons on the web page in yellow when they were. This feature is not available using the GPIO pins, despite many web pages online saying it is. As the python scripts run as discreet process, with no continuity between one use and the next, normal programming variables were of no use either. In the end I used a simple work round, creating a file when a relay was activated, and deleting the file when it was deactivated. As the file is empty, it takes up no resources, in fact I am not sure it ever exists other than in some sort of cache somewhere.
The web page Lights buttons work the same way as the Door buttons, asynchronous javascript calls a PHP script, which executes a python script to activate the correct relay. The main difference is that this routine returns a status of the relay, showing it as on or off. The web page uses this to make the button yellow or blue. Additionally, when the page is first loaded the PHP script executes a python script that returns the relay status, setting the lights buttons to the correct colour.
The new PI and Hat work perfectly, with no long term issues.
Camera again
As I mentioned above, the second Onion Omega2+ failed (as I was moving it from one location to a better one). I tried to resurrect it, and in the end it bricked itself. As I don’t have an ethernet port for it, I am not able to re write the boot code, so I have abandoned it for now.
However, the PI Zero Wifi used to control the lights is in exactly the right place to run the second camera too. So I bought an On The Go (OTG) to USB-A adapter, and plugged the camera in. I installed Motion on the PI, and set up the Logitech C525 to feed and image to be streamed over the wifi. I changed both streams to use an image 640 by 360 pixels, which is the ratio used by both 720P cameras. I reduced the frame rate to 5FPS to give the Omega2+ and the PI some chance of processing the feeds correctly.
Moving the second camera to the PI, and setting up the cameras correctly has been a brilliant move, both streams are virtually real time, maybe 0.5 to 1 second delay, very good resolution, and great images. The doors and lights work almost instantly, and the Asynchonous Javascript in the webpage makes for an instant update on the screens.
Bonus Feature!
One morning I was lying awake wondering what to do next, and other research was going through my mind at the same time, when I realised I desperately needed a fingerprint recognition system outside the garage doors that would open the garage doors in response to the correct finger being presented. I mean, who doesn’t need this?
A company called Waveshare in China supplied the relevant hardware, and some software that passed itself off as a demonstration of the hardware capabilities. I had a Raspberry PI Model2B just waiting to repurposed, and I built up the required hardware and software to run the fingerprint reader. The fingerprint reader can remember up to 1000 fingerprints, each one of which can be assigned what it calls a “permit”, a single byte which can have up to 255 values. This allows an action to be recorded against a fingerprint. When a finger is pressed onto the reader, it is validated, and if valid, the “permit” is passed to the PI. The Python code on the PI, calls the PHP code on the door server, which uses the Python code on there to open the correct door, there are also permits to switch the lights on using the Lights server. The code that came with the Finger Print reader was a bit basic, and in places incorrect, so I rewrote it to make it more flexible and complete. I gave it the capability of recording the user’s name, which finger they are recording, and allowing the action to be chosen from a drop down. The interface is only available on a Linux terminal, so setting up a new user and fingerprint is still a bit of a specialist task, but it is a lot more straightforward and easy to use than the software that was supplied.
The hardware has been boxed up, weatherproofed, and is on the door post outside the garage. So I can now get into the garage even without my cell phone.
Job Done!
The Coding
The web page HTML.
The HTML could reside on any of the three computers above, all of them have web servers built in, and all are capable of running it. Just for fun though, it runs on a fourth computer, a recycled desktop set up as a NAS drive, using Ubuntu 20.04 Server edition, running headless. As a NAS drive it is really lightly loaded, and is on 24/7. The operating system runs off an SSD, and the main drives for the file server spin down after a few minutes of no use.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
<html> <head> <meta name="viewport" content="width=device-width" /> <title>Tiny Net</title> <style type="text/css"> body {background-color:lightblue; font-family: arial;} h1 {text-align: center; color:blue;} h3 {text-align: center; color:blue;} img {width:100%; height:auto; display: block; margin: 5 auto;} .inputform {width:100%; margin:auto; } .buttonline {width:300px; margin:auto;} .button {height:30px; width:100px; margin:10px 23px 10px 23px; background-color: blue; color:white;} .notification {margin:auto; text-align:center; color:blue; margin:15px;} .footer { width: 100%; bottom: 10px; text-align: center; font-size: 8pt; } </style> <script> function callpython(pythonscript,location,controlid) { if (location == 'doors') { url = "http://192.168.1.99/";} else { url = "http://192.168.1.95/";} url = url + "phpcallpython.php?pythonscript=" + pythonscript; // document.getElementById("debugtext1").innerHTML = url; var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { if (this.responseText == 'On') { document.getElementById(controlid).style.backgroundColor = 'yellow'; document.getElementById(controlid).style.color = 'black';} if (this.responseText == 'Off') { document.getElementById(controlid).style.backgroundColor = 'blue'; document.getElementById(controlid).style.color = 'white';} // document.getElementById("debugtext2").innerHTML = this.responseText; } } xmlhttp.open("GET", url, true); xmlhttp.send(); } function checkrelaystatus () { callpython('light1_status.py','lights','button3'); callpython('light2_status.py','lights','button4'); } function showgarage () { if (document.getElementById("garagecam").innerHTML != "") { document.getElementById("garagecam").innerHTML = ""; } else { document.getElementById("garagecam").innerHTML = '<img src= "http://192.168.1.99:8080/?action=stream"/>'; } } function showdriveway () { if (document.getElementById("drivecam").innerHTML != "") { document.getElementById("drivecam").innerHTML = ""; } else { document.getElementById("drivecam").innerHTML = '<img src= "http://192.168.1.95:8081/?action=stream"/>'; } } </script> </head> <body onload="checkrelaystatus()"> <div class="inputform"> <h3>Lights</h3> <div class="buttonline"> <button class="button" id="button4" onclick="callpython('light2_toggle.py','lights','button4')">Inside</button> <button class="button" id="button3" onclick="callpython('light1_toggle.py','lights','button3');">Outside</button> </div> <h3>Camera</h3> <div class="buttonline"> <button class="button" onclick="showgarage()">Garage</button> <button class="button" onclick="showdriveway()">Driveway</button> </div> <h3>Action</h3> <div class="buttonline"> <button class="button" onclick="callpython('relay_flick_1.py','doors')">North</button> <button class="button" onclick="callpython('relay_flick_2.py','doors')">South</button> </div> <div class="notification" id="debugtext1"></div> <div class="notification" id="debugtext2"></div> </div> <div id="garagecam"></div> <div id="drivecam"></div> <div id="myfooter" class="footer"> Copyright Dave Glover (2020<?php if(date('Y') != 2020){echo ' - '.date('Y');}?>) </div> </body> </html> |
The PHP used on the Doors server.
The first line allows cross origin access via Javascript. Not actually essential in this context, but it is there anyway.
The code executes a python script, on the Omega system there is no need to run the command as Sudo as there is in Raspbian
1 2 3 4 5 6 7 8 9 |
<?php header("Access-Control-Allow-Origin:*"); $pythonscript = $_REQUEST["pythonscript"]; $pythonexec = "python"; $pythonlib = "/www/omegarelay/"; $cmd = $pythonexec." ".$pythonlib.$pythonscript; exec($cmd, $output); ?> |
The python used to open a door on the Doors server.
This uses Omega specific run time libraries (as the Relay Hat is an Onion Omega product).
A similar piece of code opens the second door.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from OmegaExpansion import relayExp import sys import time addr = 7 relayExp.setVerbosity(0) ret = relayExp.checkInit(addr) if (ret != 1): ret = relayExp.driverInit(addr) ret = relayExp.setChannel(addr, 0, 1) time.sleep(.2) ret = relayExp.setChannel(addr, 0, 0) |
The PHP used on the Lights server.
The first line is required in this case, as this function returns values used on the webpage with the Javascript on it
Note the Sudo in the command.
1 2 3 4 5 6 7 8 9 |
<php header("Access-Control-Allow-Origin:*"); $pythonscript = $_REQUEST["pythonscript"]; $pythonexec = "/usr/bin/python3.7"; $pythonlib = "/var/www/pirelay/"; $cmd = 'sudo '.$pythonexec." ".$pythonlib.$pythonscript; echo exec($cmd); ?> |
The Python to turn a light on
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/usr/bin/python '''***************************************************************************************************************** ********************************************************************************************************************''' from Relays import * if os.path.exists("light1"): relay_off(relay1) relay_off(relay2) light_unset("light1") print ('Off') else: relay_on(relay1) relay_on(relay2) light_set("light1") print ('On') |
The Python to check the status of a relay
1 2 3 |
#!/usr/bin/python from Relays import * light_status ("light1") |
The python code to perform relay actions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# ========================================================= import RPi.GPIO as GPIO import os relay1 = 4 relay2 = 17 relay3 = 27 relay4 = 22 GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) def relay_on(relay_num): GPIO.setup(relay_num, GPIO.OUT) GPIO.output(relay_num, GPIO.HIGH) def relay_off(relay_num): GPIO.setup(relay_num, GPIO.OUT) GPIO.output(relay_num, GPIO.LOW) def light_status(light_num): if os.path.exists(light_num): print ("On") else: print ("Off") def light_set(light_num): f = open(light_num, "x") f.close() def light_unset(light_num): if os.path.exists(light_num): os.remove(light_num) |
Finally, the finger print reader code.
First the main module
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#!/usr/bin/env python3.5 # -*- coding:utf-8 -*- # Fingerprint reader by Dave Glover, May 2020 # Derived from test source provided by Waveshare here - # https://www.waveshare.com/wiki/File:Capacitive-Fingerprint-Reader-Code.7z # Notable corrections - ACK_USER_EXIST is value 0x07 not 0x06 # - Code based on count of users fails when a facility to delete # fingerprints is added. A new function to find the highest userid in use is required # before new fingerprints can be registered. # Notable additions or alterations # 1. A userfile of user names and the finger being used is kept in synch with the system provided user detail # 2. A function to clear an individual user is provided, and to clear all users. # 3. Two LEDs are catered for, so that success or failure when using the FingerPrint reader sensor can be seen # and when a fingerprint is recognised, a request using a URL on another machine will open garage doors # 4. The program defaults to sleep mode when started which means the application # can be started at boot time, and it will be in automatic mode by default. # The following line can be added to the rc.local file before the 'exit 0' line # in order to start it at boot, with all output redirected into GNU screen # su pi -c 'screen -d -m -S test /python path/python3.7 /your path/main.py' # Add this line "screen -r" to the end of .bashrc to start automatically after SSH&login from Capacitive_Fingerprint_Reader import * def main(): GPIO.output(Finger_RST_Pin, GPIO.LOW) time.sleep(1) GPIO.output(Finger_RST_Pin, GPIO.HIGH) time.sleep(1) # Wait for module to start while SetCompareLevel(5) != 5: print ("***ERROR***: system type error") time.sleep(5) print (" Compare Level: %d " % GetCompareLevel()); print (" Fingerprints on system: %d " % GetUserCount()) m=showmenu() thread_Auto_Verify_Finger = threading.Thread(target=Auto_Verify_Finger,args=()) thread_Auto_Verify_Finger.setDaemon(True) thread_Auto_Verify_Finger.start() while True: print (" Please input command, from the menu:",end = "\n ") PC_Command_RxBuf.append(input()) Analysis_PC_Command() del PC_Command_RxBuf[:] # Clear PC_Command_RxBuf and prepare for the next time if __name__ == '__main__': try: main() except KeyboardInterrupt: if ser != None: ser.close() GPIO.cleanup() print("\n\n Fingerprint Reader Terminated \n") sys.exit() |
And then the real work
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 |
#!/usr/bin/env python3.5 # -*- coding:utf-8 -*- # Fingerprint reader by Dave Glover, May 2020 # Derived from test source provided by Waveshare here - # https://www.waveshare.com/wiki/File:Capacitive-Fingerprint-Reader-Code.7z import serial import time import threading import sys import RPi.GPIO as GPIO import os TRUE = 1 FALSE = 0 # Basic response message definition ACK_SUCCESS = 0x00 ACK_FAIL = 0x01 ACK_FULL = 0x04 ACK_NO_USER = 0x05 ACK_USER_EXIST = 0x07 ACK_TIMEOUT = 0x08 ACK_GO_OUT = 0x0F # The center of the fingerprint is out of alignment with sensor # User information definition ACK_NORTH = 0x01 ACK_SOUTH = 0x02 ACK_INSIDE = 0x03 ACK_OUTSIDE = 0x04 USER_MAX_CNT = 1000 # Maximum fingerprint number # Command definition CMD_HEAD = 0xF5 CMD_TAIL = 0xF5 CMD_ADD_1 = 0x01 CMD_ADD_2 = 0x02 CMD_ADD_3 = 0x03 CMD_MATCH = 0x0C CMD_DEL = 0x04 CMD_DEL_ALL = 0x05 CMD_USER_CNT = 0x09 CMD_COM_LEV = 0x28 CMD_GET_ALL_USERS = 0x2B CMD_LP_MODE = 0x2C CMD_TIMEOUT = 0x2E CMD_FINGER_DETECTED = 0x14 Finger_WAKE_Pin = 23 Finger_RST_Pin = 24 Green_LED_Pin = 18 Red_LED_Pin = 17 GPIO.setmode(GPIO.BCM) GPIO.setup(Finger_WAKE_Pin, GPIO.IN) GPIO.setup(Finger_RST_Pin, GPIO.OUT) GPIO.setup(Finger_RST_Pin, GPIO.OUT, initial=GPIO.HIGH) GPIO.setup(Green_LED_Pin, GPIO.OUT) GPIO.setup(Green_LED_Pin, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(Red_LED_Pin, GPIO.OUT) GPIO.setup(Red_LED_Pin, GPIO.OUT, initial=GPIO.LOW) g_rx_buf = [] PC_Command_RxBuf = [] Finger_SleepFlag = 1 userid = 0 permits = 0 rLock = threading.RLock() ser = serial.Serial("/dev/ttyAMA0", 19200) ## #urls for each action registerd in ACK-PERMIT #specifies IP of server, module called, and name of script to be run # urls = ["http://192.168.1.99/phpcallpython.php?pythonscript=relay_flick_1.py", "http://192.168.1.99/phpcallpython.php?pythonscript=relay_flick_2.py", "http://192.168.1.95/phpcallpython.php?pythonscript=light1_toggle.py", "http://192.168.1.95/phpcallpython.php?pythonscript=light2_toggle.py"] #*************************************************************************** # @brief send a command, and wait for the response of module #***************************************************************************/ def TxAndRxCmd(command_buf, rx_bytes_need, timeout): global g_rx_buf CheckSum = 0 tx_buf = [] tx_buf.append(CMD_HEAD) for byte in command_buf: tx_buf.append(byte) CheckSum ^= byte tx_buf.append(CheckSum) tx_buf.append(CMD_TAIL) ser.flushInput() ser.write(tx_buf) g_rx_buf = [] time_before = time.time() time_after = time.time() while time_after - time_before < timeout and len(g_rx_buf) < rx_bytes_need: # Waiting for response bytes_can_recv = ser.inWaiting() if bytes_can_recv != 0: g_rx_buf += ser.read(bytes_can_recv) time_after = time.time() if len(g_rx_buf) != rx_bytes_need: return ACK_TIMEOUT if g_rx_buf[0] != CMD_HEAD: return ACK_FAIL if g_rx_buf[rx_bytes_need - 1] != CMD_TAIL: return ACK_FAIL if g_rx_buf[1] != tx_buf[1]: return ACK_FAIL CheckSum = 0 for index, byte in enumerate(g_rx_buf): if index == 0: continue if index == 6: if CheckSum != byte: return ACK_FAIL CheckSum ^= byte return ACK_SUCCESS; #*************************************************************************** # @brief Show the text menu #***************************************************************************/ def showmenu(): if Finger_SleepFlag == 1: print ("\n Module in sleep mode. Only WAKE is valid\n") print (" Main Menu") print (" WAKE : Wake up to allow commands") else: print ("\n Module is awake. All commands valid\n") print (" Main Menu") print (" COUNT : Count existing fingerprints") print (" ADD : Register fingerprint") print (" MATCH : Fingerprint matching") print (" CLEAR : Clear fingerprints ") print (" LIST : List Users") print (" SLEEP : Switch to background sleep mode") #*************************************************************************** # @brief Get Compare Level #***************************************************************************/ def GetCompareLevel(): global g_rx_buf command_buf = [CMD_COM_LEV, 0, 0, 1, 0] r = TxAndRxCmd(command_buf, 8, 0.1) if r == ACK_TIMEOUT: return ACK_TIMEOUT if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: return g_rx_buf[3] else: return 0xFF #*************************************************************************** # @brief Set Compare Level,the default value is 5, # can be set to 0-9, the bigger, the stricter #***************************************************************************/ def SetCompareLevel(level): global g_rx_buf command_buf = [CMD_COM_LEV, 0, level, 0, 0] r = TxAndRxCmd(command_buf, 8, 0.1) if r == ACK_TIMEOUT: return ACK_TIMEOUT if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: return g_rx_buf[3] else: return 0xFF #*************************************************************************** # @brief Query the number of existing fingerprints #***************************************************************************/ def GetUserCount(): global g_rx_buf command_buf = [CMD_USER_CNT, 0, 0, 0, 0] r = TxAndRxCmd(command_buf, 8, 0.1) if r == ACK_TIMEOUT: return ACK_TIMEOUT if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: return g_rx_buf[3] else: return 0xFF #*************************************************************************** # @brief Read through the list of users to find the highest userid in use #***************************************************************************/ def GetLastUser(): global g_rx_buf #Get a count of users lastuser = 0 r=GetUserCount() #calculate length of buffer to read user details if r == 0: return lastuser else: lenbuffer = 13 + (int(r)*3) command_buf = [CMD_GET_ALL_USERS, 0, 0, 0, 0] r = TxAndRxCmd(command_buf, lenbuffer, 1) if r == ACK_TIMEOUT: return ACK_TIMEOUT if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: #print ("Success ", g_rx_buf, " ", len(g_rx_buf)) #Extract the userid count from data header countuser = (g_rx_buf[9] * 256) + g_rx_buf[10] i = 1 #loop through each tripe of userid * permission while i < countuser + 1: B1 = 8 + i*3 B2 = B1 + 1 #extract userid from data bytes thisuser = (g_rx_buf[B1] * 256) + g_rx_buf[B2] #check if this is the new highest if thisuser > lastuser: lastuser = thisuser i = i + 1 return lastuser else: return 0xFF #*************************************************************************** # @brief Get the time that fingerprint collection wait timeout #***************************************************************************/ def GetTimeOut(): global g_rx_buf command_buf = [CMD_TIMEOUT, 0, 0, 1, 0] r = TxAndRxCmd(command_buf, 8, 0.1) if r == ACK_TIMEOUT: return ACK_TIMEOUT if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: return g_rx_buf[3] else: return 0xFF #*************************************************************************** # @brief Register fingerprint #***************************************************************************/ def AddUser(): global g_rx_buf global userid global permits userid = GetLastUser() + 1 if userid >= USER_MAX_CNT: return ACK_FULL print (" Users Name?") username = input() print (" Enter Digit e.g LT, LI, LM, LR, LL") print (" or RT, RI, RM, RR, RL") userdigit = input() print (" Enter door(North or South) or Light (Inside or Outside)") ActionText = " " ActionText = input() if ActionText == "North" or ActionText == "N" or ActionText == "n": permits = ACK_NORTH elif ActionText == "South" or ActionText == "S" or ActionText == "s": permits = ACK_SOUTH elif ActionText == "Inside" or ActionText == "I" or ActionText == "i": permits = ACK_INSIDE elif ActionText == "Outside" or ActionText == "O" or ActionText == "0": permits = ACK_OUTSIDE else: permits = ACK_NORTH # default to north door command_buf = [CMD_ADD_1, 0, userid, permits, 0] print (" Place finger on sensor") r = TxAndRxCmd(command_buf, 8, 5) if r == ACK_TIMEOUT: return ACK_TIMEOUT if r == ACK_SUCCESS and g_rx_buf[4] == ACK_USER_EXIST: return ACK_USER_EXIST time.sleep(2) if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: print (" Place finger again") command_buf[0] = CMD_ADD_2 r = TxAndRxCmd(command_buf, 8, 5) if r == ACK_TIMEOUT: return ACK_TIMEOUT time.sleep(2) if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: print (" Place finger yet again") command_buf[0] = CMD_ADD_3 r = TxAndRxCmd(command_buf, 8, 5) if r == ACK_TIMEOUT: return ACK_TIMEOUT if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: #append user details to userfile file = open("/var/www/pyfinger/userfile", "at") userrecord = ",".join([str(userid), username, userdigit, str(permits), "\n"]) file.writelines(userrecord) file.close return ACK_SUCCESS else: return ACK_FAIL else: return ACK_FAIL else: return ACK_FAIL #*************************************************************************** # @brief read the user file to find a matching user name for a fingerprint #***************************************************************************/ def MatchUserDetails(userid): infile = open("/var/www/pyfinger/userfile", "rt") for line in infile.readlines(): newline = line splitstr =newline.split(",",1) uid = int(splitstr[0]) if uid == userid: return line infile.close #*************************************************************************** # @brief delete user name etc for a deleted fingerpprint #***************************************************************************/ def DeleteUserDetails(userid): import os infile = open("/var/www/pyfinger/userfile", "rt") outfile = open("/var/www/pyfinger/newuserfile", "at") for line in infile.readlines(): newline = line splitstr =newline.split(",",1) uid = int(splitstr[0]) if uid != userid: outfile.write(newline) infile.close outfile.close os.remove ("/var/www/pyfinger/userfile") os.rename (r'/var/www/pyfinger/newuserfile',r'/var/www/pyfinger/userfile') #*************************************************************************** # @brief List all the user names and setails on the userfile #***************************************************************************/ def listusers(): if os.path.isfile("/var/www/pyfinger/userfile"): file = open("/var/www/pyfinger/userfile", "rt") print (" List of users") print (" Userid Name Digit Permission") for line in file.readlines(): splitstr =line.split(",") userid = splitstr[0] username = splitstr[1] userdigit = splitstr[2] userpermits = splitstr[3] print (" ",userid, " ", username, " ", userdigit, " ", userpermits) file.close print (" End of list\n") else: print(" No Users Found") #*************************************************************************** # @brief Clear fingerprints #***************************************************************************/ def ClearUsers(): global g_rx_buf count = GetLastUser() print (" Delete users") if count == 0: print (" Nothing to delete") return ACK_SUCCESS else: r=listusers() print (" Enter userid from list (A for all)") userid = input() if userid =="A" or userid =="a": userid = FALSE command_buf = [CMD_DEL_ALL, 0, 0, 0, 0] elif userid.isdigit(): userid = int(userid) if (userid in range (1,count+1)): command_buf = [CMD_DEL, 0, userid, 0, 0] else: print (" not a valid number") return ACK_FAIL else: print (" Not a valid number") return ACK_FAIL r = TxAndRxCmd(command_buf, 8, 0.1) if r == ACK_TIMEOUT: return ACK_TIMEOUT if r == ACK_SUCCESS and g_rx_buf[4] == ACK_SUCCESS: if userid == FALSE: print (" Deleting all users") os.remove ("/var/www/pyfinger/userfile") else: print (" Deleting user ", userid) r=DeleteUserDetails(userid) return ACK_SUCCESS else: return ACK_FAIL #*************************************************************************** # @brief Check if user ID is between 1 and 3 #***************************************************************************/ def IsPermittedUser(permits): if permits == ACK_NORTH or permits == ACK_SOUTH or permits == ACK_INSIDE or permits == ACK_OUTSIDE: return TRUE else: return FALSE #*************************************************************************** # @brief Fingerprint matching #***************************************************************************/ def VerifyUser(): global g_rx_buf global userid global permits command_buf = [CMD_MATCH, 0, 0, 0, 0] r = TxAndRxCmd(command_buf, 8, 5); if r == ACK_TIMEOUT: return ACK_TIMEOUT elif r == ACK_SUCCESS and IsPermittedUser(g_rx_buf[4]): permits = (g_rx_buf[4]) userid = (g_rx_buf[2]) * 256 + (g_rx_buf[3]) return ACK_SUCCESS elif g_rx_buf[4] == ACK_NO_USER: return ACK_NO_USER elif g_rx_buf[4] == ACK_TIMEOUT: return ACK_TIMEOUT else: return ACK_GO_OUT # The center of the fingerprint is out of alignment with sensor #*************************************************************************** # @brief Read a finger print using the verify, then open the relevant door # and flick the green LED on , or show a failure message with a red LED #***************************************************************************/ def Fingerprint_Read(): global userid global permits print (" Read fingerprint") print (" Waiting for finger") r = VerifyUser() if r == ACK_SUCCESS: print (" Matched Userid ", MatchUserDetails(userid)) r = turn_green_led_on() o = Perform_Action(permits) if o == "failed": r = turn_red_led_on() print (" Failed URL/HTTP error") elif r == ACK_NO_USER: r = turn_red_led_on() print (" Failed: fingerprint not found") elif r == ACK_TIMEOUT: r = turn_red_led_on() print (" Failed: Time out !") elif r == ACK_GO_OUT: r = turn_red_led_on() print (" Failed: Not centred or flat") #*************************************************************************** # @brief Analysis the command from PC terminal #***************************************************************************/ def Analysis_PC_Command(): global Finger_SleepFlag global userid global permits cmd = PC_Command_RxBuf[0].upper() if cmd == "COUNT" and Finger_SleepFlag != 1: print (" Fingerprints on system: %d" % GetUserCount()) elif cmd == "ADD" and Finger_SleepFlag != 1: print (" Add fingerprint") r = AddUser() if r == ACK_SUCCESS: print (" Added Userid ", userid, " Permission ", permits) elif r == ACK_TIMEOUT: print (" Timed out!") elif r == ACK_USER_EXIST: print (" User finger already exists!") elif r == ACK_FAIL: print (" Failed: Centre finger on sensor") elif r == ACK_FULL: print (" Failed: fingerprint library full") elif cmd == "MATCH" and Finger_SleepFlag != 1: r=Fingerprint_Read() time.sleep(1) r = turn_green_led_off() r = turn_red_led_off() elif cmd == "CLEAR" and Finger_SleepFlag != 1: ClearUsers() elif cmd == "SLEEP" and Finger_SleepFlag != 1: GPIO.output(Finger_RST_Pin, GPIO.LOW) Finger_SleepFlag = 1 m=showmenu() elif cmd == "WAKE": if rLock.acquire(blocking=True, timeout=0.6) == True: Finger_SleepFlag = 0 GPIO.output(Finger_RST_Pin, GPIO.HIGH) time.sleep(1) # Wait for module to start rLock.release() m=showmenu() elif cmd == "LIST" and Finger_SleepFlag != 1: l =listusers() else: print (" Menu item not found") m=showmenu() #*************************************************************************** # @brief Flick the relay to open the garage door that corresponds to # the fingerprints permission (1 North. 2 South #***************************************************************************/ def Perform_Action(action): import urllib.request from urllib.error import URLError # URL of the php code on the relevant server. # If opening a door use garage door opener Onion Omega 2S webserver, # If switching on a light use the TinyNet Raspberry PI server #see URLS array at top of program # All of these use EXEC SUDO to call a python script to perform the correct action # this php module and python scripts are used by web pages to flick relays on and off. url = urls[action-1] # request the URL to be opened. No data or resonse is available # but it needs the return variable to avoid timeouts # network errors and HTTP errors are caught by the exceptions try: r = urllib.request.urlopen (url) except urllib.error.URLError as e: print(e.reason) return "failed" else: print ("URL = ", url) return "OK" #*************************************************************************** # @brief Turn LEDs on and off depending on status of finger print verification #***************************************************************************/ def turn_green_led_on(): GPIO.output(Green_LED_Pin, GPIO.HIGH) def turn_green_led_off(): GPIO.output(Green_LED_Pin, GPIO.LOW) def turn_red_led_on(): GPIO.output(Red_LED_Pin, GPIO.HIGH) def turn_red_led_off(): GPIO.output(Red_LED_Pin, GPIO.LOW) #*************************************************************************** # @brief If you enter the sleep mode, then open the Automatic wake-up function of the finger, # begin to check if the finger is pressed, and then start the module and match #***************************************************************************/ def Auto_Verify_Finger(): global Finger_SleepFlag # Put module into sleep mode by default to enable script to run at start up # and go straight into fingerprint recognition mode GPIO.output(Finger_RST_Pin, GPIO.LOW) time.sleep(1) Finger_SleepFlag = 1 while True: if rLock.acquire() == True: # If you enter the sleep mode, then open the Automatic wake-up function of the finger, # begin to check if the finger is pressed, and then start the module and match if Finger_SleepFlag == 1: if GPIO.input(Finger_WAKE_Pin) == 1: # If you press your finger time.sleep(0.01) if GPIO.input(Finger_WAKE_Pin) == 1: GPIO.output(Finger_RST_Pin, GPIO.HIGH) # Pull up the RST to start the module and start matching the fingers time.sleep(1) # Wait for module to start r=Fingerprint_Read() #After the matching action is completed, drag RST down to sleep GPIO.output(Finger_RST_Pin, GPIO.LOW) #Sleep for 5 seconds to avoid kicking off the next loop before the pin is set low #and give time for user to see LEDs time.sleep (5) r = turn_green_led_off() r = turn_red_led_off() rLock.release() |