Bar
SpaceWire UK
Specialist providers of VHDL Intellectual Property & Design Services
BarBarBarBar
Tutorial
Missing Image!
Part 20 - Extend Peek/Poke capabilities & spruce up Webserver pages

Introduction

This tutorial details the steps required to combine the Peek & Poke CGI binaries into one new BitBash binary. Extra capabilities will be added to to this new CGI, namely bit set, bit clear & bit toggle. The Webserver pages will undergo a restructure and general spruce to make them much more organised and consistent. A menu bar will be added the webpages to allow an easier & cleaner way to access the various pages. The tutorial covers quite a range of activities and includes the use of numerous languages; C, HTML, Javascript & PHP.

Aims

The aims of this tutorial are as follows :-

    Part 1 - Project Setup

    1. Setup environment
    2. Obtain tutorial files from Bitbucket (optional)
    3. Change present working directory
    4. Bump Version

    Part 3 - Hardware Deployment (Apache)

    1. Setup Zedboard hardware
    2. Launch MiniCom terminal emulator

    Part 2 - OS Development (Apache)

    1. Run Apache as root
    2. Modify Apache BitBake append

    Part 2 - Software Development (BitBash)

    1. Create C application
    2. Cross compile application

    Part 3 - Hardware Deployment (BitBash)

    1. Copy application to PetaLinux
    2. Check application is working as expected

    Part 4 - OS Development (BitBash)

    1. Modify Makefile & BitBake recipe

    Part 5 - Website Development (Home)

    1. Proposed new directory structure
    2. Create a forwarder webpage
    3. Create header & footer includes
    4. Create Webpage

    Part 6 - Hardware Deployment (Home)

    1. Check everything is working as expected

    Part 7 - Website Development (System Information & Firmware Load)

    1. Create Webpage
    2. Create Javascript

    Part 8 - Hardware Deployment (System Information & Firmware Load)

    1. Check everything is working as expected

    Part 9 - Website Development (Peek & Poke Addresses)

    1. Create Webpage
    2. Create Javascript

    Part 10 - Hardware Deployment (Peek & Poke Addresses)

    1. Check everything is working as expected

    Part 12 - Website Development (Interactive Zedboard)

    1. Create Webpage
    2. Create Javascript
    3. Create Cascaded Style Sheets
    4. Create Overlay images

    Part 13 - Hardware Deployment (Interactive Zedboard)

    1. Check everything is working as expected

    Part 14 - OS Development (Website)

    1. Check directory structure
    2. Modify BitBake recipe

    Part 15 - SQLite Enhancement

    1. Move the database location to the SD Card

    Part 16 - Revisions Control

    1. Commit new & updated files

    Part 17 - Final checks

    1. Create a Production Release
    2. Deploy Production Release on the Zedboard
    3. Check everything is working as expected
    #### Part 1 - Project Setup ####

    1. Setup environment

    Setup Xilinx design environment for the 2021.2 toolset.
    steve@Desktop:~$ xilinx
    Xilinx tools available tools at /opt/Xilinx :-
    1) 2021.2 - Vivado - SDK - Vitis - PetaLinux
    0) Exit
    Please select tools required or exit : 1
    
    Tools are as follows :-
    vivado @ /opt/Xilinx/Vivado/2021.2/bin/vivado
    vitis @ /opt/Xilinx/Vitis/2021.2/bin/vitis
    petalinux-build @ /opt/Xilinx/PetaLinux/2021.2/tool/tools/common/petalinux/bin/petalinux-build
    

    2. Obtain tutorial files from Bitbucket (optional)

    Starting from this point, having not followed the previous tutorials, can be achieved by obtaining the required files from BitBucket. The only prerequisite is that Part 1 - Installation of tools, setup of environment and creation of project area as been completed.

    Obtain OS source.
    steve@Desktop:~$ cd ~/projects
    steve@Desktop:~/projects$ git clone -b v13.0 https://bitbucket.org/spacewire_firmware/zedboard_linux
    

    3. Change present working directory

    Change the present working directory to be the project directory.
    steve@Desktop:~$ cd ~/projects/zedboard_linux
    

    4. Bump Version

    Change the version (or revision) number for this new development, this prevents ghost (post-release, same version) builds from appearing.
    steve@Desktop:~/projects/zedboard_linux$ sed -i 's/13.0/14.0/g' os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt
    

    project.txt

    Zedboard PetaLinux Example Design
    SpaceWire UK
    Steve Haywood
    14.0
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt
    
    #### Part 3 - Hardware Deployment (Apache) ####

    5. Setup Zedboard hardware

    If not already, connect up the hardware as follows :-
    1. Xubuntu PC USB ⇄ Zedboard USB JTAG/Debug
    2. Xubuntu PC USB ⇄ Zedboard USB UART
    3. Zedboard Ethernet ⇄ Router
    4. Xubuntu PC Ethenet ⇄ Router
    5. Router ⇄ Internet
    Missing Image!Set the boot mode jumpers on the Zedboard for JTAG.Missing Image!Power on the Zedboard.

    6. Launch MiniCom terminal emulator

    If not already running, open up a new terminal and launch the MiniCom terminal emulator.
    steve@Desktop:~$ minized
    
    Welcome to minicom 2.7.1
    
    OPTIONS: I18n
    Compiled on Dec 23 2019, 02:06:26.
    Port /dev/ttyACM0, 06:34:25
    
    Press CTRL-A Z for help on special keys
    
    #### Part 2 - OS Development (Apache) ####

    7. Run Apache as root

    To make life easier and avoid the hassle of setting the special root privileges on the various CGI's, Apache will be run using root instead of deamon.
    root@petalinux:~# nano /etc/apache2/httpd.conf
    
    Edit the configuration file and comment out the following two lines, as shown below.

    /etc/apache2/httpd.conf (partial)

    1. #User daemon
    2. #Group daemon
    Reload Apache so the updated httpd.conf is being used.
    root@petalinux:~# /etc/init.d/apache2 reload
    

    8. Modify Apache BitBake append

    Modify the Apache BitBake append to apply the changes to the PetaLinux build process.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend
    

    apache2_%.bbappend

    do_install_append() {
    
    sed -i 's@^#LoadModule cgid_module /usr/libexec/apache2/modules/mod_cgid.so@LoadModule cgid_module /usr/libexec/apache2/modules/mod_cgid.so@' ${D}/${sysconfdir}/${BPN}/httpd.conf
    
    sed -i 's@^DocumentRoot "/usr/share/apache2/default-site/htdocs"@DocumentRoot "/srv/www"@' ${D}/${sysconfdir}/${BPN}/httpd.conf
    
    sed -i 's@^<Directory "/usr/share/apache2/default-site/htdocs">@<Directory "/srv/www">@' ${D}/${sysconfdir}/${BPN}/httpd.conf
    
    sed -i 's@^    ScriptAlias /cgi-bin/ "/usr/libexec/apache2/modules/cgi-bin/"@    ScriptAlias /cgi-bin/ "/srv/www/cgi-bin/"@' ${D}/${sysconfdir}/${BPN}/httpd.conf
    
    sed -i 's@^<Directory "/usr/libexec/apache2/modules/cgi-bin">@<Directory "/srv/www/cgi-bin">@' ${D}/${sysconfdir}/${BPN}/httpd.conf
    
    sed -i 's@^    DirectoryIndex index.html@    DirectoryIndex index.html index.php@' ${D}/${sysconfdir}/${BPN}/httpd.conf
    
    sed -i 's@^User daemon@#User daemon@' ${D}/${sysconfdir}/${BPN}/httpd.conf
    
    sed -i 's@^Group daemon@#Group daemon@' ${D}/${sysconfdir}/${BPN}/httpd.conf
    
    }
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend -O os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend
    
    #### Part 2 - Software Development (BitBash) ####

    9. Create C application

    Create a new C application based on the previous Peek & Poke CGI's. This new application will combine the previous two applications into one and also add the ability to perform bit set, bit clear & bit toggle.

    Copy the original poke.c source to use as a basis for the new application.
    steve@Desktop:~/projects/zedboard_linux$ cp os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/peek.c os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/bitbash.c
    
    Add in the extra functionality.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/bitbash.c
    

    bitbash.c

    //
    // File .......... bitbash.c
    // Author ........ Steve Haywood
    // Version ....... 1.1
    // Date .......... 28 February 2024
    // Description ...
    //   Very simple CGI application for peeking, poking and general bit bashing
    // of a single 32-bit address location. Provides read back value or an error
    // message to the client side application.
    //
    // Examples :-
    //   http://192.168.2.87/cgi-bin/bitbash?peek&0x40010000 ......... Peek value @ 0x40010000
    //   http://192.168.2.87/cgi-bin/bitbash?poke&0x40010000&0xF ..... Poke 0xF @ 0x40010000
    //   http://192.168.2.87/cgi-bin/bitbash?set&0x40010000&0x81 ..... Set bits 0 & 7 @ 0x40010000
    //   http://192.168.2.87/cgi-bin/bitbash?clear&0x40010000&0x2 .... Clear bit 1 @ 0x40010000
    //   http://192.168.2.87/cgi-bin/bitbash?toggle&0x40010000&0xC ... Toggle bits 2 & 3 @ 0x40010000
    //
    
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    
    // Command Types
    #define c_unknown 0
    #define c_peek 1
    #define c_poke 2
    #define c_set 3
    #define c_clear 4
    #define c_toggle 5
    
    int main()
    {
      char *query;
      char *ptype;
      char *paddr;
      char *pdata;
      char *pgarbage;
      const char sep[2] = "&";
      char err = 0;
      char type;
      int fd;
      void *ptr;
      unsigned data;
      unsigned val;
      unsigned addr, page_addr, page_offset;
      unsigned page_size = sysconf(_SC_PAGESIZE);
      printf("Content-Type: text/plain;charset=us-ascii\n\n");
      query = getenv("QUERY_STRING");
      if (query) {
        if (ptype = strtok(query, sep)) {
          if (strcmp(ptype, "peek") == 0) {
            type = c_peek;
          } else if (strcmp(ptype, "poke") == 0) {
            type = c_poke;
          } else if (strcmp(ptype, "set") == 0) {
            type = c_set;
          } else if (strcmp(ptype, "clear") == 0) {
            type = c_clear;
          } else if (strcmp(ptype, "toggle") == 0) {
            type = c_toggle;
          } else {
            type = c_unknown;
          }
          if (type) {
            if (paddr = strtok(NULL, sep)) {
              addr = strtoul(paddr, NULL, 0); // Needs error checking added!
              // Get data for Poke, Set, Clear & Toggle
              if (type != c_peek) {
                if (pdata = strtok(NULL, sep)) {
                  data = strtoul(pdata, NULL, 0); // Needs error checking added!
                } else {
                  err = 1;
                }
              }
              if (!err) {
                if (!(paddr = strtok(NULL, sep))) {
                  fd = open("/dev/mem", O_RDWR);
                  if (fd > 0) {
                    page_addr = (addr & ~(page_size - 1));
                    page_offset = addr - page_addr;
                    ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr);
                    if (ptr != MAP_FAILED) {
    
                      // Read existing data if Set, Clear or Toggle
                      if (type >= c_set) {
                        val = *((volatile unsigned int*)(ptr + page_offset));
                      }
    
                      // Set updated data
                      switch (type) {
                        case c_peek:
                          break;
                        case c_poke:
                          val = data;
                          break;
                        case c_set:
                          val = val | data;
                          break;
                        case c_clear:
                          val = val & ~data;
                          break;
                        case c_toggle:
                          val = val ^ data;
                          break;
                        default:
                          break;
                      }
    
                      // Write updated data & read it back if Poke, Set, Clear or Toggle
                      if (type != c_peek) {
                        *((volatile unsigned int*)(ptr + page_offset)) = val;
                      }
    
                      // Read data, existing for Peek, updated for Poke, Set, Clear and Toggle
                      val = *((volatile unsigned int*)(ptr + page_offset));
    
                      // Pass read data back to user
                      printf("0x%08X", val);
    
                    } else printf("Error: Failed to mmap");
                  } else printf("Error: Failed to open /dev/mem");
                } else printf("Error: Unexpected field found");
              } else printf("Error: Data field not found");
            } else printf("Error: Address field not found");
          } else printf("Error: Command field not recognised");
        } else printf("Error: Command field not found");
      } else printf("Error: No QUERY_STRING");
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/bitbash.c -O os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/bitbash.c
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/peek.c os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/bitbash.c
    

    10. Cross compile application

    Cross compile the C code targetting the ARM processor.
    steve@Desktop:~/projects/zedboard_linux$ arm-linux-gnueabihf-gcc os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/bitbash.c -o /tmp/bitbash
    
    #### Part 3 - Hardware Deployment (BitBash) ####

    11. Copy application to PetaLinux

    Copy the compiled application over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux$ ssh-keygen -f /home/steve/.ssh/known_hosts -R 192.168.2.87
    steve@Desktop:~/projects/zedboard_linux$ ssh-keyscan -H 192.168.2.87 >> ~/.ssh/known_hosts
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root scp /tmp/bitbash root@192.168.2.87:/srv/www/cgi-bin
    

    12. Check application is working as expected

    Using curl enter the following URL to command the bitbash CGI to write 0x18 to the LED register at 0x40010000. This should turn on LEDs 4 & 3.
    steve@Desktop:~/projects/zedboard_linux$ curl -w "\nReceived %{size_download} bytes\n" "http://192.168.2.87/cgi-bin/bitbash?poke&0x40010000&0x18"
    
    Using curl enter the following URL to command the bitbash CGI to read the LED register at 0x40010000. This should show the previously written vale of 0x00000018.
    steve@Desktop:~/projects/zedboard_linux$ curl -w "\nReceived %{size_download} bytes\n" "http://192.168.2.87/cgi-bin/bitbash?peek&0x40010000"
    
    Using curl enter the following URL to command the bitbash CGI to set bits 7 & 0 of the LED register at 0x40010000. This should light up LEDs 7 & 0. All other LEDs should remain unchanged.
    steve@Desktop:~/projects/zedboard_linux$ curl -w "\nReceived %{size_download} bytes\n" "http://192.168.2.87/cgi-bin/bitbash?set&0x40010000&0x81"
    
    Using curl enter the following URL to command the bitbash CGI to clear bits 4 & 3 of the LED register at 0x40010000. This should turn off LEDs 4 & 3. All other LEDs should remain unchanged.
    steve@Desktop:~/projects/zedboard_linux$ curl -w "\nReceived %{size_download} bytes\n" "http://192.168.2.87/cgi-bin/bitbash?clear&0x40010000&0x18"
    
    Using curl enter the following URL to command the bitbash CGI to toggle all of the LED register at 0x40010000. This should turn off LEDs 7 & 0 and turn on LEDs 6 to 1.
    steve@Desktop:~/projects/zedboard_linux$ curl -w "\nReceived %{size_download} bytes\n" "http://192.168.2.87/cgi-bin/bitbash?toggle&0x40010000&0xFF"
    
    #### Part 4 - OS Development (BitBash) ####

    13. Modify Makefile & BitBake recipe

    With confidence that the bitbash application is working OK the Makefile & BitBake recipe can be modified to include the new application in the PetaLinux build process.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile
    

    Makefile

    PEEK = peek
    POKE = poke
    PEEKSTRING = peekstring
    LOADFIRMWARE = loadfirmware
    BITBASH = bitbash
    
    # Add any other object files to this list below
    PEEK_OBJS = peek.o
    POKE_OBJS = poke.o
    PEEKSTRING_OBJS = peekstring.o
    LOADFIRMWARE_OBJS = loadfirmware.o
    BITBASH_OBJS = bitbash.o
    
    all: $(PEEK) $(POKE) $(PEEKSTRING) $(LOADFIRMWARE) $(BITBASH)
    
    $(LOADFIRMWARE): $(LOADFIRMWARE_OBJS)
    	$(CC) $(LDFLAGS) -o $@ $(LOADFIRMWARE_OBJS) $(LDLIBS)
    
    $(BITBASH): $(BITBASH_OBJS)
    	$(CC) $(LDFLAGS) -o $@ $(BITBASH_OBJS) $(LDLIBS)
    
    $(PEEKSTRING): $(PEEKSTRING_OBJS)
    	$(CC) $(LDFLAGS) -o $@ $(PEEKSTRING_OBJS) $(LDLIBS)
    
    $(POKE): $(POKE_OBJS)
    	$(CC) $(LDFLAGS) -o $@ $(POKE_OBJS) $(LDLIBS)
    
    $(PEEK): $(PEEK_OBJS)
    	$(CC) $(LDFLAGS) -o $@ $(PEEK_OBJS) $(LDLIBS)
    
    clean:
    	-rm -f $(BITBASH) $(LOADFIRMWARE) $(PEEKSTRING) $(POKE) $(PEEK) *.elf *.gdb *.o
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile -O os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile
    
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb
    

    peekpokecgi.bb

    #
    # This is the peekpokecgi aplication recipe
    #
    #
    
    SUMMARY = "peekpokecgi application"
    SECTION = "PETALINUX/apps"
    LICENSE = "MIT"
    LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
    SRC_URI = "file://peek.c \
               file://poke.c \
               file://peekstring.c \
               file://loadfirmware.c \
               file://bitbash.c \
               file://Makefile \
              "
    FILES_${PN} += "/srv/www/cgi-bin"
    S = "${WORKDIR}"
    CFLAGS_prepend = "-I ${S}/include"
    do_compile() {
            oe_runmake
    }
    do_install() {
            install -d ${D}/srv/www/cgi-bin
            install -m 0755 ${S}/peek ${D}/srv/www/cgi-bin
            install -m 0755 ${S}/poke ${D}/srv/www/cgi-bin
            install -m 0755 ${S}/peekstring ${D}/srv/www/cgi-bin
            install -m 0755 ${S}/loadfirmware ${D}/srv/www/cgi-bin
            install -m 0755 ${S}/bitbash ${D}/srv/www/cgi-bin
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb -O os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb
    
    #### Part 5 - Website Development (Home) ####

    14. Proposed new directory structure

    As with a lot of developments that start small and grow their structure can became somewhat cumbersome and inconsistent. To address this for the Webserver a more consistent file structure is going to be adopted with each page being served out of its own directory. The directory will contains the webpage and any associated files that go along with it. There will also be a new share directory that contains all the shared files used by the different webpages. The Apache2 setup is such that it looks for index.<html | php> files inside the root www directory and its subdirectories, this capability will be utilised going forward.

    The new file structure (example) :- OK lets get cracking, firstly with the basics, the directory structure and file moves. There will be four new & adapted webpages that will be available from the Webserver.
    steve@Desktop:~/projects/zedboard_linux$ cd os/petalinux/project-spec/meta-user/recipes-apps/website/files
    steve@Desktop:~/projects/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files$ mkdir {home,system,peekpoke,zedboard,share}
    steve@Desktop:~/projects/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files$ git mv {amber.gif,green.gif,red.gif} share
    steve@Desktop:~/projects/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files$ git mv styles.css share/style.css
    steve@Desktop:~/projects/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files$ git mv uptime.js peekpoke/script.js
    steve@Desktop:~/projects/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files$ git mv zedboard.png zedboard
    steve@Desktop:~/projects/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files$ git mv cgi-bin/index.php peekpoke
    steve@Desktop:~/projects/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files$ cd -
    

    15. Create a forwarder webpage

    Create a forwarder webpage for inside the root www directory. The reason for this is to keep all the references from the index.php files consistent. Lets say the Homepage was held in the root www directory, references to the header & footer includes would be different (shared/header.php) from those held in the subdirectories (../shared/header.php). Although this is easy to adjust for in PHP lets stick with consistency for now!
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/index.php
    

    index.php

    <?php
    //
    // File .......... index.php
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.0
    // Conception .... 27 February 2024
    // Standard ...... PHP 7
    // Description ...
    //   Forwarder page for use in root www directory.
    //
    ?>
    
    <?php
    header("Location: home");
    exit;
    ?>
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/index.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/index.php
    

    16. Create header & footer includes

    Create the header and footer includes that will be used in all the webpages. At this point the menu bar will be constructed, this requires extensive CSS work. Note the Internet contains a plethora of examples for these html/css menu bars. The one used here is stolen from above (SpaceWire UK) and modified to suite the application (palette changes mainly).

    Lets do the footer first since it is the easiest one.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/footer.php
    

    footer.php

    <?php
    //
    // File .......... footer.php
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.0
    // Conception .... 27 February 2024
    // Standard ...... PHP 7
    // Description ...
    //   Footer include for website pages.
    //
    ?>
    
    <div class="section">Designed by Steve Haywood @ 2021-<?php echo date("Y") ?></div>
    <?php
    // Add global JS if one exists
    if (file_exists("../share/script.js")) {
        echo '<script src="../share/script.js"></script>';
    }
    // Add local JS if one exists
    if (file_exists("script.js")) {
        echo '<script src="script.js"></script>';
    }
    ?>
    </body>
    </html>
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/footer.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/footer.php
    
    Now the header, a little more involved but not overly complicated. An associative array is used to help build the page elements using the subdirectory name as a key. The page title, header text & any specific onload commands are all pulled from the array.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php
    

    header.php

    <?php
    //
    // File .......... header.php
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.0
    // Conception .... 27 February 2024
    // Standard ...... PHP 7
    // Description ...
    //   Header include for website pages.
    //
    ?>
    
    <?php
    
    // Menu Key = Subdirectory Name
    const c_tab      = 0; // Menu Name
    const c_title    = 1; // Page Tile & Header Text
    const c_onload   = 2; // OnLoad Calls
    const c_submenu  = 3; // Submenu Array
    // Sub-Menu Key = Webpage
    const c_sub_tab  = 0; // Menu Name
    
    // Define pages
    $pages = array(
      "home" => array (
        "Home",
        "Home",
        "",
        array ()
        ),
      "system" => array (
        "System",
        "System Information &amp; Firmware Load",
        "read_os_ids(); read_ids()",
        array ()
        ),
      "peekpoke" => array (
        "Peek &amp; Poke",
        "Peek &amp; Poke Addresses",
        "add_register()",
        array ()
        ),
      "zedboard" => array (
        "Zedboard",
        "Interactive Zedboard",
        "",
        array ()
        ),
      "misc" => array (
        "Misc",
        "Miscellaneous",
        "",
        array (
               "test-cgi" => array("CGI Basic Test"),
               "hello_world.php" => array("PHP Basic Test"),
               "sqlite_test.php" => array ("SQLite Basic Test"),
               "phpliteadmin.php" => array("PHP Lite Admin"),
              ),
        )
    );
    
    // Get subdirectory webpage is being served from
    $base = basename(dirname($_SERVER['PHP_SELF']));
    ?>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <?php
    // Add global CSS if one exists
    if (file_exists("../share/style.css")) {
        echo '<link href="../share/style.css" rel="stylesheet">';
    }
    // Add local CSS if one exists
    if (file_exists("style.css")) {
        echo '<link href="style.css" rel="stylesheet">';
    }
    ?>
    <title><?php echo $pages[$base][c_title] ?></title>
    </head>
    <body onload="<?php echo $pages[$base][c_onload] ?>">
    <div class="section"><h2><?php echo $pages[$base][c_title] ?></h2></div>
    
    <div class="menu">
    <ul class="menu_ul">
    <?php
    foreach ($pages as $key => $fields) {
      if ($key == $base) {
        $colour = "000";
      } else {
        $colour = "666";
      }
      if (count($fields[c_submenu]) > 0) {
        echo '<li class="menu_li"><a style="color:#'.$colour.'" href="#">'.$fields[c_tab].'</a>';
        echo '<ul class="menu_ul">';
        foreach ($fields[c_submenu] as $subkey => $subfields) {
          echo '<li class="menu_li"><a href="/cgi-bin/'.$subkey.'" target=_blank>'.$subfields[c_sub_tab].'</a></li>';
        }
        echo '</ul>';
        echo '</li>';
      } else {
        echo '<li class="menu_li"><a style="color:#'.$colour.'" href="../'.$key.'">'.$fields[c_tab].'</a></li>';
      }
    }
    ?>
    </ul>
    </div>
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php
    
    The CSS is extended at this stage to include the ability to have a centre area that expands horizontally to fill the gap between the top and bottom sections of the webpage. Modifications to the attributes of body are required and a new content section is added. An inner section designed to fit inside the contents section is also added to give an area with a dashed surround. A few extra bits and bobs are tinkered with but nothing worth a mention.

    The main action in this updated CSS is where list elements are shaped to form a horizontal menu bar.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/style.css
    

    style.css

    /*
     * File .......... style.css
     * Author ........ Steve Haywood
     * Website ....... http://www.spacewire.co.uk
     * Project ....... SpaceWire UK Tutorial
     * Version ....... 1.1
     * Conception .... 17 January 2022
     * Standard ...... CSS 3
     * Description ...
     *   Global Cascading Style Sheet for use in all webpages.
     */
    
    body
    {
      height: calc(100vh - 3px);
      margin: 0;
      font-family: "Muli", sans-serif;
    }
    
    h2, h3
    {
      margin: 0;
    }
    
    .section
    {
      margin: 3px;
      padding: 16px;
      background-color: #EFEFFF;
      border: 1px solid #CCCCFF;
      text-align: center;
    }
    
    .content
    {
      min-height:calc(100vh - 194px);
      margin: 3px;
      padding: 16px;
      background-color: #EFEFFF;
      border: 1px solid #CCCCFF;
      text-align: center;
    }
    
    .inner
    {
      margin: 3px;
      padding: 16px;
      border: 1px dashed #AAAAFF;
      text-align: center;
    }
    
    table, th, td {
      border: 1px solid #AAA;
      border-collapse: collapse;
      padding: 0.1em 0.5em;
    }
    
    table {
      display: inline-block;
      border: 0;
    }
    
    
    th {
      background-color: #E0E0FF;
    }
    
    th, td {
      height: 1.4em;
    }
    
    form {
      display: inline-block;
    }
    
    .menu
    {
      margin: 3px;
      height: 27px;
      background-color: #EFEFFF;
      border: 1px solid #CCCCFF;
      text-align: center;
    }
    
    ul.menu_ul
    {
      font-size: 14px;
      font-weight: bold;
      color: #666;
      margin: 0;
      padding: 0;
      list-style: none;
    }
    
    ul.menu_ul li
    {
      display: block;
      position: relative;
      float: left;
    }
    
    li.menu_li ul
    {
      display: none;
    }
    
    ul.menu_ul li a
    {
      display: block;
      text-decoration: none;
      color: #666;
      border-right: 2px solid #fff;
      padding: 5px 15px 5px 15px;
      background: #EFEFFF;
      margin-left: 1px;
      white-space: nowrap;
    }
    
    ul.menu_ul li a:hover
    {
      background: #E0E0FF;
    }
    
    li.menu_li:hover ul
    {
      display: block;
      position: absolute;
    }
    
    li.menu_li:hover li
    {
      float: none;
      font-size: 14px;
    }
    
    li.menu_li:hover a
    {
      background: #EAEAFF;
    }
    
    li.menu_li:hover li a:hover
    {
      background: #E0E0FF;
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/style.css -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/style.css
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/styles.css os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/style.css
    

    17. Create Webpage

    With all the building blocks in place a simple home page can now be constructed.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/home/index.php
    

    index.php

    <?php
    //
    // File .......... index.php
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.0
    // Conception .... 27 February 2024
    // Standard ...... PHP 7
    // Description ...
    //   Webpage for Home.
    //
    ?>
    
    <?php require '../share/header.php'; ?>
    
    <div class="content" style="text-align: left;">
    <h3>Welcome</h3>
    <ul><li>
    Hello and welcome to the Zedboard Webserver tutorial project provided by the guys over at SpaceWire UK.
    </li></ul>
    <h3>Links</h3>
    <ul><li>
    <a href="https://www.spacewire.co.uk/tutorial/index">Using Xilinx PetaLinux, Vitis &amp; Vivado 2021.2 with Xubuntu 20.04.3 on a Zedboard rev. D</a>
    </li></ul>
    </div>
    
    <?php require '../share/footer.php'; ?>
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/home/index.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/home/index.php
    
    #### Part 6 - Hardware Deployment (Home) ####

    18. Check everything is working as expected

    Recursively copy the updated and newly created files over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root scp -r os/petalinux/project-spec/meta-user/recipes-apps/website/files/* root@192.168.2.87:/srv/www
    
    Remove index.html as it take precedence over index.php.
    steve@Desktop:~/projects/zedboard_linux$ git rm os/petalinux/project-spec/meta-user/recipes-apps/website/files/index.html
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root ssh -t root@192.168.2.87 'rm /srv/www/index.html'
    
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). All being well the redirector from /srv/www/index.php redirects to /stv/www/home/index.php and the following page is displayed. Missing Image!
    #### Part 7 - Website Development (System Information & Firmware Load) ####

    19. Create Webpage

    Copy the main index.php file and perform the following :-
    steve@Desktop:~/projects/zedboard_linux$ cp os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php
    
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php
    

    index.php

    <!--
    File .......... index.php
    Author ........ Steve Haywood
    Website ....... http://www.spacewire.co.uk
    Project ....... SpaceWire UK Tutorial
    Version ....... 1.2
    Conception .... 8 August 2023
    Standard ...... PHP 7
    Description ...
      Webpage for System Information & Firmware Load.
    -->
    
    <?php
    // Get information
    $sys_host = exec('hostname', $retval);
    $sys_time = exec('date', $retval);
    $sys_load = exec('awk \'{print $1}\' /proc/loadavg', $retval);
    $sys_up = exec('awk \'{print $1}\' /proc/uptime', $retval);
    $cpu_model = exec('grep model /proc/cpuinfo | cut -d : -f2 | tail -1 | sed \'s/\s//\'', $retval);
    $cpu_cores = exec('grep -c ^processor /proc/cpuinfo', $retval);
    $mem_total = exec('free -m | awk \'NR==2{print $2}\'', $retval);
    $mem_used = exec('free -m | awk \'NR==2{print $3}\'', $retval);
    $mem_free = exec('free -m | awk \'NR==2{print $4}\'', $retval);
    $net_mac = exec('cat /sys/class/net/eth0/address', $retval);
    $net_ip_loc = exec('ip a | grep inet | grep -vw lo | grep -v inet6 | cut -d \/ -f1 | sed \'s/[^0-9\.]*//g\'', $retval);
    $net_ip_ext = exec('wget -q -O- http://ipecho.net/plain', $retval);
    ?>
    
    <?php require '../share/header.php'; ?>
    
    <div class="content">
    
    <div class="inner">
    <?php
    $id = array(0 => "o", 1 => "");
    $title = array(0 => "Operating System", 1 => "Firmware");
    $button = array(0 => "read_os", 1 => "read");
    for ($i = 0 ; $i <= 1 ; $i++) {
    ?>
    <table>
    <thead>
    <tr><th colspan="3"><?php echo $title[$i] ?> Information <button onclick="<?php echo $button[$i] ?>_ids()">Read ID</button></th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td style="text-align:right">Description :</td>
    <td style="text-align:left" id="<?php echo $id[$i] ?>id_0">Unknown</td>
    <td><img id="<?php echo $id[$i] ?>sid_0" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Company :</td>
    <td style="text-align:left" id="<?php echo $id[$i] ?>id_1">Unknown</td>
    <td><img id="<?php echo $id[$i] ?>sid_1" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Author :</td>
    <td style="text-align:left" id="<?php echo $id[$i] ?>id_2">Unknown</td>
    <td><img id="<?php echo $id[$i] ?>sid_2" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Version :</td>
    <td style="text-align:left" id="<?php echo $id[$i] ?>id_3">Unknown</td>
    <td><img id="<?php echo $id[$i] ?>sid_3" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Timestamp :</td>
    <td style="text-align:left" id="<?php echo $id[$i] ?>id_4">Unknown</td>
    <td><img id="<?php echo $id[$i] ?>sid_4" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Hash :</td>
    <td style="text-align:left" id="<?php echo $id[$i] ?>id_5">Unknown</td>
    <td><img id="<?php echo $id[$i] ?>sid_5" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    </tbody>
    </table>
    <?php
    }
    ?>
    </div>
    
    
    <div class="inner">
    <table>
    <thead>
    <tr><th colspan="3">Loadable Firmware</th></tr>
    </thead>
    <tbody>
    <?php
      $dirPath = "/media/sd-mmcblk0p1/firmware";
      $files = scandir($dirPath);
      foreach ($files as $file) {
        $filePath = $dirPath . '/' . $file;
        if (is_file($filePath)) {
          echo "<tr><td style=\"text-align:left\">";
          echo $file;
          echo "</td><td><button onclick=\"loadfirmware('$file')\">Load</button></td><td><img id=\"fm_$file\" style=\"vertical-align:middle\" src=\"../share/amber.gif\" title=\"Unknown!\" alt=\"Missing Image!\"></td></tr>";
        }
      }
    ?>
    </tbody>
    </table>
    </div>
    
    <div class="inner">
    
    <table>
    <tr><th colspan="2">System</th></tr>
    <tr><td>Hostname</td>
    <td><?php echo $sys_host ?></td>
    </tr><tr><td>Time</td><td><?php echo $sys_time ?></td></tr>
    <tr><td>Uptime</td><td><span id="uptime_text"><?php echo $sys_up ?></span> seconds <button onclick="get_uptime()">Refresh</button> Auto :
    <select id="uptime" onchange="uptime();">
      <option value="0">Off</option>
      <option value="1">1s</option>
      <option value="5">5s</option>
    </select>
    </td></tr>
    </table>
    
    <table>
    <tr><th colspan="2">CPU</th></tr>
    <tr><td>Model</td>
    <td><?php echo $cpu_model ?></td></tr>
    <tr><td>Cores</td><td><?php echo $cpu_cores ?></td></tr>
    <tr><td>Load</td><td><?php echo $sys_load ?></td></tr>
    </table>
    
    <table>
    <tr><th colspan="2">Memory</th></tr>
    <tr><td>Total</td><td><?php echo $mem_total ?> Mb</td></tr>
    <tr><td>Used</td><td><?php echo $mem_used ?> Mb</td></tr>
    <tr><td>Free</td><td><?php echo $mem_free ?> Mb</td></tr>
    </table>
    
    <table>
    <tr><th colspan="2">Network</th></tr>
    <tr><td>MAC Address</td><td><?php echo $net_mac ?></td></tr>
    <tr><td>Internal IP</td><td><?php echo $net_ip_loc ?></td></tr>
    <tr><td>External IP</td><td><?php echo $net_ip_ext ?></td></tr>
    </table>
    
    </div>
    
    </div>
    
    <?php require '../share/footer.php'; ?>
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.php os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/index.php
    

    20. Create Javascript

    Create a global Javascript that contains information pertinent to all the webpages.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/script.js
    

    script.js

    //
    // File .......... script.js
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.7
    // Conception .... 28 February 2024
    // Standard ...... ECMA-262
    // Description ...
    //   Global Javascript for use in all webpages.
    //
    
    // Constants
    const c_axi_identification = 0x40000000;
    const c_id_description     = 0x0000;
    const c_id_company         = 0x0080;
    const c_id_author          = 0x00C0;
    const c_id_version         = 0x0100;
    const c_id_timestamp       = 0x0120;
    const c_id_hash            = 0x0140;
    
    const c_axi_gpio_zed       = 0x40010000;
    const c_gpio_leds          = 0x0000;
    const c_gpio_switches      = 0x0008;
    const c_gpio_buttons       = 0x0010;
    
    // Null function
    function nullfunc(respText) {
    }
    
    //
    // Javascript wrapper for call to bitbash CGI
    // cmd .... "peek", "poke", "set", "clear", "toggle"
    // addr ... 0x40010000 (for example, address to access)
    // data ... 0x000000FF (for example, data to write or mask to apply, not used for peek)
    // func ... Javascript function to call upto success
    //
    function bitbash(cmd, addr, data, func) {
      var url = "/cgi-bin/bitbash?" + cmd + "&" + addr;
      if (cmd != "peek") {
        url = url + "&" + data;
      }
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            func(respText);
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/script.js -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/script.js
    
    Copy the main script.js file and perform the following :-
    steve@Desktop:~/projects/zedboard_linux$ cp os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js
    
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js
    

    script.js

    //
    // File .......... script.js
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.7
    // Conception .... 17 January 2022
    // Standard ...... ECMA-262
    // Description ...
    //   Javascript for System Information & Firmware Load.
    //
    
    // Interval between Load Firmware & Read Firmware ID's
    var timer_fw;
    
    // Interval between PetaLinux uptime reads
    var timer_uptime;
    
    // Load PL Firmware
    function loadfirmware(filename) {
      var url = "/cgi-bin/loadfirmware?" + filename;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("fm_" + filename);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../share/red.gif?" + now;
              img_obj.title = "Last loadfirmware failed : " + respText.substr(7);
            } else {
              img_obj.src = "../share/green.gif?" + now;
              img_obj.title = "Last loadfirmware successful";
            }
            clearInterval(timer_fw);
            timer_fw = setInterval(read_ids(), 1000);
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Download OS information file & display result
    async function read_os_ids() {
      let response = await fetch("/project.txt?" + Date.now());
      if (response.status == 200) {
        let ids = await response.text();
        fields = ids.split(/\r?\n/);
      }
      for (var i = 0; i < 6; i++) {
        const now = Date.now();
        const txt_obj = document.getElementById("oid_" + i);
        const img_obj = document.getElementById("osid_" + i);
        if (response.status == 200) {
          if (i < fields.length && fields[i] != "") {
            img_obj.src = "../share/green.gif?" + now;
            img_obj.title = "Last file fetch successful";
            txt_obj.innerHTML = fields[i];
          } else {
            img_obj.src = "../share/red.gif?" + now;
            img_obj.title = "Missing field information";
            txt_obj.innerHTML = "Unknown";
          }
        } else {
          img_obj.src = "../share/red.gif?" + now;
          img_obj.title = "Last file fetch failed";
          txt_obj.innerHTML = "Unknown";
        }
      }
    }
    
    // Peek all strings
    function read_ids() {
      read_id(c_id_description, 0);
      read_id(c_id_company, 1);
      read_id(c_id_author, 2);
      read_id(c_id_version, 3);
      read_id(c_id_timestamp, 4);
      read_id(c_id_hash, 5);
    }
    
    // Peek string & display result
    function read_id(offset, reg) {
      var url = "/cgi-bin/peekstring?" + c_axi_identification + "&" + 4096 + "&" + offset + "&" + 128;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("sid_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../share/red.gif?" + now;
              img_obj.title = "Last peekstring failed : " + respText.substr(7);
            } else {
              const now = Date.now();
              img_obj.src = "../share/green.gif?" + now;
              img_obj.title = "Last peekstring successful";
              document.getElementById("id_" + reg).innerHTML = respText;
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Get uptime
    function get_uptime() {
      var url = "/cgi-bin/uptime.cgi";
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var txtObj = document.getElementById("uptime_text");
            if (txtObj) {
              txtObj.innerHTML = respText;
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Update uptime timer
    function uptime() {
      clearInterval(timer_uptime);
      var uptime = document.getElementById("uptime");
      if (uptime) {
        var interval = uptime.value;
        if (interval > 0) {
          timer_uptime = setInterval("get_uptime()", 1000 * interval);
        }
      }
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/script.js
    
    #### Part 8 - Hardware Deployment (System Information & Firmware Load) ####

    21. Check everything is working as expected

    Recursively copy the updated and newly created files over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root scp -r os/petalinux/project-spec/meta-user/recipes-apps/website/files/* root@192.168.2.87:/srv/www
    
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). Select System from the menu bar. All being well the following page should be displayed. Missing Image! Note the failures on Timestamp & Hash, this is because the recursive copy replaced the build generated project.txt with the default one that does not contain these two fields.
    #### Part 9 - Website Development (Peek & Poke Addresses) ####

    22. Create Webpage

    Edit the main index.php and file and perform the following :-
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php
    

    index.php

    <?php
    //
    // File .......... index.php
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.2
    // Conception .... 8 August 2023
    // Standard ...... PHP 7
    // Description ...
    //   Webpage for Peek & Poke Addresses.
    //
    ?>
    
    <?php require '../share/header.php'; ?>
    
    <div class="content">
    
    <table id="registers">
    <tr>
    <th>Address</th>
    <th>Hex</th>
    <th>Peek Value</th>
    <th>Sel</th>
    <th>Peek</th>
    <th>Status</th>
    <th>Copy</th>
    <th colspan="2">Poke Value</th>
    <th>Sel</th>
    <th>Poke</th>
    <th>Status</th>
    <th>Description</th>
    </tr>
    </table>
    <br><br>
    <input title="Add new row to end of address table" type="button" value="Add" onclick="add_row()">
    <select title="Set type of row to add to address table" id="type">
      <option value="0">Register</option>
      <option value="1">Section</option>
    </select>
    <input title="Remove last address from table" type="button" value="Remove" onclick="rem_register()">
    <input title="Peek all selected addresses in table" type="button" value="Peek All" onclick="peek_all()">
    <input title="Copy all table peek values into poke values" type="button" value="Copy All" onclick="copy_all()">
    <input title="Poke all selected addresses in table" type="button" value="Poke All" onclick="poke_all()">
    Peek Refresh :
    <select title="Set timer interval for automatic peek of table addresses" id="timer" onchange="timer()">
      <option value="0">Off</option>
      <option value="1">1s</option>
      <option value="5">5s</option>
    </select>
    Configuration :
    <button title="Create configuration file from table" onclick="create_config()">Create...</button> <a title="Right click and Save Link As... to locate and rename this file" download="config.txt" id="download" href="" style="display: none">config.txt</a>
    <input title="Read configuration file into table" type="file" id="load_config">
    
    </div>
    
    <?php require '../share/footer.php'; ?>
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.php os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php
    

    23. Create Javascript

    Edit the main script.js file and perform the following :-
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js
    

    script.js

    //
    // File .......... script.js
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.7
    // Conception .... 17 January 2022
    // Standard ...... ECMA-262
    // Description ...
    //   Javascript Peek & Poke Addresses.
    //
    
    // Constants
    const c_uns = 0; // Unsigned string
    const c_hex = 1; // Hexadecimal string
    
    // Requests
    var timer_peek_all;
    
    // Update peek_all timer
    function timer() {
      clearInterval(timer_peek_all);
      var timer = document.getElementById("timer");
      interval = timer.value;
      if (interval > 0) {
        timer_peek_all = setInterval("peek_all()", 1000 * interval);
      }
    }
    
    // Convert unsigned/hexadecimal number string into a specified number string.
    function fmtUnsignedLong(string, format) {
      var hexStr;
      const value = parseInt(string);
      switch(format) {
        case c_uns:
          hexStr = value.toString(10);
          break;
        case c_hex:
          hexStr = value.toString(16).toUpperCase();
          hexStr = "0x" + "00000000".substr(0, 8 - hexStr.length) + hexStr;
          break;
        default:
          break;
      }
      return hexStr;
    }
    
    // Call fmtUnsignedLong with type setting from row in peek/poke address table.
    function fmtUnsignedLongReg(reg, value) {
      const disp_obj = document.getElementById("display_" + reg);
      var hexStr = "0xNaN";
      if (disp_obj) {
        const format = (disp_obj.checked) ? (c_hex) : (c_uns);
        hexStr = fmtUnsignedLong(value, format);
      }
      return hexStr;
    }
    
    // Copy peek to poke & update any associated widgets
    function copy(reg) {
      const peek_obj = document.getElementById("peek_" + reg);
      if (peek_obj) {
        const poke_obj = document.getElementById("poke_" + reg);
        if (poke_obj) {
          poke_obj.value = peek_obj.value;
        }
        const range_obj = document.getElementById("poke_range_" + reg);
        if (range_obj) {
          range_obj.value = parseInt(peek_obj.value);
        }
        const select_obj = document.getElementById("poke_select_" + reg);
        if (select_obj) {
          select_obj.value = peek_obj.value;
        }
      }
    }
    
    // Copy selected peek to poke in section
    function copy_section(row) {
      var obj_sel;
      do {
        row++;
        obj_sel = document.getElementById("peek_sel_" + row);
        if (obj_sel) {
          if (obj_sel.checked) {
            copy(row);
          }
        }
      } while (obj_sel);
    }
    
    // Copy all peek to poke
    function copy_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        copy(index);
      }
    }
    
    // Peek address & display result
    function peek(reg) {
      var url = "/cgi-bin/bitbash?" + "peek" + "&" + document.getElementById("addr_" + reg).value;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("speek_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../share/red.gif?" + now;
              img_obj.title = "Last peek failed : " + respText.substr(7);
            } else {
              img_obj.src = "../share/green.gif?" + now;
              img_obj.title = "Last peek successful";
              document.getElementById("peek_" + reg).value = fmtUnsignedLongReg(reg, respText);
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Peek all selected addresses in section
    function peek_section(row) {
      var obj_sel;
      do {
        row++;
        obj_sel = document.getElementById("peek_sel_" + row);
        if (obj_sel) {
          if (obj_sel.checked) {
            peek(row);
          }
        }
      } while (obj_sel);
    }
    
    // Peek all selected addresses in table
    function peek_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_sel = document.getElementById("peek_sel_" + index);
        if (obj_sel) {
          if (obj_sel.checked) {
            peek(index);
          }
        }
      }
    }
    
    function poke(reg) {
      var url = "/cgi-bin/bitbash?" + "poke" + "&" + document.getElementById("addr_" + reg).value + "&" + document.getElementById("poke_" + reg).value;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("spoke_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../share/red.gif?" + now;
              img_obj.title = "Last poke failed : " + respText.substr(7);
            } else {
              img_obj.src = "../share/green.gif?" + now;
              img_obj.title = "Last poke successful";
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Poke value from input widget & update any associated widgets
    function poke_widget(reg) {
      const poke_obj = document.getElementById("poke_" + reg);
      if (poke_obj) {
        const range_obj = document.getElementById("poke_range_" + reg);
        if (range_obj) {
          range_obj.value = parseInt(poke_obj.value);
        }
        const select_obj = document.getElementById("poke_select_" + reg);
        if (select_obj) {
          select_obj.value = poke_obj.value;
        }
      }
      poke(reg);
    }
    
    // Poke value on Enter key from within input widget
    // Holding down Enter keeps on poking which is not desired - needs improvement!
    function poke_key(reg) {
      if (event.key === 'Enter') {
        poke_widget(reg);
      }
    }
    
    // Update radix for peek & poke input widgets
    function update_radix(reg) {
      const poke_obj = document.getElementById("poke_" + reg);
      if (poke_obj) {
        poke_obj.value = fmtUnsignedLongReg(reg, poke_obj.value);
      }
      const peek_obj = document.getElementById("peek_" + reg);
      if (peek_obj) {
        peek_obj.value = fmtUnsignedLongReg(reg, peek_obj.value);
      }
    }
    
    // Poke value from range widget & update any associated widgets
    function poke_range(reg) {
      const range_obj = document.getElementById("poke_range_" + reg);
      if (range_obj) {
        const poke_obj = document.getElementById("poke_" + reg);
        if (poke_obj) {
          poke_obj.value = fmtUnsignedLongReg(reg, range_obj.value);
        }
      }
      poke(reg);
    }
    
    // Poke value from select widget & update any associated widgets
    function poke_select(reg) {
      const select_obj = document.getElementById("poke_select_" + reg);
      if (select_obj) {
        const poke_obj = document.getElementById("poke_" + reg);
        if (poke_obj) {
          poke_obj.value = fmtUnsignedLongReg(reg, select_obj.value);
        }
      }
      poke(reg);
    }
    
    // Poke all selected addresses in section
    function poke_section(row) {
      var obj_sel;
      do {
        row++;
        obj_sel = document.getElementById("poke_sel_" + row);
        if (obj_sel) {
          if (obj_sel.checked) {
            poke_widget(row);
          }
        }
      } while (obj_sel);
    }
    
    // Poke all selected addresses in table
    function poke_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_sel = document.getElementById("poke_sel_" + index);
        if (obj_sel) {
          if (obj_sel.checked) {
            poke_widget(index);
          }
        }
      }
    }
    
    // Add row to table
    function add_row() {
      const obj_type = document.getElementById("type");
      const row_type = obj_type.value;
      switch(row_type) {
        case "0":
          add_register();
          break;
        case "1":
          add_section();
          break;
        default:
          break;
      }
    }
    
    // Add peek/poke row to table
    function add_register(reg) {
      const table = document.getElementById("registers");
      const next = table.rows.length - 1;
      var addr = c_axi_gpio_zed;
      if (next > 0) {
        const obj_addr = document.getElementById("addr_" + (next - 1));
        if (obj_addr) {
          addr = parseInt(obj_addr.value) + 4;
        }
      }
      const fields = ["reg", addr.toString(), "true", "true", "0x00000000", "true", "Register @ " + fmtUnsignedLong(addr.toString(), c_hex)];
      add_row_raw(fields);
    }
    
    // Add section row to table
    function add_section(reg) {
      const fields = ["sec", "Section Description"];
      add_row_raw(fields);
    }
    
    // Add row to table
    function add_row_raw(fields) {
      const table = document.getElementById("registers");
      const next = table.rows.length - 1;
      const row = table.insertRow(-1);
      var newcell;
    
      // Separate out fields
      var type            = fields[0];
      if (type == "sec") {
        var description   = fields[1];
      } else {
        var address       = fields[1];
        var display_type  = fields[2];
        var peek_select   = fields[3];
        var options       = fields[4].split("#");
        var poke_select   = fields[5];
        var description   = fields[6];
        // Decode fields
        var disp_checked  = (display_type == "true") ? ("checked") : ("");
        var format        = (display_type == "true") ? (c_hex) : (c_uns);
        var peek_checked  = (peek_select == "true") ? ("checked") : ("");
        var poke_checked  = (poke_select == "true") ? ("checked") : ("");
      }
    
      newcell = row.insertCell(-1);
      if (type == "sec") {
        newcell.colSpan = "4";
        newcell.innerHTML = "--- Section ---";
      } else {
        newcell.innerHTML = '<input title="Address to peek/poke" type="text" id="addr_' + next + '" value="' + fmtUnsignedLong(address, c_hex) + '" size="10">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Select peak/poke display type (unsigned/hexadecimal)" type="checkbox" id="display_' + next + '" ' + disp_checked + ' onchange="update_radix(' + next + ')">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Value peeked at address" type="text" id="peek_' + next + '" value="'+ fmtUnsignedLong("0x0", format) +'" size="10" readonly="readonly">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Select address for peeking" type="checkbox" id="peek_sel_' + next + '" ' + peek_checked + '>';
      }
    
      newcell = row.insertCell(-1);
      if (type == "sec") {
        newcell.innerHTML = '<input title="Peek all selected addresses in section" type="submit" value="Peek" onclick="peek_section(' + next + ')">';
      } else {
        newcell.innerHTML = '<input title="Peek address" type="submit" value="Peek" onclick="peek(' + next + ')">';
      }
    
      newcell = row.insertCell(-1);
      if (type == "sec") {
        newcell.innerHTML = '<img style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
      } else {
        newcell.innerHTML = '<img title="Peek status" id="speek_' + next + '" style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
      }
    
      newcell = row.insertCell(-1);
      if (type == "sec") {
        newcell.innerHTML = '<input title="Copy all selected peek values into poke values in section" type="submit" value=">>" onclick="copy_section(' + next + ')">';
      } else {
        newcell.innerHTML = '<input title="Copy peek value into poke value" type="submit" value=">>" onclick="copy(' + next + ')">';
      }
    
      newcell = row.insertCell(-1);
      switch (type) {
        case "sec":
          newcell.colSpan = "3";
          newcell.innerHTML = "--- Section ---";
          break;
        case "reg":
          const poke_value    = fields[4];
          newcell.innerHTML = '<input title="Value to poke at address" type="text" id="poke_' + next + '" value="'+ fmtUnsignedLong(poke_value, format) +'" size="10" onkeydown="poke_key(' + next + ')">';
          newcell = row.insertCell(-1);
          newcell.style.padding = "0";
          break;
        case "range":
          var range_value   = options[0];
          var range_min     = options[1];
          var range_max     = options[2];
          newcell.innerHTML = '<input title="Value to poke at address" type="text" id="poke_' + next + '" value="'+ fmtUnsignedLong(range_value, format) +'" size="10" onkeydown="poke_key(' + next + ')">';
          newcell = row.insertCell(-1);
          newcell.innerHTML = '<input title="Value to poke at address" style="width:100%" type="range" min="'+ range_min +'" max="'+ range_max +'" value="'+ range_value +'" id="poke_range_' + next + '" oninput="poke_range(' + next + ')">';
          break;
        case "select":
          var opt_str = "";
          var opt_sel;
          var opt_pair;
          const sel = options[0];
          for (var index = 1; index < options.length; index++) {
            opt_pair = options[index].split("^");
            opt_sel = (sel == index - 1) ? (" selected") : ("");
            opt_str = opt_str.concat('<option value="', opt_pair[0], '"', opt_sel + '>', opt_pair[1], '</option>');
          }
          newcell.innerHTML = '<input title="Value to poke at address" type="text" id="poke_' + next + '" value="'+ fmtUnsignedLong(sel, format) +'" size="10" onkeydown="poke_key(this, ' + next + ')">';
          newcell = row.insertCell(-1);
          newcell.innerHTML = '<select title="Value to poke at address" style="width:100%" id="poke_select_' + next + '" onchange="poke_select(' + next + ')">' + opt_str + '</select>';
          break;
        default:
          break;
      }
    
      if (type == "sec") {
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Poke all selected addresses in section" type="submit" value="Poke" onclick="poke_section(' + next + ')">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<img style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Description of section" type="text" id="name_' + next + '" value="' + description + '" size="40">';
      } else {
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Select address for poking" type="checkbox" id="poke_sel_' + next + '" ' + poke_checked + '>';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Poke address" type="submit" value="Poke" onclick="poke_widget(' + next + ')">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<img title="Poke status" id="spoke_' + next + '" style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
    
        newcell = row.insertCell(-1);
        newcell.innerHTML = '<input title="Description of address" type="text" id="name_' + next + '" value="' + description + '" size="40">';
      }
    }
    
    // Remove row from table
    function rem_register(reg) {
      var table = document.getElementById("registers");
      if (table.rows.length > 1) {
        table.deleteRow(-1);
      }
    }
    
    // Remove all rows from table
    function remove_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        table.deleteRow(-1);
      }
    }
    
    // Note: browser file access is made difficult for security reasons - there maybe a better way of doing file read & write.
    
    var config_file = null;
    
    // Create virtual configuration file from address table for user to download
    function create_config() {
      var text = "";
      var table = document.getElementById("registers");
      const rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_addr = document.getElementById("addr_" + index);
        if (obj_addr) { // Register type
          var addr = document.getElementById("addr_" + index).value;
          var display = document.getElementById("display_" + index).checked;
          var peek_sel = document.getElementById("peek_sel_" + index).checked;
          var poke = document.getElementById("poke_" + index).value;
          var poke_sel = document.getElementById("poke_sel_" + index).checked;
          var name = document.getElementById("name_" + index).value;
          text += "reg" + "|" + addr + "|" + display + "|" + peek_sel + "|" + poke + "|" + poke_sel + "|"+ name + "\n";
        } else { // Section type
          var name = document.getElementById("name_" + index).value;
          text += "sec" + "|" + name + "\n";
        }
      }
      const data = new Blob([text], {type: 'text/plain'});
      if (config_file !== null) {
        URL.revokeObjectURL(config_file);
      }
      config_file = URL.createObjectURL(data);
      var link = document.getElementById('download');
      link.href = config_file;
      link.style.display = 'inline';
    }
    
    // Read configuration file and update address table
    function load_config(input) {
      var file = input.target.files[0];
      if (file) {
        var reader = new FileReader();
        reader.onload = function(input) {
          var contents = input.target.result;
          const lines = contents.split(/\r\n|\n/);
          remove_all();
          lines.forEach((line) => {
            if (line.length > 0) {
              var table = document.getElementById("registers");
              var next = table.rows.length - 1;
              const values = line.split("|");
              switch(values[0]) {
                case "reg":
                case "sec":
                case "range":
                case "select":
                  add_row_raw(values);
                  break;
                default:
                  alert("Error: Unrecognized table type found (" + values[0] + "), ignoring.");
              }
            }
          });
        };
        reader.readAsText(file);
      }
    }
    
    document.getElementById('load_config').addEventListener('change', load_config, false);
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js
    
    #### Part 10 - Hardware Deployment (Peek & Poke Addresses) ####

    24. Check everything is working as expected

    Recursively copy the updated and newly created files over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root scp -r os/petalinux/project-spec/meta-user/recipes-apps/website/files/* root@192.168.2.87:/srv/www
    
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). Select Peek & Poke from the menu bar. All being well the following page should be displayed. Missing Image!
    #### Part 12 - Website Development (Interactive Zedboard) ####

    25. Create Webpage

    Let's have a little fun and finally use the zedboard image that's been kicking around for a long time now.

    Create an interactive webpage for the zedboard image that includes the following features :-
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/index.php
    

    index.php

    <?php
    /*
     *
     * File .......... index.php
     * Author ........ Steve Haywood
     * Website ....... http://www.spacewire.co.uk
     * Project ....... SpaceWire UK Tutorial
     * Version ....... 1.0
     * Conception .... 26 February 2024
     * Standard ...... PHP 7
     * Description ...
     *   Webpage for Interactive Zedboard.
     *
     */
    ?>
    
    <?php require '../share/header.php'; ?>
    
    <div class="content" style="text-align:left">
    
    <h3>Interactive Zedboard :-</h3>
    <ul>
    <li><button onclick="get_status()">Read</button> board state for LEDs, switches &amp; push buttons.</li>
    <li>Automatically refresh board state every <select id="status" onchange="status();">
      <option value="0">Never</option>
      <option value="1">100</option>
      <option value="10">1000</option>
    </select> ms.</li>
    <li>Toggle LEDs on/off by clicking on them.</li>
    <li>Clear latched push button states by clicking on them.</li>
    </ul>
    
    <div class="zedboard">
    
    <img src="zedboard.png" alt="Missing Image!" usemap="#zedmap">
    
    <img id="id_led_0" class="led_0" src="led_off.png" alt="">
    <img id="id_led_1" class="led_1" src="led_off.png" alt="">
    <img id="id_led_2" class="led_2" src="led_off.png" alt="">
    <img id="id_led_3" class="led_3" src="led_off.png" alt="">
    <img id="id_led_4" class="led_4" src="led_off.png" alt="">
    <img id="id_led_5" class="led_5" src="led_off.png" alt="">
    <img id="id_led_6" class="led_6" src="led_off.png" alt="">
    <img id="id_led_7" class="led_7" src="led_off.png" alt="">
    
    <img id="id_sw_0" class="sw_0" src="sw_none.png" alt="">
    <img id="id_sw_1" class="sw_1" src="sw_none.png" alt="">
    <img id="id_sw_2" class="sw_2" src="sw_none.png" alt="">
    <img id="id_sw_3" class="sw_3" src="sw_none.png" alt="">
    <img id="id_sw_4" class="sw_4" src="sw_none.png" alt="">
    <img id="id_sw_5" class="sw_5" src="sw_none.png" alt="">
    <img id="id_sw_6" class="sw_6" src="sw_none.png" alt="">
    <img id="id_sw_7" class="sw_7" src="sw_none.png" alt="">
    
    <img id="id_btn_0" class="btn_0" src="btn_none.png" alt="">
    <img id="id_btn_1" class="btn_1" src="btn_none.png" alt="">
    <img id="id_btn_2" class="btn_2" src="btn_none.png" alt="">
    <img id="id_btn_3" class="btn_3" src="btn_none.png" alt="">
    <img id="id_btn_4" class="btn_4" src="btn_none.png" alt="">
    
    </div>
    
    <map id="zedmap" name="zedmap">
    
    <area title="Toggle LED 0 On/Off" shape="rect" coords="382,443,405,461" alt="Missing Image!" href="#" onclick="toggle_led(1)">
    <area title="Toggle LED 1 On/Off" shape="rect" coords="356,443,379,461" alt="Missing Image!" href="#" onclick="toggle_led(2)">
    <area title="Toggle LED 2 On/Off" shape="rect" coords="330,443,353,461" alt="Missing Image!" href="#" onclick="toggle_led(4)">
    <area title="Toggle LED 3 On/Off" shape="rect" coords="304,443,327,461" alt="Missing Image!" href="#" onclick="toggle_led(8)">
    <area title="Toggle LED 4 On/Off" shape="rect" coords="278,443,301,461" alt="Missing Image!" href="#" onclick="toggle_led(16)">
    <area title="Toggle LED 5 On/Off" shape="rect" coords="252,443,275,461" alt="Missing Image!" href="#" onclick="toggle_led(32)">
    <area title="Toggle LED 6 On/Off" shape="rect" coords="226,443,249,461" alt="Missing Image!" href="#" onclick="toggle_led(64)">
    <area title="Toggle LED 7 On/Off" shape="rect" coords="200,443,223,461" alt="Missing Image!" href="#" onclick="toggle_led(128)">
    
    <area title="Clear top button state" shape="rect" coords="486,416,519,441" alt="Missing Image!" href="#" onclick="clear_button(16)">
    <area title="Clear centre button state" shape="rect" coords="486,446,519,471" alt="Missing Image!" href="#" onclick="clear_button(1)">
    <area title="Clear bottom button state" shape="rect" coords="486,477,519,502" alt="Missing Image!" href="#" onclick="clear_button(2)">
    <area title="Clear left button state" shape="rect" coords="337,446,480,471" alt="Missing Image!" href="#" onclick="clear_button(4)">
    <area title="Clear right button state" shape="rect" coords="525,446,558,471" alt="Missing Image!" href="#" onclick="clear_button(8)">
    
    </map>
    
    </div>
    
    <?php require '../share/footer.php'; ?>
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/index.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/index.php
    

    26. Create Javascript

    Create the Javascript for the Interactive Zedboard webpage.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/script.js
    

    script.js

    //
    // File .......... script.js
    // Author ........ Steve Haywood
    // Website ....... http://www.spacewire.co.uk
    // Project ....... SpaceWire UK Tutorial
    // Version ....... 1.7
    // Conception .... 28 February 2024
    // Standard ...... ECMA-262
    // Description ...
    //   Javascript for Interactive Zedboard.
    //
    
    // Interval between status reads
    let timer_status;
    
    // Read LEDs, switches & buttons registers and update image overlays
    function get_status() {
      bitbash("peek", c_axi_gpio_zed + c_gpio_leds, 0, update_leds);
      bitbash("peek", c_axi_gpio_zed + c_gpio_switches, 0, update_switches);
      bitbash("peek", c_axi_gpio_zed + c_gpio_buttons, 0, update_buttons);
    }
    
    // Update status timer
    function status() {
      clearInterval(timer_status);
      var status = document.getElementById("status");
      if (status) {
        var interval = status.value;
        if (interval > 0) {
          timer_status = setInterval("get_status()", 100 * interval);
        }
      }
    }
    
    // Update Zedboard LEDs image overlays
    function update_leds(respText) {
      for (var i = 0; i < 8; i++) {
        var img_obj = document.getElementById("id_led_" + i);
        if (img_obj) {
          const now = Date.now();
          const value = parseInt(respText);
          if (value & (2**i)) {
            img_obj.src = "led_on.png?" + now;
          } else {
            img_obj.src = "led_off.png?" + now;
          }
        }
      }
    }
    
    // Update Zedboard switches overlays
    function update_switches(respText) {
      for (var i = 0; i < 8; i++) {
        var img_obj = document.getElementById("id_sw_" + i);
        if (img_obj) {
          const now = Date.now();
          const value = parseInt(respText);
          if (value & (2**i)) {
            img_obj.src = "sw_on.png?" + now;
          } else {
            img_obj.src = "sw_off.png?" + now;
          }
        }
      }
    }
    
    // Update Zedboard buttons image overlays
    function update_buttons(respText) {
      for (var i = 0; i < 5; i++) {
        var img_obj = document.getElementById("id_btn_" + i);
        const value = parseInt(respText);
        if (img_obj) {
          const now = Date.now();
          if (value & (2**i)) {
            img_obj.src = "btn_on.png?" + now;
          } else {
            img_obj.src = "btn_off.png?" + now;
          }
        }
      }
    }
    
    // Toggle LED on/off
    function toggle_led(mask) {
      bitbash('toggle', c_axi_gpio_zed + c_gpio_leds, mask, nullfunc);
    }
    
    // Clear button state
    function clear_button(mask) {
      bitbash('clear', c_axi_gpio_zed + c_gpio_buttons, mask, nullfunc);
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/script.js -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/script.js
    

    27. Create Cascaded Style Sheets

    Create a Cascaded Style Sheets for the Interactive Zedboard webpage.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/style.css
    

    style.css

    /*
     * File .......... style.css
     * Author ........ Steve Haywood
     * Website ....... http://www.spacewire.co.uk
     * Project ....... SpaceWire UK Tutorial
     * Version ....... 1.0
     * Conception .... 28 February 2024
     * Standard ...... CSS 3
     * Description ...
     *   Cascading Style Sheet for Interactive Zedboard.
     */
    
    .zedboard {
      border: 0;
      float: left;
      position: relative;
    }
    
    .led_0 { position: absolute; left: 391px; top: 446px; }
    .led_1 { position: absolute; left: 365px; top: 446px; }
    .led_2 { position: absolute; left: 339px; top: 446px; }
    .led_3 { position: absolute; left: 313px; top: 446px; }
    .led_4 { position: absolute; left: 287px; top: 446px; }
    .led_5 { position: absolute; left: 261px; top: 446px; }
    .led_6 { position: absolute; left: 235px; top: 446px; }
    .led_7 { position: absolute; left: 209px; top: 446px; }
    
    .sw_0 { position: absolute; left: 388px; top: 475px; }
    .sw_1 { position: absolute; left: 361px; top: 475px; }
    .sw_2 { position: absolute; left: 335px; top: 475px; }
    .sw_3 { position: absolute; left: 308px; top: 475px; }
    .sw_4 { position: absolute; left: 281px; top: 475px; }
    .sw_5 { position: absolute; left: 255px; top: 475px; }
    .sw_6 { position: absolute; left: 228px; top: 475px; }
    .sw_7 { position: absolute; left: 201px; top: 475px; }
    
    .btn_0 { position: absolute; left: 497px; top: 454px; }
    .btn_1 { position: absolute; left: 497px; top: 485px; }
    .btn_2 { position: absolute; left: 458px; top: 454px; }
    .btn_3 { position: absolute; left: 536px; top: 454px; }
    .btn_4 { position: absolute; left: 497px; top: 424px; }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/style.css -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/style.css
    

    28. Create Overlay images

    Create transparent overlay images for the LEDs, switches and buttons using GIMP. Life too short? Grab the files :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/{btn_none.png,btn_off.png,btn_on.png,led_off.png,led_on.png,sw_none.png,sw_off.png,sw_on.png} -P os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard
    
    #### Part 13 - Hardware Deployment (Interactive Zedboard) ####

    29. Check everything is working as expected

    Recursively copy the updated and newly created files over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root scp -r os/petalinux/project-spec/meta-user/recipes-apps/website/files/* root@192.168.2.87:/srv/www
    
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). Select Zedboard from the menu bar. Click on the Read Button. All being well something akin to the following page should be displayed. Missing Image!
    #### Part 14 - OS Development (Website) ####

    30. Check directory structure

    Examine the new directory structure to check all is as expected.
    steve@Desktop:~/projects/zedboard_linux$ tree os/petalinux/project-spec/meta-user/recipes-apps/website/files
    os/petalinux/project-spec/meta-user/recipes-apps/website/files
    ├── cgi-bin
    │   ├── hello_world.php
    │   ├── phpliteadmin.config.php
    │   ├── phpliteadmin.php
    │   ├── sqlite_test.php
    │   ├── test-cgi
    │   └── uptime.cgi
    ├── home
    │   └── index.php
    ├── index.php
    ├── peekpoke
    │   ├── index.php
    │   └── script.js
    ├── project.txt
    ├── share
    │   ├── amber.gif
    │   ├── footer.php
    │   ├── green.gif
    │   ├── header.php
    │   ├── red.gif
    │   ├── script.js
    │   └── style.css
    ├── system
    │   ├── index.php
    │   └── script.js
    └── zedboard
        ├── btn_none.png
        ├── btn_off.png
        ├── btn_on.png
        ├── index.php
        ├── led_off.png
        ├── led_on.png
        ├── script.js
        ├── style.css
        ├── sw_none.png
        ├── sw_off.png
        ├── sw_on.png
        └── zedboard.png
    
    6 directories, 32 files
    
    Looks good!

    31. Modify BitBake recipe

    With confidence that the updated Website application is working OK the BitBake recipe can be modified to include the changes in the PetaLinux build process.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb
    

    website.bb

    #
    # This file is the website recipe.
    #
    
    SUMMARY = "Simple website application"
    SECTION = "PETALINUX/apps"
    LICENSE = "MIT"
    LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
    
    SRC_URI = "file://project.txt"
    SRC_URI += "file://index.php"
    
    SRC_URI += "file://share/header.php"
    SRC_URI += "file://share/footer.php"
    SRC_URI += "file://share/style.css"
    SRC_URI += "file://share/script.js"
    SRC_URI += "file://share/amber.gif"
    SRC_URI += "file://share/green.gif"
    SRC_URI += "file://share/red.gif"
    
    SRC_URI += "file://cgi-bin/uptime.cgi"
    SRC_URI += "file://cgi-bin/test-cgi"
    SRC_URI += "file://cgi-bin/hello_world.php"
    SRC_URI += "file://cgi-bin/sqlite_test.php"
    SRC_URI += "file://cgi-bin/phpliteadmin.php"
    SRC_URI += "file://cgi-bin/phpliteadmin.config.php"
    
    SRC_URI += "file://home/index.php"
    
    SRC_URI += "file://system/index.php"
    SRC_URI += "file://system/script.js"
    
    SRC_URI += "file://peekpoke/index.php"
    SRC_URI += "file://peekpoke/script.js"
    
    SRC_URI += "file://zedboard/index.php"
    SRC_URI += "file://zedboard/script.js"
    SRC_URI += "file://zedboard/style.css"
    SRC_URI += "file://zedboard/zedboard.png"
    SRC_URI += "file://zedboard/sw_on.png"
    SRC_URI += "file://zedboard/sw_off.png"
    SRC_URI += "file://zedboard/sw_none.png"
    SRC_URI += "file://zedboard/btn_on.png"
    SRC_URI += "file://zedboard/btn_off.png"
    SRC_URI += "file://zedboard/btn_none.png"
    SRC_URI += "file://zedboard/led_on.png"
    SRC_URI += "file://zedboard/led_off.png"
    
    FILES_${PN} += "/srv/www"
    
    S = "${WORKDIR}"
    
    do_install() {
      install -d ${D}/srv/www
      install -m 0644 ${S}/project.txt ${D}/srv/www
      install -m 0644 ${S}/index.php ${D}/srv/www
    
      install -d ${D}/srv/www/share
      install -m 0644 ${S}/share/header.php ${D}/srv/www/share
      install -m 0644 ${S}/share/footer.php ${D}/srv/www/share
      install -m 0644 ${S}/share/style.css ${D}/srv/www/share
      install -m 0644 ${S}/share/script.js ${D}/srv/www/share
      install -m 0644 ${S}/share/amber.gif ${D}/srv/www/share
      install -m 0644 ${S}/share/green.gif ${D}/srv/www/share
      install -m 0644 ${S}/share/red.gif ${D}/srv/www/share
    
      install -d ${D}/srv/www/cgi-bin
      install -m 0777 -d ${D}/srv/www/cgi-bin/db
      install -m 0755 ${S}/cgi-bin/uptime.cgi ${D}/srv/www/cgi-bin
      install -m 0755 ${S}/cgi-bin/test-cgi ${D}/srv/www/cgi-bin
      install -m 0644 ${S}/cgi-bin/hello_world.php ${D}/srv/www/cgi-bin
      install -m 0644 ${S}/cgi-bin/sqlite_test.php ${D}/srv/www/cgi-bin
      install -m 0644 ${S}/cgi-bin/phpliteadmin.php ${D}/srv/www/cgi-bin
      install -m 0644 ${S}/cgi-bin/phpliteadmin.config.php ${D}/srv/www/cgi-bin
    
      install -d ${D}/srv/www/home
      install -m 0644 ${S}/home/index.php ${D}/srv/www/home
    
      install -d ${D}/srv/www/system
      install -m 0644 ${S}/system/index.php ${D}/srv/www/system
      install -m 0644 ${S}/system/script.js ${D}/srv/www/system
    
      install -d ${D}/srv/www/peekpoke
      install -m 0644 ${S}/peekpoke/index.php ${D}/srv/www/peekpoke
      install -m 0644 ${S}/peekpoke/script.js ${D}/srv/www/peekpoke
    
      install -d ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/index.php ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/style.css ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/script.js ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/zedboard.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/sw_on.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/sw_off.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/sw_none.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/btn_on.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/btn_off.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/btn_none.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/led_on.png ${D}/srv/www/zedboard
      install -m 0644 ${S}/zedboard/led_off.png ${D}/srv/www/zedboard
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb -O os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb
    
    #### Part 15 - SQLite Enhancement ####

    32. Move the database location to the SD Card

    Databases stored on a read-only file system have very limited (short-term) use. Using the SD Card to store such databases would be very advantageous. Lets move the database location from from /srv/www/cgi-bin/db to /media/sd-mmcblk0p1/database.
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root ssh -t root@192.168.2.87 'mkdir /media/sd-mmcblk0p1/database'
    
    Change the SQLite test PHP (on PetaLinux for now) to reflect the db location change.

    sqlite_test.php (partial)

    1.     $this->open('db/test.db');
    2.     $this->open('/media/sd-mmcblk0p1/database/test.db');
    Update the file using sed.
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root ssh -t root@192.168.2.87 "sed -i 's/db\/test.db/\/media\/sd-mmcblk0p1\/database\/test.db/' /srv/www/cgi-bin/sqlite_test.php"
    
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). Select Misc » SQLite Basic Test from the menu bar. All being well the following page should be displayed in a new tab. Missing Image! Examine the database directory to check that everything is as expected.
    root@petalinux:~# ls -la /media/sd-mmcblk0p1/database
    total 20
    drwxr-xr-x    2 root     root          4096 Mar  2 08:24 .
    drwxr-xr-x    5 root     root          4096 Jan  1  1970 ..
    -rwxr-xr-x    1 root     root         12288 Mar  2 08:24 test.db
    
    Since there is now a persistent database that will still exist after a reboot, an enhanced SQLite Test webpage is worth the investment. Let's enhance what we already have, something akin to the following should do the trick.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test.php
    

    sqlite_test.php

    <?php
    /*
    * File .......... sqlite_test.php
    * Author ........ Steve Haywood
    * Website ....... http://www.spacewire.co.uk
    * Project ....... SpaceWire UK Tutorial
    * Version ....... 1.2
    * Conception .... 8 August 2023
    * Standard ...... PHP 7
    * Description ...
    *   Simple HTML, PHP & SQLite example code that offers some degree of error
    * checking on the form inputs. Reports SQL operations and success/failure
    * status. Demonstrates some of the common SQL queries :-
    *
    * 1. Create/open database
    * 2. Create table
    * 3. Drop table
    * 4. Insert row
    * 5. Delete row
    * 6. Update row
    * 7. Close database
    */
    ?>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Simple PHP &amp; SQLite Demonstration</title>
    </head>
    
    <style>
    table, th, td {
      border: 1px solid;
    }
    
    .success {
      color: green;
    }
    
    .fail {
      color: red;
    }
    </style>
    
    <body>
    
    <script>
    function row_delete(obj_submit) {
      if (obj_submit) {
        const row = obj_submit.id.substr(7);
        const obj_item = document.getElementById("item_" + row);
        if (obj_item) {
          location.replace("<?php echo $whoami; ?>?action=delete&item=" + obj_item.value);
        }
      }
    }
    
    function row_insert(obj_submit) {
      if (obj_submit) {
        const obj_item = document.getElementById("insert_item");
        if (obj_item) {
          const obj_quantity = document.getElementById("insert_quantity");
          if (obj_quantity) {
            location.replace("<?php echo $whoami; ?>?action=insert&item=" + obj_item.value + "&quantity=" + obj_quantity.value);
          }
        }
      }
    }
    
    function row_update(obj_submit) {
      if (obj_submit) {
        const row = obj_submit.id.substr(7);
        const obj_item = document.getElementById("item_" + row);
        if (obj_item) {
          const obj_quantity = document.getElementById("quantity_" + row);
          if (obj_quantity) {
            location.replace("<?php echo $whoami; ?>?action=update&item=" + obj_item.value + "&quantity=" + obj_quantity.value);
          }
        }
      }
    }
    </script>
    
    <?php
    $whoami = basename($_SERVER['PHP_SELF']);
    $db_name = 'test.db';
    $table = "fruit";
    $indent = "&nbsp;&nbsp;&nbsp;... ";
    
    echo '<h3>Simple PHP &amp; SQLite Demonstration</h3>';
    
    $db_exists = file_exists('/media/sd-mmcblk0p1/database/test.db');
    
    if ($db_exists) {
      echo "Attempting to open existing database<br>";
    } else {
      echo "Attempting to create new database<br>";
    }
    
    class MyDB extends SQLite3 {
      function __construct() {
        $this->open('/media/sd-mmcblk0p1/database/test.db');
      }
    }
    
    if ($db_exists) {
      echo $indent . 'Opening of existing';
    } else {
      echo $indent . 'Creation of new';
    }
    
    $db = new MyDB();
    if ($db) {
      echo ' database <span class="success">successful</span>';
    } else {
      echo ' database <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    }
    
    echo '<br><br>';
    
    echo 'Validating form submission (if any)<br>';
    
    $form_action_key = "action";
    $form_action_values = array("insert", "delete", "create", "drop", "update");
    if (isset($_GET[$form_action_key])) {
      $form_action_value = $_GET[$form_action_key];
      echo $indent . 'Form key (' . $form_action_key . ') <span class="success">detected</span> with value (' . $form_action_value . ') which ';
      if (in_array($form_action_value, $form_action_values)) {
        echo '<span class="success">exists</span>';
        $process_form = 1; // Good form submission
      } else {
        echo '<span class="fail">does not exist</span>';
        $process_form = 0; // Bad form submission
      }
      echo ' in expected set (';
      $last_value = end(array_values($form_action_values));
      foreach ($form_action_values as $value) {
        echo $value;
        if ($value != $last_value) {
          echo (', ');
        }
      }
      echo ')';
    } else {
      echo $indent . 'Form key (' . $form_action_key . ') <span class="success">not detected</span>';
      $process_form = 0; // No form submission
    }
    
    if (!$process_form) {
      echo ', treating page as a non-form/normal page';
    }
    
    if ($process_form) {
    
      echo '<br><br>';
    
      switch($form_action_value) {
        case "create" :
    
          echo 'Attempting to create table<br>';
          $query = "CREATE TABLE $table (
            item VARCHAR(30) NOT NULL PRIMARY KEY UNIQUE,
            quantity int unsigned NOT NULL
          )";
          $return = $db->exec($query);
          if ($return) {
            echo $indent . 'Table created <span class="success">successfully</span>';
          } else {
            echo $indent . 'Table creation <span class="fail">failed</span> : ' . $db->lastErrorMsg();
          }
          break;
    
        case "drop" :
    
          echo 'Attempting to drop table<br>';
          $query = "DROP TABLE $table";
          $result = $db->exec($query);
          if ($result) {
            echo $indent . 'Table dropped <span class="success">successfully</span>';
          } else {
            echo $indent . 'Table drop <span class="fail">failed</span> : ' . $db->lastErrorMsg();
          }
          break;
    
        case 'insert' :
          echo 'Attempting to insert into table<br>';
          $proceed = 1;
          $item = $_GET['item'];
          if ((isset($item) == 0) || ($item == "")) {
            echo $indent . 'Row insertion <span class="fail">failed</span> due to missing/empty item field<br>';
            $proceed = 0;
          }
          $quantity = $_GET['quantity'];
          if ((isset($quantity) == 0) || ($quantity == "")) {
            echo $indent . 'Row insertion <span class="fail">failed</span> due to missing/empty quantity field<br>';
            $proceed = 0;
          }
          if ($proceed) {
            $query = "INSERT INTO $table (item, quantity) VALUES ('$item', '$quantity')";
            $result = $db->exec($query);
            if ($result) {
              echo $indent . 'Table insertion <span class="success">successful</span>';
            } else {
              echo $indent . 'Table insertion <span class="fail">failed</span> : ' . $db->lastErrorMsg();
            }
          }
          break;
    
        case 'delete' :
          $item = $_GET['item'];
          echo 'Attempting to delete row with Unique ID ' . $item . ' from table<br>';
          if ($item) {
            $query = "DELETE FROM $table WHERE item='$item'";
            $result = $db->exec($query);
            if ($result) {
              echo $indent . 'Row deletion <span class="success">successful</span>';
            } else {
              echo $indent . 'Row deletion <span class="fail">failed</span> : ' . $db->lastErrorMsg();
            }
          } else {
            echo $indent . 'Row deletion <span class="fail">failed</span> due to missing/empty item field<br>';
          }
          break;
    
        case 'update' :
          echo 'Attempting to update row in table<br>';
          $proceed = 1;
          $item = $_GET['item'];
          if ((isset($item) == 0) || ($item == "")) {
            echo $indent . 'Row insertion <span class="fail">failed</span> due to missing/empty item field<br>';
            $proceed = 0;
          }
          $quantity = $_GET['quantity'];
          if ((isset($quantity) == 0) || ($quantity == "")) {
            echo $indent . 'Row insertion <span class="fail">failed</span> due to missing/empty quantity field<br>';
            $proceed = 0;
          }
          if ($proceed) {
            $query = "UPDATE $table SET quantity = $quantity WHERE item = '$item'";
            $result = $db->exec($query);
            if ($result) {
              echo $indent . 'Table row update <span class="success">successful</span>';
            } else {
              echo $indent . 'Table row update <span class="fail">failed</span> : ' . $db->lastErrorMsg();
            }
          }
          break;
    
      }
    
    }
    ?>
    
    <br><br>
    
    <?php
    echo 'Attempting to select all data from table (if any)<br>';
    $query = "SELECT * FROM $table";
    $result = $db->query($query);
    if ($result) {
      echo $indent . 'Table data selection <span class="success">successful</span>';
    } else {
      echo $indent . 'Table data selection <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    }
    
    if ($result) {
    
      echo '<br><br>';
      echo 'Attempting to fetch data from table (if any)';
      echo '<br><br>';
    
      echo '<table>';
      echo '<tr>';
      echo '<th>Item</th>';
      echo '<th>Quantity</th>';
      echo '<th>Action</th>';
      echo '</tr>';
    
      $pos = 0;
      while($row = $result->fetchArray(SQLITE3_ASSOC))
      {
        echo '<tr>';
    
        echo '<td>';
        echo '<input id="item_' . $pos . '" type="text" value="'.$row['item'].'" readonly>';
        echo '</td>';
    
        echo '<td>';
        echo '<input id="quantity_' . $pos . '" type="text" value="'.$row['quantity'].'">';
        echo '</td>';
    
        echo '<td>';
        echo '<input id="delete_' . $pos . '" type="submit" value="Delete" onclick="row_delete(this)">';
        echo '<input id="update_' . $pos . '" type="submit" value="Update" onclick="row_update(this)">';
        echo '</td>';
    
        echo '</tr>';
        $pos++;
      }
    
      echo '<tr>';
      echo '  <td>';
      echo '  <input id="insert_item" type="text">';
      echo '  </td>';
      echo '  <td>';
      echo '  <input id="insert_quantity" type="text">';
      echo '  </td>';
      echo '  <td>';
      echo '  <input id="insert" type="submit" value="Insert" onclick="row_insert(this)">';
      echo '  </td>';
      echo '</tr>';
      echo '</table>';
    }
    
    echo '<h3>Select option below :-</h3>';
    
    echo '<ul>';
    echo '<li><a href="'.$whoami.'?action=create">Create table</a></li>';
    echo '<li><a href="'.$whoami.'?action=drop">Drop table</a></li>';
    echo '</ul>';
    
    ?>
    
    <?php
    $db->close();
    ?>
    
    </body>
    </html>
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test.php
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test.php os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test.php
    
    Recursively copy the updated and newly created files over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root scp -r os/petalinux/project-spec/meta-user/recipes-apps/website/files/* root@192.168.2.87:/srv/www
    
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). Select Misc » SQLite Basic Test from the menu bar. All being well the following page should be displayed in a new tab. Missing Image! Upon the reboot of PetaLinux all the fruity information will still be available in the database.

    Change the PHP Lite Admin configuration to reflect the db location change.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/phpliteadmin.config.php
    

    cgi-bin/phpliteadmin.config.php (partial)

    1. $directory = 'db';
    2. $directory = '/media/sd-mmcblk0p1/database';
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0026/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/phpliteadmin.config.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/phpliteadmin.config.php
    
    Recursively copy the updated and newly created files over to PetaLinux.
    steve@Desktop:~/projects/zedboard_linux$ sshpass -p root scp -r os/petalinux/project-spec/meta-user/recipes-apps/website/files/* root@192.168.2.87:/srv/www
    
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). Select Misc » PHP Lite Admin from the menu bar. All being well the following page should be displayed in a new tab. Missing Image!
    #### Part 16 - Revisions Control ####

    33. Commit new & updated files

    Check GIT status to make sure all is well and there are no spurious elements.
    steve@Desktop:~/projects/zedboard_linux$ git status
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
            deleted:    os/petalinux/project-spec/meta-user/recipes-apps/website/files/index.html      
            renamed:    os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.php -> os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php      
            renamed:    os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js -> os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js      
            renamed:    os/petalinux/project-spec/meta-user/recipes-apps/website/files/amber.gif -> os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/amber.gif      
            renamed:    os/petalinux/project-spec/meta-user/recipes-apps/website/files/green.gif -> os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/green.gif      
            renamed:    os/petalinux/project-spec/meta-user/recipes-apps/website/files/red.gif -> os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/red.gif      
            renamed:    os/petalinux/project-spec/meta-user/recipes-apps/website/files/styles.css -> os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/style.css      
            renamed:    os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard.png -> os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/zedboard.png      
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile      
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb      
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/phpliteadmin.config.php      
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test.php      
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/index.php      
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/peekpoke/script.js      
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt      
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/style.css      
            modified:   os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb      
            modified:   os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend      
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
            os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/bitbash.c      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/home/      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/index.php      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/footer.php      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/header.php      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/share/script.js      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/system/      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/btn_none.png      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/btn_off.png      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/btn_on.png      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/index.php      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/led_off.png      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/led_on.png      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/script.js      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/style.css      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/sw_none.png      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/sw_off.png      
            os/petalinux/project-spec/meta-user/recipes-apps/website/files/zedboard/sw_on.png      
    
    Looks good!.
    steve@Desktop:~/projects/zedboard_linux$ git add os/petalinux/project-spec/meta-user/recipes-apps/website
    steve@Desktop:~/projects/zedboard_linux$ git add os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi
    
    Commit the updates, create an annotated tag and push the commit & tag up to the remote repository.
    steve@Desktop:~/projects/zedboard_linux$ git commit -a -m "Restructured website to include a menu bar. Added a better SQLite Test webpage. Changed Apache to run as root."
    steve@Desktop:~/projects/zedboard_linux$ git push
    steve@Desktop:~/projects/zedboard_linux$ git tag -a v14.0 -m "PetaLinux, Peek/Poke, LED Runner, LAMP (Apache:root, SQLite, PHP & myLiteAdmin), Peek/Poke CGI, Load Firmware CGI, PL Access, Style Sheet, Register Bank & ID Strings with XSA from zedboard_leds_switches v5.0"
    steve@Desktop:~/projects/zedboard_linux$ git push origin v14.0
    
    steve@Desktop:~/projects/zedboard_linux$ cd os/petalinux
    
    #### Part 17 - Final checks ####

    34. Create a Production Release

    With the project area assumed clean and everything committed & pushed into the repository, a potential production release of PetaLinux (v14.0) can be produced.

    Double check GIT status.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    
    nothing to commit, working tree clean
    
    Optionally, but preferred, clear out all the superfluous files from the project area (non-tracked files).
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git clean -fdx
    
    Optionally, but preferred, clear out the transfer area.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ rm -rf /tftpboot/*
    
    Build/rebuild PetaLinux.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-build
    
    Package PetaLinux to produce the boot image BOOT.BIN, this will include the first stage boot loader zynq_fsbl.elf, the programmable logic system.bit, the Linux boot loader u-boot.elf and the device tree blob system.dtb.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --uboot images/linux/u-boot.elf
    

    35. Deploy Production Release on the Zedboard

    Check the Zedboard is present on the network & PetaLinux is up and running.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ ping -c 1 192.168.2.87
    
    Enable a pathway to PetaLinux running on the Zedboard.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ ssh-keygen -f ~/.ssh/known_hosts -R 192.168.2.87
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ ssh-keyscan -H 192.168.2.87 >> ~/.ssh/known_hosts
    
    Upload the new PetaLinux boot files to the Zedboard.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root scp /tftpboot/{BOOT.BIN,boot.scr,image.ub} root@192.168.2.87:/media/sd-mmcblk0p1
    
    Reboot PetaLinux running on the Zedboard to make use of the new boot files.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ sshpass -p root ssh -t root@192.168.2.87 /sbin/reboot
    

    36. Check everything is working as expected

    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). Select System from the menu bar. All being well the following should be displayed in the Operating System Information & Firmware Information sections.

    There should be no unsavory comments after the version numbers! Missing Image!