Difference between revisions of "Raspberry Pi Kiosk"

From MagnetoWiki
Jump to navigation Jump to search
(notes on google setup)
(added webpage code)
Line 36: Line 36:
 
   sed -i 's/"exit_type":"Crashed"/"exit_type":"Normal"/' /home/pi/.config/chromium/Default/Preferences
 
   sed -i 's/"exit_type":"Crashed"/"exit_type":"Normal"/' /home/pi/.config/chromium/Default/Preferences
 
    
 
    
   /usr/bin/chromium-browser --noerrdialogs --disable-infobars --kiosk https://www.bio.fsu.edu/grad/kiosk/unit1.html
+
   /usr/bin/chromium-browser --noerrdialogs --disable-infobars --kiosk https://www.bio.fsu.edu/grad/kiosk/kiosk.html?kiosk=Unit%20I
  
Create a system daemon service file <code>kiosk.service</code>, and copy it to <code>/lib/systemd/system/kiosk.service</code> (don't forget to use <code>sudo</code> to do the copying).
+
This script:
 +
 +
* disables the screen saver (the xset commands)
 +
 
 +
* hides the mouse (unclutter)
 +
 
 +
* deletes any chromium crash notice settings (the sed commands)
 +
 
 +
* Points the pi to the kiosk.html webpage, with the physical location of the kiosk as a query parameter, eg <code>https://www.bio.fsu.edu/grad/kiosk/kiosk.html?kiosk=King%20Atrium</code>
 +
 
 +
 
 +
 
 +
5. Create a system daemon service file <code>kiosk.service</code>, and copy it to <code>/lib/systemd/system/kiosk.service</code> (don't forget to use <code>sudo</code> to do the copying).
  
  
Line 70: Line 82:
  
  
5. Reboot the Pi.  Note: use <code>sudo reboot</code> to restart the pi from the command line.
+
6. Reboot the Pi.  Note: use <code>sudo reboot</code> to restart the pi from the command line.
  
  
Line 123: Line 135:
 
* Columns C-?: "<kiosk name>" -- The query key for each of your kiosks, eg. "Lobby", "Dept Office", etc. The cells in these columns indicate the number of seconds used to display each slide on that particular kiosk. Enter zero seconds to hide the slide from that particular kiosk.
 
* Columns C-?: "<kiosk name>" -- The query key for each of your kiosks, eg. "Lobby", "Dept Office", etc. The cells in these columns indicate the number of seconds used to display each slide on that particular kiosk. Enter zero seconds to hide the slide from that particular kiosk.
  
 +
Get the "Publish to Web" key for this Google Sheet via "File" -> "Publish to the Web" -> "Publish" button. This key (which is NOT the same as the shared link for editing) will be used by the website php script to get access to the sheet contents.
  
 +
==Kiosk Webpage==
 +
 +
1. On the public website, install the following <code>kiosk_slides.php</code> file
 +
 +
<?php
 +
header("Content-type: text/csv");
 +
 +
readfile('<nowiki>https://docs.google.com/spreadsheets/d/e/</nowiki><google_sheet_key>/pub?gid=0&single=true&output=csv');
 +
 +
where <code><google_sheet_key></code> is the "Publish to Web" key for the google sheet listing the slides and display times (keep this a secret if there are shared links to the presentations in this sheet).
  
==Kiosk Webpage==
 
  
  
Point the pi to the kiosk.html webpage, with its location as a query parameter, eg <code>https://www.bio.fsu.edu/grad/kiosk/kiosk.html?kiosk=King%20Atrium</code>
+
2. In the same directory as <code>kiosk_slides.php</code>, create the <code>kiosk.html</code> page. Each Pi kiosk will look at this page, which cycles through the presentations as specified in the google sheet. Note that there is nothing private (ie no secrets) in the webpage itself. Even accessing the <code>kiosk_slides.php</code> file will only display the google sheet contents with the public presenation keys (but not any shared editable links).
 +
 +
<nowiki>
 +
<!DOCTYPE html>
 +
<html lang="en">
 +
      <head>
 +
 +
          <meta http-equiv="refresh" content="3600" /><!-- force refresh every 60 minutes, in case of changes to this page on the server-->
 +
          <meta charset="utf-8">
 +
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
 +
          <meta name="description" content="">
 +
          <meta name="author" content="">
 +
          <title>Pi Kiosk</title>
 +
   
 +
          <script src="https://code.jquery.com/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
 +
 +
          <!-- integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="  -->
 +
 +
      </head>
 +
 +
      <body>
 +
 +
          <style>
 +
 +
                html,body {
 +
                    padding: 0;
 +
                    margin: 0;
 +
                }
 +
                iframe {
 +
                    display: block;
 +
                    width: 1980px;
 +
                    height: 1114px;
 +
                    position:relative;
 +
                    left:-28px;
 +
                    top:-4px;
 +
                }
 +
 +
                .slide {
 +
                    width:1920px;
 +
                    height:1080px;
 +
                    overflow:hidden;
 +
                    border:solid 1px gray;
 +
                }
 +
 +
          </style>
 +
 +
          <div id="slide0" class="slide">
 +
                <div style="text-align:center;margin-top:300px;"><h1>Announcements</h1></div>
 +
          </div>
 +
          <div id="slide1" class="slide"></div>
 +
          <div id="slide2" class="slide"></div>
 +
 +
          <div id="slide3" class="slide"></div>
 +
          <div id="slide4" class="slide"></div>
 +
          <div id="slide5" class="slide"></div>
 +
 +
          <div id="slide6" class="slide"></div>
 +
          <div id="slide7" class="slide"></div>
 +
          <div id="slide8" class="slide"></div>
 +
 +
          <div id="slide9" class="slide"></div>
 +
          <div id="slide10" class="slide"></div>
 +
          <div id="slide11" class="slide"></div>
 +
 +
 +
          <div id="slide12" class="slide"></div>
 +
          <div id="slide13" class="slide"></div>
 +
          <div id="slide14" class="slide"></div>
 +
 +
          <script>
 +
 +
                /*
 +
                    look up list of google presentations from a google sheet (retrieved by kiosk_slides.php)
 +
                    filter the list by our kioskName, taken from ?kiosk=<kioskName> parameter
 +
                    construct iframe based on "publish to web" url of the google presentation
 +
                    assign each slide iframe to one of the predefined slide divs (#slide0-#slide<max_slides>)
 +
                    based on display time in the google sheet, show then hide each slide div in sequence
 +
     
 +
                */
 +
 +
 +
                function getParameterByName(name, url) {
 +
                    if (!url) {
 +
                        url = window.location.href;
 +
                    }
 +
                    name = name.replace(/[\[\]]/g, "\\$&");
 +
                    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
 +
                        results = regex.exec(url);
 +
                    if (!results) return null;
 +
                    if (!results[2]) return '';
 +
                    return decodeURIComponent(results[2].replace(/\+/g, " "));
 +
                }
 +
 +
                function ParseGradPhileFormQuery(query) {
 +
 +
                    // note that fsuid, eval_year, eval_term, default_eval_year, default_eval_term, qr_abbr and auth are globals
 +
 +
                    var kiosk_param = getParameterByName("kiosk", query);
 +
 +
                    if (kiosk_param) {
 +
                        kioskName = kiosk_param;
 +
                    }
 +
 +
 +
                }
 +
 +
 +
                var kioskName = "_none";
 +
                const max_slides = 15;
 +
                var slides = [];
 +
 +
                // get kioskName from query
 +
                ParseGradPhileFormQuery(window.location.search);
 +
 +
                jQuery.get("kiosk_slides.php", function(slides_csv) {
 +
 +
                    var rows = slides_csv.split("\n");
 +
 +
                    var my_column_index = rows[0].split(",").indexOf(kioskName)
 +
 +
                    if (-1 == my_column_index) {
 +
                        // unknown kiosk
 +
                        return;
 +
                    }
 +
                    // remove header row
 +
                    rows = rows.slice(1);
 +
 +
                    rows.forEach(function(r) {
 +
                        var cells = r.split(",");
 +
                        var display_time = parseInt(cells[my_column_index], 10);
 +
 +
                        if (0 != display_time) {
 +
                            slides.push({
 +
                                // url to display first slide of a google presentation
 +
                                'url': "https://docs.google.com/presentation/d/e/" + cells[1] + "/embed?start=true&loop=false&delayms=60000",
 +
                                'display_time': display_time * 1000
 +
 +
                            })
 +
                        }
 +
 +
                    }); // next rows
 +
 +
                    if (slides.length == 0) {
 +
                        // no slides allocated to this kiosk
 +
                        return;
 +
                    }
 +
 +
                    // we only have max_slides number of divs allocated for slides,
 +
                    // so discard the excess slides
 +
                    while (max_slides < slides.length) {
 +
                        slides.pop();
 +
                    }
 +
 +
                    var current_slide = 0;
 +
 +
                    // initialize  content of slides
 +
                    // TODO: put in status check before switching to this url
 +
                    for (var i = 0; i < slides.length; i++) {
 +
                        // iframe to display slide from google presentation
 +
                        // width="2016" height="1134"
 +
                        $("#slide" + i).html('<iframe src="' + slides[i].url + '" frameborder="0"  allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>');
 +
                    }
 +
 +
                    // cycle through the slides, delaying for the associated display_time before going to next slide
 +
                    function updateSlide() {
 +
                        current_slide = (current_slide + 1) % slides.length;
 +
 +
                        console.log("curent_slide: " + current_slide + " display_time: " + slides[current_slide].display_time);
 +
 +
                        $(".slide").hide();
 +
                        $("#slide" + current_slide).show();
 +
                        setTimeout(updateSlide, slides[current_slide].display_time);
 +
                    }
 +
 +
                    // start the display cycle
 +
                    updateSlide();
 +
 +
 +
                }); // get php callback
 +
 +
          </script>
 +
 +
      </body>
 +
</html>
 +
</nowiki>

Revision as of 12:09, 18 February 2020

Raspberry Pi Setup

Modified procedure at Raspberry Pi Kiosk using Chromium.

1. Follow default setup of the RPi, including updates.


2. Install unclutter to hide the mouse when idle

 sudo apt-get install unclutter

You might need to install sed as well, but that was pre-installed on my RPi.


3. Enable auto-login at

 sudo raspi-config

Arrow down to "3 Boot Options" then "B1 Desktop / CLI" then "B4 Desktop Autologin". (I set "B2 Wait for Network at Boot" as well).


4. Create a bash script at /home/pi/kiosk.sh. (Note that "chromium-browser" may have changed name to just "chromium")

 #!/bin/bash
 xset s noblank
 xset s off
 xset -dpms
 
 unclutter -idle 0.5 -root &
 
 sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' /home/pi/.config/chromium/Default/Preferences
 sed -i 's/"exit_type":"Crashed"/"exit_type":"Normal"/' /home/pi/.config/chromium/Default/Preferences
 
 /usr/bin/chromium-browser --noerrdialogs --disable-infobars --kiosk https://www.bio.fsu.edu/grad/kiosk/kiosk.html?kiosk=Unit%20I

This script:

  • disables the screen saver (the xset commands)
  • hides the mouse (unclutter)
  • deletes any chromium crash notice settings (the sed commands)


5. Create a system daemon service file kiosk.service, and copy it to /lib/systemd/system/kiosk.service (don't forget to use sudo to do the copying).


   [Unit]
   Description=Chromium Kiosk
   Wants=graphical.target
   After=graphical.target
   
   [Service]
   Environment=DISPLAY=:0
   Environment=XAUTHORITY=/home/pi/.Xauthority
   Type=simple
   ExecStart=/bin/bash /home/pi/kiosk.sh
   Restart=on-abort
   User=pi
   Group=pi
   
   [Install]
   WantedBy=graphical.target

To copy the service file:

sudo cp kiosk.service /lib/systemd/system/kiosk.service


Enable and start the service:

sudo systemctl enable kiosk.service
sudo systemctl start kiosk.service


6. Reboot the Pi. Note: use sudo reboot to restart the pi from the command line.


To exit kiosk mode, use alt-space bar or alt-F4.


Chromium update pop-up fix

In February 2020 a bug in Chromium caused an update popup to appear. The following commands will delay displaying the update popup for 1 year (from Raspberrypi.org forum posting by ShiftPlusOne.

sudo touch /etc/chromium-browser/customizations/01-disable-update-check;echo CHROMIUM_FLAGS=\"\$\{CHROMIUM_FLAGS\} --check-for-update-interval=31536000\" | sudo tee /etc/chromium-browser/customizations/01-disable-update-check


Network Connectivity

Get the RPi ethernet MAC address with ifconfig eth0. If the RPi is on a DHCP setup, then you should see the assigned ip with ifconfig -a.

Remote Screensharing

For remote screensharing, follow procedure at Raspberry Pi Screen Sharing with TightVnc or Setting up VNC on Raspberry Pi for Mac access and Mac Screen and File Sharing.


Google Docs

1. Create a number of Google Presentations, with 1 slide per presentation. Only 1 slide per presentation, as the kiosk cycles through multiple presentations, and NOT the slides within a presentation.


2. Set share permissions on the presentation so that anyone with the link can edit the presentation (or however you want to control editing). Keep this link secret if anyone can edit it.


3. Via "File" -> "Publish to the Web" -> "Publish" button, get the public key for displaying the presentation, which is in the middle of the presentation link, eg:

https://docs.google.com/presentation/d/e/2PACX-1vTKawnqr671l-BcVQP0_dwK2pQDFdtM5RQZwxsF6hLOGZBk4kRIjcTwF6mCP_sknF12ZzEuBlm6aX1i/pub?start=false&loop=false&delayms=3000


4. Set up a Google Sheet, with each row listing one of the Google Presentations, with columns for each of your kiosks.

  • Column A: "Display" -- the name of each presentation (eg "Upcoming Event", "Public Service Announcement", "This Week's Seminar", etc.) You can link the name of the presenatation with it's shared editable link for convenience if desired, but not required.
  • Column B: "URL" -- the public key of each presentation used in the "Publish to the Web" link (just the key, the kiosk page will fill in the rest of the url).
  • Columns C-?: "<kiosk name>" -- The query key for each of your kiosks, eg. "Lobby", "Dept Office", etc. The cells in these columns indicate the number of seconds used to display each slide on that particular kiosk. Enter zero seconds to hide the slide from that particular kiosk.

Get the "Publish to Web" key for this Google Sheet via "File" -> "Publish to the Web" -> "Publish" button. This key (which is NOT the same as the shared link for editing) will be used by the website php script to get access to the sheet contents.

Kiosk Webpage

1. On the public website, install the following kiosk_slides.php file

<?php
header("Content-type: text/csv");

readfile('https://docs.google.com/spreadsheets/d/e/<google_sheet_key>/pub?gid=0&single=true&output=csv');

where <google_sheet_key> is the "Publish to Web" key for the google sheet listing the slides and display times (keep this a secret if there are shared links to the presentations in this sheet).


2. In the same directory as kiosk_slides.php, create the kiosk.html page. Each Pi kiosk will look at this page, which cycles through the presentations as specified in the google sheet. Note that there is nothing private (ie no secrets) in the webpage itself. Even accessing the kiosk_slides.php file will only display the google sheet contents with the public presenation keys (but not any shared editable links).

 <!DOCTYPE html>
 <html lang="en">
      <head>
 
          <meta http-equiv="refresh" content="3600" /><!-- force refresh every 60 minutes, in case of changes to this page on the server-->
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta name="description" content="">
          <meta name="author" content="">
          <title>Pi Kiosk</title>
     
          <script src="https://code.jquery.com/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
 
           <!-- integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="  -->
 
      </head>
 
      <body>
 
           <style>
 
                html,body {
                     padding: 0;
                     margin: 0;
                }
                iframe {
                     display: block;
                     width: 1980px;
                     height: 1114px;
                     position:relative;
                     left:-28px;
                     top:-4px;
                }
 
                .slide {
                     width:1920px;
                     height:1080px;
                     overflow:hidden;
                     border:solid 1px gray;
                }
 
           </style>
 
           <div id="slide0" class="slide">
                <div style="text-align:center;margin-top:300px;"><h1>Announcements</h1></div>
           </div>
           <div id="slide1" class="slide"></div>
           <div id="slide2" class="slide"></div>
 
           <div id="slide3" class="slide"></div>
           <div id="slide4" class="slide"></div>
           <div id="slide5" class="slide"></div>
 
           <div id="slide6" class="slide"></div>
           <div id="slide7" class="slide"></div>
           <div id="slide8" class="slide"></div>
 
           <div id="slide9" class="slide"></div>
           <div id="slide10" class="slide"></div>
           <div id="slide11" class="slide"></div>
 
 
           <div id="slide12" class="slide"></div>
           <div id="slide13" class="slide"></div>
           <div id="slide14" class="slide"></div>
 
           <script>
 
                /* 
                     look up list of google presentations from a google sheet (retrieved by kiosk_slides.php)
                     filter the list by our kioskName, taken from ?kiosk=<kioskName> parameter
                     construct iframe based on "publish to web" url of the google presentation
                     assign each slide iframe to one of the predefined slide divs (#slide0-#slide<max_slides>)
                     based on display time in the google sheet, show then hide each slide div in sequence
      
                */
 
 
                function getParameterByName(name, url) {
                    if (!url) {
                        url = window.location.href;
                    }
                    name = name.replace(/[\[\]]/g, "\\$&");
                    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
                        results = regex.exec(url);
                    if (!results) return null;
                    if (!results[2]) return '';
                    return decodeURIComponent(results[2].replace(/\+/g, " "));
                }
 
                function ParseGradPhileFormQuery(query) {
 
                    // note that fsuid, eval_year, eval_term, default_eval_year, default_eval_term, qr_abbr and auth are globals 
 
                    var kiosk_param = getParameterByName("kiosk", query);
 
                    if (kiosk_param) {
                        kioskName = kiosk_param;
                    }
 
 
                }
 
 
                var kioskName = "_none";
                const max_slides = 15;
                var slides = [];
 
                // get kioskName from query
                ParseGradPhileFormQuery(window.location.search);
 
                jQuery.get("kiosk_slides.php", function(slides_csv) {
 
                    var rows = slides_csv.split("\n");
 
                    var my_column_index = rows[0].split(",").indexOf(kioskName)
 
                    if (-1 == my_column_index) {
                        // unknown kiosk
                        return;
                    }
                    // remove header row
                    rows = rows.slice(1);
 
                    rows.forEach(function(r) {
                        var cells = r.split(",");
                        var display_time = parseInt(cells[my_column_index], 10);
 
                        if (0 != display_time) {
                            slides.push({
                                // url to display first slide of a google presentation
                                'url': "https://docs.google.com/presentation/d/e/" + cells[1] + "/embed?start=true&loop=false&delayms=60000",
                                'display_time': display_time * 1000
 
                            })
                        }
 
                    }); // next rows
 
                    if (slides.length == 0) {
                        // no slides allocated to this kiosk
                        return;
                    }
 
                    // we only have max_slides number of divs allocated for slides, 
                    // so discard the excess slides
                    while (max_slides < slides.length) {
                        slides.pop();
                    }
 
                    var current_slide = 0;
 
                    // initialize  content of slides
                    // TODO: put in status check before switching to this url
                    for (var i = 0; i < slides.length; i++) {
                        // iframe to display slide from google presentation
                        // width="2016" height="1134"
                        $("#slide" + i).html('<iframe src="' + slides[i].url + '" frameborder="0"  allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>');
                    }
 
                    // cycle through the slides, delaying for the associated display_time before going to next slide
                    function updateSlide() {
                        current_slide = (current_slide + 1) % slides.length;
 
                        console.log("curent_slide: " + current_slide + " display_time: " + slides[current_slide].display_time);
 
                        $(".slide").hide();
                        $("#slide" + current_slide).show();
                        setTimeout(updateSlide, slides[current_slide].display_time);
                    }
 
                    // start the display cycle
                    updateSlide();
 
 
                }); // get php callback
 
           </script>
 
      </body>
 </html>