{"id":863,"date":"2020-02-21T18:50:51","date_gmt":"2020-02-22T01:50:51","guid":{"rendered":"http:\/\/zethus.ca\/wp\/?p=863"},"modified":"2020-02-21T19:17:47","modified_gmt":"2020-02-22T02:17:47","slug":"photobooth","status":"publish","type":"post","link":"http:\/\/zethus.ca\/wp\/?p=863","title":{"rendered":"PhotoBooth"},"content":{"rendered":"\n<p>My daughter is getting married, and I though it would be fun to build a photobooth.  What I wanted what something that took pictures, and saved them locally, so they could be downloaded by participants with phones.  No cloud. No internet.   Initially I came up with the following:<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Photobooth Requirements and Design<\/h1>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"459\" src=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/photobooth-1024x459.png\" alt=\"Hardware setup\" class=\"wp-image-865\" srcset=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/photobooth-1024x459.png 1024w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/photobooth-300x134.png 300w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/photobooth-768x344.png 768w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/photobooth-500x224.png 500w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/photobooth.png 1136w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Hardware:<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Raspberry Pi 4, for speed<\/li><li>8MP rpi camera &#8211; should be good enough, need to test.<\/li><li>LED strips for &#8220;flash&#8221;, 12-24 input, probably 12.<\/li><li>control circuit required, should be able to use GPIO from the pi to PWM brightness<\/li><li>Monitor (hdmi) for preview, instructions, slideshow.<\/li><li>DMD display for &#8220;countdown&#8221; attract mode. etc.<\/li><li>Arcade Buttons for picture. Cat 5 connectors for ease of connection, or alternatively, leaning toward using an esp 8266 to control the pictures over WiFi captive portal\/url<\/li><li>Computer power supply with +5 and +12, high current for DMD and LEDs etc.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Software:<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Based off of raspian-buster.<\/li><li>Picture taker.<\/li><li>Overlay picture on green screen.<\/li><li>Save Picture with ID.<\/li><li>Captive Portal<\/li><li>Access code required. <\/li><li>Email capture<\/li><li>Photo Download.<\/li><li>DMD Display Process<\/li><li>ESP 8266 button press receiver code.<\/li><\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">Implementation, based on pirate box:<\/h1>\n\n\n\n<p>I originally thought somthing based off the pirate box might be cool: <a href=\"https:\/\/piratebox.cc\/raspberry_pi:diy:armbian\">https:\/\/piratebox.cc\/raspberry_pi:diy:armbian<\/a>.  This was a fail, raspian image was obsolete, and this didnt provide the required functionality<br><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Implementation, second try:<\/h1>\n\n\n\n<p>Next I tried something off of this <a href=\"https:\/\/www.instructables.com\/id\/Wifi-Photobooth-With-a-Raspberry-Pi\/\">instructable page<\/a>. I started off with graphical buster:<br>then<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nsudo apt-get update\nsudo apt-get upgrade\nsudo apt-get dist-upgrade\n<\/pre><\/div>\n\n\n<p><a href=\"https:\/\/elinux.org\/RPI-Wireless-Hotspot\">Install the wireless hotspot<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1. Setup the access point<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Note: use this to set up different repo mirrors, if required: <a href=\"https:\/\/pimylifeup.com\/raspbian-repository-mirror\/\">mirror<\/a><\/li><li><a href=\"https:\/\/pimylifeup.com\/raspberry-pi-wireless-access-point\/\">Install the access point<\/a><\/li><li>note I did not use any of the ip forwarding stuff.<\/li><li><a href=\"https:\/\/pimylifeup.com\/raspberry-pi-captive-portal\/\">install the captive portal<\/a><\/li><li>used qifi to <a href=\"https:\/\/qifi.org\/\">generate the qr code for the website<\/a><\/li><li>Captive portal is working. Edit \/etc\/nodogsplash\/htdocs\/splash.html to redirect to the custom webpage using the <a href=\"https:\/\/www.w3docs.com\/snippets\/html\/how-to-redirect-a-web-page-in-html.html\">meta tab<\/a><\/li><li>note we need to use php7.3 which is the default php now<\/li><li>the instructions for directory permissions were wrong, missing -R on chown of group www-data<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">General Software Design<\/h2>\n\n\n\n<p>Up to this point we have a camera working with some minimal software<\/p>\n\n\n\n<p>I Used the code from <a href=\"https:\/\/github.com\/hzeller\/rpi-rgb-led-matrix\">https:\/\/github.com\/hzeller\/rpi-rgb-led-matrix<\/a> for LED matrix code.<\/p>\n\n\n\n<p>Installed the PCF8523 RTC as per <a href=\"https:\/\/learn.adafruit.com\/adding-a-real-time-clock-to-raspberry-pi\/set-rtc-time\">https:\/\/learn.adafruit.com\/adding-a-real-time-clock-to-raspberry-pi\/set-rtc-time<\/a><\/p>\n\n\n\n<p>A Usb audio dongle was used (i couldn&#8217;t use the builtin display, it conflicted with the DMD.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Final Product<\/h2>\n\n\n\n<p>Before I dump you into the code, here are some pictures of the final product:<\/p>\n\n\n\n<figure class=\"wp-block-gallery columns-3 is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\"><ul class=\"blocks-gallery-grid\"><li class=\"blocks-gallery-item\"><figure><a href=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3910-768x1024.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"1024\" src=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3910-768x1024.jpg\" alt=\"\" data-id=\"872\" data-link=\"http:\/\/zethus.ca\/wp\/?attachment_id=872\" class=\"wp-image-872\" srcset=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3910-768x1024.jpg 768w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3910-225x300.jpg 225w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3910-1152x1536.jpg 1152w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3910-1536x2048.jpg 1536w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3910-scaled.jpg 1920w\" sizes=\"auto, (max-width: 768px) 100vw, 768px\" \/><\/a><figcaption class=\"blocks-gallery-item__caption\">DMD installed<\/figcaption><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><a href=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15b2.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15b2.jpg\" alt=\"\" data-id=\"873\" data-link=\"http:\/\/zethus.ca\/wp\/?attachment_id=873\" class=\"wp-image-873\" srcset=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15b2.jpg 1024w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15b2-300x225.jpg 300w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15b2-768x576.jpg 768w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15b2-400x300.jpg 400w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"blocks-gallery-item__caption\">DMD interface<\/figcaption><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><a href=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3916-768x1024.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"1024\" src=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3916-768x1024.jpg\" alt=\"\" data-id=\"874\" data-link=\"http:\/\/zethus.ca\/wp\/?attachment_id=874\" class=\"wp-image-874\" srcset=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3916-768x1024.jpg 768w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3916-225x300.jpg 225w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3916-1152x1536.jpg 1152w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3916-1536x2048.jpg 1536w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3916-scaled.jpg 1920w\" sizes=\"auto, (max-width: 768px) 100vw, 768px\" \/><\/a><figcaption class=\"blocks-gallery-item__caption\">Remote control<\/figcaption><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><a href=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3915-768x1024.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"1024\" src=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3915-768x1024.jpg\" alt=\"\" data-id=\"877\" data-link=\"http:\/\/zethus.ca\/wp\/?attachment_id=877\" class=\"wp-image-877\" srcset=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3915-768x1024.jpg 768w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3915-225x300.jpg 225w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3915-1152x1536.jpg 1152w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3915-1536x2048.jpg 1536w, http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/IMG_3915-scaled.jpg 1920w\" sizes=\"auto, (max-width: 768px) 100vw, 768px\" \/><\/a><figcaption class=\"blocks-gallery-item__caption\">Esp-01 running Remote<\/figcaption><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><a href=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15e7.jpg\"><img decoding=\"async\" src=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15e7-410x1024.jpg\" alt=\"\" data-id=\"879\" data-full-url=\"http:\/\/zethus.ca\/wp\/wp-content\/uploads\/2020\/02\/UNADJUSTEDNONRAW_thumb_15e7.jpg\" data-link=\"http:\/\/zethus.ca\/wp\/?attachment_id=879\" class=\"wp-image-879\"\/><\/a><figcaption class=\"blocks-gallery-item__caption\">Sample image<br><\/figcaption><\/figure><\/li><\/ul><\/figure>\n\n\n\n<p>This is the photobooth in the early development stages<\/p>\n\n\n\n<figure class=\"wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Photobooth\" width=\"584\" height=\"329\" src=\"https:\/\/www.youtube.com\/embed\/jUGv7KnnYKQ?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>This is the (yuk) python code, I&#8217;m not proud of any of this code, but it worked well enough for me:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport glob\nimport sys\nimport os\nimport time\nimport random\nimport subprocess\nimport threading\nimport shlex\nimport cv2\nimport pygame\nimport picamera\nimport sounddevice as sd\nimport soundfile as sf\nfrom PIL import Image\nfrom pygame.locals import *\nimport RPi.GPIO as GPIO\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\nos.environ&#x5B;&quot;SDL_FBDEV&quot;] = &quot;\/dev\/fb0&quot;\n\n#setup the button webserver\nfrom time import sleep\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\nhost_name = '192.168.220.1'  # Change this to your Raspberry Pi IP address\nhost_port = 8000\n\nclass Photobooth:\n    screen = None\n    o = None\n\n################################################################################\n#init\n    def __init__(self):\n        global ScreenSize , backgrounds, awb\n        &quot;Ininitializes a new pygame screen using the framebuffer&quot;\n        # Based on &quot;Python GUI in Linux frame buffer&quot;\n        # http:\/\/www.karoltomala.com\/blog\/?p=679\n        disp_no = os.getenv(&quot;DISPLAY&quot;)\n        if disp_no:\n            print (&quot;I'm running under X display = {0}&quot; + disp_no)\n\n        # Check which frame buffer drivers are available\n        # Start with fbcon since directfb hangs with composite output\n        drivers = &#x5B;'fbcon', 'directfb', 'svgalib']\n        found = False\n        for driver in drivers:\n            # Make sure that SDL_VIDEODRIVER is set\n            if not os.getenv('SDL_VIDEODRIVER'):\n                os.putenv('SDL_VIDEODRIVER', driver)\n            try:\n                pygame.display.init()\n            except pygame.error:\n                print (&quot;Driver: {0} failed.&quot; + driver)\n                continue\n            found = True\n            break\n\n        if not found:\n            raise Exception('No suitable video driver found!')\n\n        ScreenSize = (pygame.display.Info().current_w, pygame.display.Info().current_h)\n        print (&quot;Framebuffer size: %d x %d&quot; % (ScreenSize&#x5B;0], ScreenSize&#x5B;1]))\n        self.screen = pygame.display.set_mode(ScreenSize, pygame.FULLSCREEN)\n        # Clear the screen to start\n        self.screen.fill((0, 0, 0))\n        # Initialise font support\n        pygame.font.init()\n        # Render the screen\n        pygame.display.update()\n        pygame.mouse.set_visible(False)\n\n        #load in the background images\n        backgrounds = glob.glob('Backgrounds\/*.jpg')\n        bgidx = -1\n        print(&quot;Backgrounds found: &quot;)\n        print( *backgrounds)\n\n        \n################################################################################\n#destrctor\n    def __del__(self):\n        pygame.quit()\n        #Destructor to make sure pygame shuts down, etc.\n\n################################################################################\n# slideshow\n    # Shows an arbitrary picture from the AllPicturesList\n    def slideshow(self):\n        global AllPicturesList, PictureFolder, NumberOfPicturesTaken, IsCamActive, RandomPicCount\n        if IsCamActive is True:  # Stop preview if it is active.\n            self.StopPreview()\n\n        #unload overlay \n        if self.o:\n            self.o.close()\n\n        # Load a random Picture from the list.\n        RandomPicCount += 1\n        if RandomPicCount &gt; 10:\n            rannum = 0\n            RandomPicCount = 0\n        else:\n            rannum = random.randint(0, NumberOfPicturesTaken - 1)\n\n        print (&quot;load picture %d&quot; % rannum)\n        NextPictureName = AllPicturesList&#x5B;rannum]\n        TempPicture = os.path.join(PictureFolder, NextPictureName)\n        TempImage = pygame.image.load(TempPicture).convert()\n        TempScreen = pygame.transform.scale(TempImage, (1440, 900))  \n        PB.screen.blit(TempScreen, (0, 0))                    \n\n        # Display the image-name\n        if rannum &gt; 1 :          # Skip if it is the instruction picture\n            font = pygame.font.Font(None, 40)\n            PictureNameText = font.render(str(NextPictureName), True, ( 0xFF, 0xFF, 0xFF ))  # White text\n            PB.screen.blit(PictureNameText, (50, 700))\n\n        # Update Display\n        pygame.display.update()\n\n    # Function to start the preview\n    def startPreview(self):\n        global cam, IsCamActive, CamFrameRate\n        # Clear the screen to start\n        PB.screen.fill((0, 0, 0))\n        pygame.display.update()\n        #cam.awb_mode = 'incandescent'\n        cam.awb_mode = 'off'\n        cam.awb_gains = (1.1,2.2)\n        cam.resolution = PictureSize\n        cam.framerate = CamFrameRate\n        cam.hflip = True\n        cam.start_preview()\n        # time.sleep(0)\n        IsCamActive = True\n\n\n    # Function to overlay the preview with the countdown images\n    def CD(self,message):\n        global LastPictures, cam, IsCamActive\n        \n        # turn on the cam if required, this will give the users time to set up their positions\n        if IsCamActive is False:\n            self.startPreview()\n\n        # need to turn the lights off when using matrix\n        os.system(&quot;sh lightsoff.sh&quot;)            \n        # give a countdown\n        if message is True:\n            os.system(&quot;..\/rpi-rgb-led-matrix\/examples-api-use\/demo --led-cols=64  --led-slowdown-gpio=5 -D 1 ~pi\/smile.ppm -m 6 -t 4&quot;)\n        os.system(&quot;sh lightson.sh&quot;)\n        self.takePicture()\n        os.system(&quot;sh lightsdim.sh&quot;)\n\n    def qroverlay(self):\n        img = Image.open(&quot;QR.png&quot;)\n        # \/\/ is integer division\n        pad = Image.new('RGB', (\n             ((img.size&#x5B;0] + 31) \/\/ 32) * 32,\n             ((img.size&#x5B;1] + 15) \/\/ 16) * 16,\n             ))\n        pad.paste(img, (0, 0))\n        o = cam.add_overlay(pad.tobytes(), size=img.size, alpha=255, layer=4, fullscreen=False, window=(32, 20, img.size&#x5B;0], img.size&#x5B;1]))\n        # time.sleep(1)\n\n\n    def nextbackground(self):\n        global backgrounds, bgidx\n        # go to live\n        self.startPreview()\n        bgidx = bgidx +1\n        self.displaybackground()\n \n    def prevbackground(self):\n        global backgrounds, bgidx\n        # go to live\n        self.startPreview()\n        bgidx = bgidx - 1\n        self.displaybackground()\n\n    def displaybackground(self):\n        global backgrounds, bgidx\n        if bgidx &lt; 0:\n            bgidx = len(backgrounds) -1\n        if bgidx &gt;= len(backgrounds):\n            print (&quot;Clearing background&quot;)\n            bgidx = -1\n            self.o.close()\n            self.printDmd(&quot;No Background&quot;)\n        else:\n            bgstr = backgrounds&#x5B;bgidx]\n            bgstr = bgstr&#x5B;12:-4]\n            print (&quot;using background&quot; + bgstr)\n            img = Image.open(backgrounds&#x5B;bgidx])\n            # \/\/ is integer division\n            pad = Image.new('RGB', (\n                ((img.size&#x5B;0] + 31) \/\/ 32) * 32,\n                ((img.size&#x5B;1] + 15) \/\/ 16) * 16,\n            ))\n            pad.paste(img, (0, 0))\n            if self.o:\n                self.o.close()\n            self.o = cam.add_overlay(pad.tobytes(), size=img.size, alpha=128, layer=3, fullscreen=True)\n            self.printDmd(bgstr)\n\n    # Function to stop preview\n    def StopPreview(self):\n        global cam, IsCamActive\n        if IsCamActive is True:\n            print (&quot;Stop preview&quot;)\n            cam.stop_preview()\n            IsCamActive = False\n\n    def image_manipulation(self, front_file, save_folder):\n        img = cv2.imread(front_file)\n        width = img.shape&#x5B;0]  # To select the right background and overlay\n        height = img.shape&#x5B;1]\n        print (&quot;width and height&quot; + str(width) + &quot; &quot; +str (height))\n        if (bgidx &gt;= 0):\n            back = cv2.imread(backgrounds&#x5B;bgidx])\n            width = back.shape&#x5B;0]  # To select the right background and overlay\n            height = back.shape&#x5B;1]   \n            print (&quot;width and height&quot; + str(width) + &quot; &quot; +str (height))\n            reds = img&#x5B;:, :, 2]\n            greens = img&#x5B;:, :, 1]\n            blues = img&#x5B;:, :, 0]\n            #orig was 20\n#            mask = ((reds &lt; (greens - 20))\n#                    &amp; (blues &lt; (greens - 20))\n#                   &amp; (greens &gt; 35))\n#            mask = ((reds &lt; (greens  ))   # this is all foreground\n#                    &amp; (blues &lt; (greens  ))\n#                    &amp; (greens &gt; 35))\n#            mask = ((reds &lt; (greens  ))   # this is all foreground\n#                    &amp; (blues &lt; (greens  ))\n#                    &amp; (greens &gt; 35))\n#            mask = ((reds &lt; (greens + 10 ))   # this is all foreground\n#                    &amp; (blues &lt; (greens + 10 ))\n#                    &amp; (greens &gt; 35))\n# this is getting there a little too much forground\n            mask = ((reds &lt; (greens + 30 )) &amp; (blues &lt; (greens + 30 )) &amp; (greens &gt; 35))\n            mask = ((reds &lt; (greens )) &amp; (blues &lt; (greens )) &amp; (greens &gt; 35))\n#            mask = ((reds &lt; (greens + 40 ))   # this is getting there a little too much forground\n#                    &amp; (blues &lt; (greens + 40 ))\n#                    &amp; (greens &gt; 35))\n#            mask = (greens &gt; 35)   # this is mostly background\n            try:\n                img&#x5B;mask] = back&#x5B;mask]\n            except IndexError:\n                logging.error(&quot;Background dimensions should be {0}x{1}&quot;\n                            .format(width, height))\n                sys.exit()\n        basename = os.path.basename(front_file)\n        basename = os.path.join(save_folder, basename)\n        flipped = cv2.flip(img,1)\n        cv2.imwrite(basename, flipped)\n        return basename\n\n    # Function to take a picture\n    def takePicture(self):\n        global cam, IsCamActive, Count\n        if IsCamActive is False:\n            self.startPreview()\n        NewPictureName = &quot;img_&quot; + time.strftime(&quot;%Y%m%d%H%M%S&quot;) + &quot;.png&quot;\n        data, fs = sf.read(&quot;shutter.wav&quot;, dtype='float32')\n        #sleep(1)\n        cam.capture(NewPictureName)          # Take the picture!\n        sd.play(data, fs)\n        LastPictures.append(NewPictureName)  # keep it on the list for combining\n        self.image_manipulation(NewPictureName, 'Pictures')\n\n    def printDmd(self, str):\n        os.system(&quot;sh lightsoff.sh&quot;)\n        os.system(&quot;..\/rpi-rgb-led-matrix\/examples-api-use\/scrolling-text-example -f ..\/rpi-rgb-led-matrix\/fonts\/10x20.bdf --led-slowdown-gpio=5 -s 10 &quot; + str + &quot; --led-cols=64 -l 1&quot;)\n\nclass MyServer(BaseHTTPRequestHandler):\n    def do_HEAD(self):\n        self.send_response(200)\n        self.send_header('Content-type', 'text\/html')\n        self.end_headers()\n\n    def do_GET(self):\n        html = '''\n           &lt;html&gt;\n           &lt;body style=&quot;width:960px; margin: 20px auto;&quot;&gt;\n           &lt;h1&gt;Photobooth control&lt;\/h1&gt;\n           &lt;p&gt;Picture: &lt;a href=&quot;\/pic&quot;&gt;take picture&lt;\/a&gt; &lt;br&gt; &lt;a href=&quot;\/next&quot;&gt;next background&lt;\/a&gt;&lt;br&gt;&lt;a href=&quot;\/prev&quot;&gt;previous background&lt;\/a&gt;&lt;\/p&gt;\n           &lt;br&gt;&lt;a href=&quot;\/shutdown&quot;&gt;shutdown the system&lt;\/a&gt;&lt;\/p&gt;\n           &lt;br&gt;&lt;a href=&quot;\/reboot&quot;&gt;reboot the system&lt;\/a&gt;&lt;\/p&gt;\n           &lt;h2&gt; Last button press &lt;\/h2&gt;\n           &lt;div id=&quot;last-but&quot;&gt;&lt;\/div&gt;\n           &lt;script&gt;\n               document.getElementById(&quot;last-but&quot;).innerHTML=&quot;{}&quot;;\n           &lt;\/script&gt;\n           &lt;\/body&gt;\n           &lt;\/html&gt;\n        '''\n        self.do_HEAD()\n        status = ''\n        if self.path=='\/':\n            print (&quot;nothing&quot;)\n        elif self.path=='\/pic':\n            e1 = pygame.event.Event(pygame.USEREVENT, attr1='pic')\n            pygame.event.post(e1)\n            status='take pic'\n        elif self.path=='\/next':\n            e1 = pygame.event.Event(pygame.USEREVENT, attr1='next')\n            pygame.event.post(e1)\n            status='next background'\n        elif self.path=='\/prev':\n            e1 = pygame.event.Event(pygame.USEREVENT, attr1='prev')\n            pygame.event.post(e1)\n            status='prev background'\n        elif self.path=='\/shutdown':\n            e1 = pygame.event.Event(pygame.USEREVENT, attr1='shutdown')\n            pygame.event.post(e1)\n            status='toast'\n        elif self.path=='\/reboot':\n            e1 = pygame.event.Event(pygame.USEREVENT, attr1='reboot')\n            pygame.event.post(e1)\n            status='try again'\n        self.wfile.write(html.format(status).encode(&quot;utf-8&quot;))\n\n\n# Global variables\ncam = picamera.PiCamera()   # There is only one camera\nIsCamActive = False         # Is the camera-preview active?\nLastPictures = &#x5B;]           # List of the last pictures for combining\nSlideshowTimerCounter = 0   # counter to trigger the slideshow\nAllPicturesList = &#x5B;]        # List of all pictures ever taken\nPictureFolder = os.path.join(\n    os.getcwd(), 'Pictures')  # folder for the pictures\nNumberOfPicturesTaken = 1   # how many pictures are in the AllPicturesList?\n\nPictureSize = (1440, 900)  # Size for the camera CGR make this max!\nCamFrameRate = 12           # We don't need 25fps. relax!\n\n#set size of the screen\nScreenSize = 1440, 900\n\nRandomPicCount = 0\n\nbackgrounds = None\nbgidx = -1\n\n\n# external shell-script-call: imagemagick convert ...\ndef combine():\n    global LastPictures\n    subprocess.call(&#x5B;'\/home\/pi\/pb\/combine.sh', LastPictures&#x5B;0],\n                     LastPictures&#x5B;1], LastPictures&#x5B;2], LastPictures&#x5B;3]])\n    LastPictures = &#x5B;]\n\n# Update the list of all pictures taken, may be optimised for not to read all files again, but only add the new ones...\ndef UpdateAllPicturesList():\n    global AllPicturesList, NumberOfPicturesTaken, PictureFolder\n    AllPicturesList = &#x5B;]\n    NumberOfPicturesTaken = 0\n    # this will include the splash screen\n    for dat in os.listdir(PictureFolder):\n        if dat.endswith('.png'):\n            AllPicturesList.append(dat)\n            NumberOfPicturesTaken += 1\n\ndef safeshutdown(arg):\n        # shutdown our Raspberry Pi\n        os.system(&quot;sudo shutdown -h now&quot;)\n\ndef thread_function(name):\n    print(&quot;Thread %s: starting&quot;% name)\n    http_server = HTTPServer((host_name, host_port), MyServer)\n    print(&quot;Server Starts - %s:%s&quot; % (host_name, host_port))\n    http_server.serve_forever()\n    print(&quot;Thread %s: finishing&quot;% name)\n\n\n# Main\nprint(&quot;Start&quot;)\nPB = Photobooth()\nprint(&quot;Photobooth created&quot;)\nos.system(&quot;sh lightsoff.sh&quot;)\ndata, fs = sf.read(&quot;ms.wav&quot;)\nZisPic = os.path.join(os.getcwd(), 'zis.png')\nTempImage = pygame.image.load(ZisPic).convert()\nTempScreen = pygame.transform.scale(TempImage, (1440, 900))\nPB.screen.blit(TempScreen, (0, 0))\npygame.display.update()\nsd.play(data, fs)\nos.system(&quot;..\/rpi-rgb-led-matrix\/examples-api-use\/demo --led-cols=64  --led-slowdown-gpio=5 -D 1 ~pi\/pb\/zis.ppm -m 6000 -t 1&quot;)\nos.system(&quot;..\/rpi-rgb-led-matrix\/examples-api-use\/scrolling-text-example -f ..\/rpi-rgb-led-matrix\/fonts\/10x20.bdf --led-slowdown-gpio=5 -s 5 `date` --led-cols=64 -l 2&quot;);\nstatus = sd.wait()\n    \n\nPB.startPreview()\nprint(&quot;preview running&quot;)\n\n#start button webserver thread\nx = threading.Thread(target=thread_function, args=(1,))\nprint(&quot;thread created&quot;)\nx.daemon = True\nx.start()\nprint(&quot;thread running&quot;)\ntime.sleep(0.1)\no = None\n\n\n# Implement Button-Callback to shut down the raspberry.\n#GPIO.add_event_detect(10, GPIO.RISING, callback=safeshutdown, bouncetime=300)\n\n# After start, show the instructions\nAllPicturesList.append('Splash.png')\nSlideshowTimerCounter = 140  #immediately start with info screen after booting\nwhile True:\n    # get key and webserver events \n    event = pygame.event.poll()     \n    \n    # any key event just shuts down, typically no keyboard\n    if event.type == pygame.KEYDOWN:\n        pygame.quit()\n        sys.exit()\n    \n    # just print out a user event\n    if event.type == USEREVENT:\n        #e1 = pygame.event.Event(pygame.USEREVENT, attr1='attr1')\n        print (f'Event found {event}')\n\n    # the next event is for next background\n    if ( event.type == USEREVENT and event.attr1=='next'):        \n        PB.nextbackground()\n        # Reset the Slideshow-Counter\n        SlideshowTimerCounter = 0\n        for event in pygame.event.get():\n            # empty the queue\n            pass\n        \n    # the prev event is for the previous background, but just exit the program\n    if ( event.type == USEREVENT and event.attr1=='prev'):\n        PB.prevbackground()\n        # Reset the Slideshow-Counter\n        SlideshowTimerCounter = 0\n        for event in pygame.event.get():\n            # empty the queue\n            pass\n\n        # CGR REMOVE THIS\n#        pygame.quit()\n#        sys.exit()\n        \n    # a real shutdown event from the webpage\n    if ( event.type == USEREVENT and event.attr1=='shutdown'):\n        pygame.quit()   \n        os.system(&quot;shutdown -h now&quot;)\n        sys.exit()\n        \n    # a real reboot event from the webpage\n    if ( event.type == USEREVENT and event.attr1=='reboot'):      \n        pygame.quit()  \n        os.system(&quot;shutdown -r now&quot;)\n        sys.exit()\n        \n    # If there was a BIG-ASS Button-Event (TM): Go and take some pictures\n    if event.type == pygame.MOUSEBUTTONDOWN or ( event.type == USEREVENT and event.attr1=='pic'):\n        PB.CD(True)  # First picture\n        PB.CD(False)  # second picture\n        PB.CD(False)  # third picture\n        PB.CD(False)  # fourth picture\n        os.system(&quot;sh lightsoff.sh&quot;)            \n        # put up wait message\n        os.system(&quot;..\/rpi-rgb-led-matrix\/examples-api-use\/demo --led-cols=64  --led-slowdown-gpio=5 -D 1 ~pi\/Wait.ppm -m 6 -t 1&quot;)\n        combine()   # and combine them into one...\n        for event in pygame.event.get():\n            # empty the queue\n            pass\n\n        # Update all pictures list\n        UpdateAllPicturesList()\n\n        os.system(&quot;..\/rpi-rgb-led-matrix\/examples-api-use\/demo --led-cols=64  --led-slowdown-gpio=5 -D 1 ~pi\/Ready.ppm -m 6 -t 1&quot;)\n\n        os.system(&quot;sh lightsdim.sh&quot;)            \n        # Overlay the last QR-Code to preview\n        PB.qroverlay()\n\n        # Reset the Slideshow-Counter\n        SlideshowTimerCounter = 0\n    else:\n        SlideshowTimerCounter += 1    # increment Timer-Counter\n        time.sleep(0.1)\n        if SlideshowTimerCounter &gt; 120:\n            # Start Slideshow after 12s\n            if SlideshowTimerCounter % 40 == 0:\n                # Change picture every 4s\n                PB.slideshow()\n                \n\n\n<\/pre><\/div>\n\n\n<p>The web code is lifted right off of the instructables page:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: php; title: ; notranslate\" title=\"\">\n&lt;!DOCTYPE HTML &gt;\n\n&lt;html&gt;\n&lt;head&gt;\n&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text\/html; charset=utf-8&quot;&gt;\n\n&lt;title&gt; Photobooth Homepage &lt;\/title&gt;\n\n&lt;style type=&quot;text\/css&quot;&gt;\n\nbody { background-color:#000000; color: white; }\n\n.wrapButtons {\n    text-align:center;\n    font-size:60px;\n    font-family:&quot;Verdana&quot;;\n    }\n\n&lt;\/style&gt;\n\n&lt;\/head&gt;\n\n&lt;body&gt;\n&lt;h1&gt; All pictures &lt;\/h1&gt;\n&lt;b&gt;\n&lt;?php\n$timestamp = time();\n$date = date(&quot;d.m.Y&quot;,$timestamp);\n$hour = date(&quot;H:i:s&quot;,$timestamp);\necho $date,&quot; - &quot;,$hour,&quot; &quot;;\n?&gt;\n&lt;\/b&gt;\n\n&lt;br&gt;\n&lt;p&gt;\n\n&lt;?php\n$picroot = &quot;.\/pics&quot;;\n \nif (is_dir($picroot))\n{\n    if ( $handle = opendir($picroot) )\n    {\n        while (($file = readdir($handle)) !== false)\n        {\n            if ( filetype( $picroot.'\/'.$file) == &quot;file&quot;\n                 AND substr( $picroot.'\/'.$file, -4) == &quot;.jpg&quot;)\n            {\n                $fullpath&#x5B;] = $picroot.'\/'.$file;\n            }\n        }\n        closedir($handle);\n    }\n}\n \nrsort($fullpath);\n\nforeach ( $fullpath AS $dateiname )\n{\n    echo &quot;&lt;img src=\\&quot;$dateiname\\&quot;&quot;;\n    echo &quot; alt=\\&quot;&quot;;\n    echo '&quot; \\&gt; ';\n}\n\n?&gt;\n\n&lt;!-- this function is created by chuck from http:\/\/webcheatsheet.com\/php\/create_thumbnail_images.php --&gt;\n\n&lt;?php\nfunction createThumbs( $pathToImages, $pathToThumbs, $thumbWidth )\n{\n  \/\/ open the directory\n  $dir = opendir( $pathToImages );\n\n  \/\/ loop through it, looking for any\/all JPG files:\n  while (false !== ($fname = readdir( $dir ))) {\n    \/\/ parse path for the extension\n    $info = pathinfo($pathToImages . $fname);\n    \/\/ continue only if this is a JPEG image\n    if ( strtolower($info&#x5B;'extension']) == 'jpg' )\n    {\n      echo &quot;Creating thumbnail for {$fname} &lt;br \/&gt;&quot;;\n\n      \/\/ load image and get image size\n      $img = imagecreatefromjpeg( &quot;{$pathToImages}{$fname}&quot; );\n      $width = imagesx( $img );\n      $height = imagesy( $img );\n\n      \/\/ calculate thumbnail size\n      $new_width = $thumbWidth;\n      $new_height = floor( $height * ( $thumbWidth \/ $width ) );\n\n      \/\/ create a new temporary image\n      $tmp_img = imagecreatetruecolor( $new_width, $new_height );\n\n      \/\/ copy and resize old image into new image\n      imagecopyresized( $tmp_img, $img, 0, 0, 0, 0, $new_width, $new_height, $width, $height );\n\n      \/\/ save thumbnail into a file\n      imagejpeg( $tmp_img, &quot;{$pathToThumbs}{$fname}&quot; );\n    }\n  }\n  \/\/ close the directory\n  closedir( $dir );\n}\n\/\/ call createThumb function and pass to it as parameters the path\n\/\/ to the directory that contains images, the path to the directory\n\/\/ in which thumbnails will be placed and the thumbnail's width.\n\/\/ We are assuming that the path will be a relative path working\n\/\/ both in the filesystem, and through the web for links\ncreateThumbs(&quot;pics\/&quot;,&quot;pics\/thumbs\/&quot;,100);\n?&gt;\n\n&lt;?php\nfunction createGallery( $pathToImages, $pathToThumbs )\n{\n  echo &quot;Creating gallery.html &lt;br \/&gt;&quot;;\n\n  $output = &quot;&lt;html&gt;&quot;;\n  $output .= &quot;&lt;head&gt;&lt;title&gt;Thumbnails&lt;\/title&gt;&lt;\/head&gt;&quot;;\n\n  $output .= &quot;&lt;style type=\\&quot;text\/css\\&quot;&gt;&quot;;\n  $output .= &quot;body { background-color:#000000; color: white; }&quot;;\n  $output .= &quot;.wrapButtons {&quot;;\n  $output .= &quot;    text-align:center;&quot;;\n  $output .= &quot;    font-size:60px;&quot;;\n  $output .= &quot;    font-family:\\&quot;Verdana\\&quot;;&quot;;\n  $output .= &quot;    }&quot;;\n  $output .= &quot;&lt;\/style&gt;&quot;;\n\n  $output .= &quot;&lt;body&gt;&quot;;\n  $output .= &quot;&lt;table cellspacing=\\&quot;0\\&quot; cellpadding=\\&quot;2\\&quot; width=\\&quot;500\\&quot;&gt;&quot;;\n  $output .= &quot;&lt;tr&gt;&quot;;\n\n  \/\/ open the directory\n  $dir = opendir( $pathToThumbs );\n\n  $counter = 0;\n  \/\/ loop through the directory\n  while (false !== ($fname = readdir($dir)))\n  {\n    \/\/ strip the . and .. entries out\n    if ($fname != '.' &amp;&amp; $fname != '..')\n    {\n      $output .= &quot;&lt;td valign=\\&quot;middle\\&quot; align=\\&quot;center\\&quot;&gt;&lt;a href=\\&quot;{$pathToImages}{$fname}\\&quot;&gt;&quot;;\n      $output .= &quot;&lt;img src=\\&quot;{$pathToThumbs}{$fname}\\&quot; border=\\&quot;0\\&quot; \/&gt;&quot;;\n      $output .= &quot;&lt;\/a&gt;&lt;\/td&gt;&quot;;\n\n      $counter += 1;\n      if ( $counter % 4 == 0 ) { $output .= &quot;&lt;\/tr&gt;&lt;tr&gt;&quot;; }\n    }\n  }\n  \/\/ close the directory\n  closedir( $dir );\n\n  $output .= &quot;&lt;\/tr&gt;&quot;;\n  $output .= &quot;&lt;\/table&gt;&quot;;\n  $output .= &quot;&lt;\/body&gt;&quot;;\n  $output .= &quot;&lt;\/html&gt;&quot;;\n\n  \/\/ open the file\n  $fhandle = fopen( &quot;gallery.html&quot;, &quot;w&quot; );\n  \/\/ write the contents of the $output variable to the file\n  fwrite( $fhandle, $output );\n  \/\/ close the file\n  fclose( $fhandle );\n}\n\/\/ call createGallery function and pass to it as parameters the path\n\/\/ to the directory that contains images and the path to the directory\n\/\/ in which thumbnails will be placed. We are assuming that\n\/\/ the path will be a relative path working\n\/\/ both in the filesystem, and through the web for links\ncreateGallery(&quot;pics\/&quot;,&quot;pics\/thumbs\/&quot;);\n?&gt;\n&lt;p&gt;\n\n&lt;\/body&gt;\n&lt;\/html&gt;\n\n<\/pre><\/div>\n\n\n<p>The most fun part of the code was building the remote control button, the ESP8266 code is here:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/**********************************************\n * Edit Settings.h for motor config etc (motors? there are no motors\n * in this project, or settings.h :)\n ***********************************************\/\n#include &quot;ChangeMac.hpp&quot;\n#include &lt;ESP8266HTTPClient.h&gt;\n#include &lt;ESP8266HTTPUpdateServer.h&gt;\n#include &lt;ESP8266WebServer.h&gt;\n#include &lt;ESP8266WiFi.h&gt;\n#include &lt;ESP8266mDNS.h&gt;\n#include &lt;WiFiManager.h&gt;\n\nconst char* ssid     = &quot;bill&quot;;\nconst char* password = &quot;gertrude&quot;; \/* password must be 8 char or more for WPA *\/\n\n#define VERSION &quot;0.999&quot;\n\n#define HOSTNAME &quot;PhotoButton-&quot;\n#define CONFIG &quot;\/conf.txt&quot;\n\n\/* Useful Constants *\/\n#define SECS_PER_MIN (60UL)\n#define SECS_PER_HOUR (3600UL)\n#define SECS_PER_DAY (SECS_PER_HOUR * 24L)\n\n\/* Useful Macros for getting elapsed time *\/\n#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN)\n#define numberOfMinutes(_time_) ((_time_ \/ SECS_PER_MIN) % SECS_PER_MIN)\n#define numberOfHours(_time_) ((_time_ % SECS_PER_DAY) \/ SECS_PER_HOUR)\n#define elapsedDays(_time_) (_time_ \/ SECS_PER_DAY)\n\n\/\/ Time\nlong   lastEpoch        = 0;\nlong   firstEpoch       = 0;\nlong   displayOffEpoch  = 0;\nString lastMinute       = &quot;xx&quot;;\nString lastSecond       = &quot;xx&quot;;\nString lastReportStatus = &quot;&quot;;\n\n\/\/ declairing prototypes\nvoid configModeCallback(WiFiManager* myWiFiManager);\n\nint8_t getWifiQuality()\n{\n    int32_t dbm = WiFi.RSSI();\n    if (dbm &lt;= -100) {\n        return 0;\n    } else if (dbm &gt;= -50) {\n        return 100;\n    } else {\n        return 2 * (dbm + 100);\n    }\n}\n\/\/ GPIOS for button\/lights\nint BUTTON_LIGHT = 2;\nint PIC_BUTT     = 0;\nint NEXT_BUTT    = 12;\nint PREV_BUTT    = 14;\n\n\/\/ initial stack\nchar* stack_start;\n\nvoid\nprintStackSize()\n{\n    char stack;\n    Serial.print(F(&quot;stack size &quot;));\n    Serial.println(stack_start - &amp;stack);\n}\n\nbool\ncaptiveLogin()\n{\n    static const char* LOCATION       = &quot;Location&quot;;\n    static const char* AUTH_TARGET    = &quot;authtarget: &quot;;\n    static const char* HEADER_NAMES&#x5B;] = {LOCATION};\n\n    String uri;\n    {\n        HTTPClient http;\n        http.begin(&quot;http:\/\/192.168.220.1\/index.html&quot;);\n        http.collectHeaders(HEADER_NAMES, 2);\n        int httpCode = http.GET();\n        if (httpCode == 200) { return true; }\n        if (httpCode != 307 || !http.hasHeader(LOCATION)) { return false; }\n        uri = http.header(LOCATION);\n        Serial.print(&quot;portal=&quot;);\n        Serial.println(uri);\n        delay(2000);\n    }\n\n    String auth_target_str;\n    {\n        HTTPClient http;\n        http.begin(uri);\n        http.collectHeaders(HEADER_NAMES, 2);\n        int httpCode = http.GET();\n        if (httpCode != 200) { return false; }\n        Serial.println(&quot;Got portal page.  Now looking for the auth token&quot;);\n        String payload = http.getString();\n        \/\/ Serial.println(payload);\n        size_t tok_loc = payload.indexOf(&quot;authtarget: &quot;);\n        auth_target_str =\n            payload.substring(tok_loc, tok_loc + 79); \/* beware magic number *\/\n        auth_target_str.concat(&quot;http:\/\/192.168.220.1\/index.html&quot;);\n        auth_target_str.remove(0, 12);\n        Serial.print(&quot;cookie=&quot;);\n        Serial.println(auth_target_str);\n        delay(1000);\n\n        Serial.print(&quot;Sending authentication&quot;);\n        http.begin(auth_target_str);\n        httpCode = http.GET();\n        Serial.print(&quot;got HTTP return code &quot;);\n        Serial.println(httpCode);\n        if (httpCode != 200) { return false; }\n        payload = http.getString();\n        Serial.println(payload);\n        delay(1000);\n\n        Serial.println(&quot;Get first simple page&quot;);\n        http.begin(&quot;http:\/\/192.168.220.1\/index.html&quot;);\n        httpCode = http.GET();\n        Serial.print(&quot;got HTTP return code &quot;);\n        Serial.println(httpCode);\n        if (httpCode != 200) { return false; }\n        payload = http.getString();\n        Serial.println(payload);\n        delay(1000);\n    }\n    {\n        Serial.println(&quot;Get first simple page, again&quot;);\n        HTTPClient http;\n        http.begin(&quot;http:\/\/192.168.220.1\/index.html&quot;);\n        int httpCode = http.GET();\n        Serial.print(&quot;got HTTP return code &quot;);\n        Serial.println(httpCode);\n        if (httpCode != 200) { return false; }\n        String payload = http.getString();\n        Serial.println(payload);\n        delay(1000);\n    }\n}\n\nvoid\nflash_led(int number, int dly)\n{\n    while (number--) {\n        digitalWrite(BUTTON_LIGHT, LOW);\n        delay(dly);\n        digitalWrite(BUTTON_LIGHT, HIGH);\n        delay(dly);\n    }\n}\n\n\nint\nany_butt()\n{\n    return (!digitalRead(PIC_BUTT) || !digitalRead(PREV_BUTT) ||\n            !digitalRead(NEXT_BUTT));\n}\n\nvoid\nbreathe_led()\n{\n    for (int i = 0; i &lt; 1000; i++) {\n        analogWrite(BUTTON_LIGHT, i);\n        if (any_butt()) { return; }\n        delay(1);\n    }\n    for (int i = 0; i &lt; 1000; i++) {\n        analogWrite(BUTTON_LIGHT, 1000 - i);\n        if (any_butt()) { return; }\n        delay(1);\n    }\n}\n\nvoid\nsetup()\n{\n    \/* this is start of the photobutton connection to the captive portal *\/\n    Serial.begin(115200);\n    Serial.println();\n    Serial.println();\n\n    Serial.println(&quot;Flash light for a bit&quot;);\n    pinMode(BUTTON_LIGHT, OUTPUT);\n    flash_led(10, 50);\n    breathe_led();\n\n    WiFi.mode(WIFI_STA);\n    WiFi.persistent(false);\n\n    uint8_t mac&#x5B;6];\n    makeRandomMac(mac);\n    changeMac(mac);\n    Serial.print(&quot;MAC address is &quot;);\n    Serial.println(WiFi.macAddress());\n\n    String hostname = &quot;PBButton-&quot;;\n    hostname += random(10);\n    hostname += random(10);\n    hostname += random(10);\n    hostname += random(10);\n    WiFi.hostname(hostname);\n    Serial.print(&quot;Hostname is &quot;);\n    Serial.println(hostname);\n\n    Serial.print(&quot;Connecting to &quot;);\n    Serial.println(ssid);\n    WiFi.begin(ssid, password);\n    while (WiFi.status() != WL_CONNECTED) {\n        delay(500);\n        Serial.print(WiFi.status());\n    }\n\n    Serial.println(&quot;&quot;);\n    Serial.println(&quot;WiFi connected&quot;);\n    Serial.println(&quot;IP address: &quot;);\n    Serial.println(WiFi.localIP());\n\n    if (!captiveLogin()) { ESP.restart(); }\n\n    flash_led(30, 20);\n\n    \/\/ init record of stack\n    char stack;\n    stack_start = &amp;stack;\n\n    \/\/ print the received signal strength:\n    Serial.print(&quot;Signal Strength (RSSI): &quot;);\n    Serial.print(getWifiQuality());\n    Serial.println(&quot;%&quot;);\n\n    flash_led(5, 100);\n\n    Serial.println(&quot;*** Leaving setup()&quot;);\n\n    pinMode(PIC_BUTT, INPUT_PULLUP);\n    pinMode(PREV_BUTT, INPUT_PULLUP);\n    pinMode(NEXT_BUTT, INPUT_PULLUP);\n}\n\n\/\/************************************************************\n\/\/ Main Looop\n\/\/************************************************************\nvoid\nloop()\n{\n\n    breathe_led();\n    printf(&quot;breathe\\n&quot;);\n    if (!digitalRead(PIC_BUTT)) {\n        HTTPClient http;\n        http.begin(&quot;http:\/\/192.168.220.1:8000\/pic&quot;);\n        int httpCode = http.GET();\n        Serial.print(&quot;got HTTP return code &quot;);\n        Serial.println(httpCode);\n        delay(20000);\n    }\n    if (!digitalRead(PREV_BUTT)) {\n        HTTPClient http;\n        http.begin(&quot;http:\/\/192.168.220.1:8000\/prev&quot;);\n        int httpCode = http.GET();\n        Serial.print(&quot;got HTTP return code &quot;);\n        Serial.println(httpCode);\n        delay(500);\n    }\n    if (!digitalRead(NEXT_BUTT)) {\n        HTTPClient http;\n        http.begin(&quot;http:\/\/192.168.220.1:8000\/next&quot;);\n        int httpCode = http.GET();\n        Serial.print(&quot;got HTTP return code &quot;);\n        Serial.println(httpCode);\n        delay(500);\n    }\n}\n\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>My daughter is getting married, and I though it would be fun to build a photobooth. What I wanted what something that took pictures, and saved them locally, so they could be downloaded by participants with phones. No cloud. No &hellip; <a href=\"http:\/\/zethus.ca\/wp\/?p=863\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-863","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_featured_media_url":"","_links":{"self":[{"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=\/wp\/v2\/posts\/863","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=863"}],"version-history":[{"count":13,"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=\/wp\/v2\/posts\/863\/revisions"}],"predecessor-version":[{"id":884,"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=\/wp\/v2\/posts\/863\/revisions\/884"}],"wp:attachment":[{"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=863"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=863"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/zethus.ca\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=863"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}