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 (3 March 2024)

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
    2. Final checks
    #### 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
    #### 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 SD Card. 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

    1. do_install_append() {

    2. 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

    3. sed -i 's@^DocumentRoot "/usr/share/apache2/default-site/htdocs"@DocumentRoot "/srv/www"@' ${D}/${sysconfdir}/${BPN}/httpd.conf

    4. sed -i 's@^<Directory "/usr/share/apache2/default-site/htdocs">@<Directory "/srv/www">@' ${D}/${sysconfdir}/${BPN}/httpd.conf

    5. sed -i 's@^    ScriptAlias /cgi-bin/ "/usr/libexec/apache2/modules/cgi-bin/"@    ScriptAlias /cgi-bin/ "/srv/www/cgi-bin/"@' ${D}/${sysconfdir}/${BPN}/httpd.conf

    6. sed -i 's@^<Directory "/usr/libexec/apache2/modules/cgi-bin">@<Directory "/srv/www/cgi-bin">@' ${D}/${sysconfdir}/${BPN}/httpd.conf

    7. sed -i 's@^    DirectoryIndex index.html@    DirectoryIndex index.html index.php@' ${D}/${sysconfdir}/${BPN}/httpd.conf

    8. sed -i 's@^User daemon@#User daemon@' ${D}/${sysconfdir}/${BPN}/httpd.conf

    9. sed -i 's@^Group daemon@#Group daemon@' ${D}/${sysconfdir}/${BPN}/httpd.conf

    10. }
    Life too short? Save the above file in the appropriate location and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend -O os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:os/petalinux/project-spec/meta-user/recipes-httpd/apache2/apache2_%.bbappend 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.

    Using poke.c as the base lets add the required functionality.
    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
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/bitbash.c

    bitbash.c

    1. //
    2. // File .......... bitbash.c
    3. // Author ........ Steve Haywood
    4. // Version ....... 1.1
    5. // Date .......... 28 February 2024
    6. // Description ...
    7. //   Very simple CGI application for peeking, poking and general bit bashing
    8. // of a single 32-bit address location. Provides read back value or an error
    9. // message to the client side application.
    10. //
    11. // Examples :-
    12. //   http://192.168.2.87/cgi-bin/bitbash?peek&0x40010000 ......... Peek value @ 0x40010000
    13. //   http://192.168.2.87/cgi-bin/bitbash?poke&0x40010000&0xF ..... Poke 0xF @ 0x40010000
    14. //   http://192.168.2.87/cgi-bin/bitbash?set&0x40010000&0x81 ..... Set bits 0 & 7 @ 0x40010000
    15. //   http://192.168.2.87/cgi-bin/bitbash?clear&0x40010000&0x2 .... Clear bit 1 @ 0x40010000
    16. //   http://192.168.2.87/cgi-bin/bitbash?toggle&0x40010000&0xC ... Toggle bits 2 & 3 @ 0x40010000
    17. //

    18. #include <stdio.h>
    19. #include <string.h>
    20. #include <stdlib.h>
    21. #include <unistd.h>
    22. #include <sys/mman.h>
    23. #include <fcntl.h>

    24. // Command Types
    25. #define c_unknown 0
    26. #define c_peek 1
    27. #define c_poke 2
    28. #define c_set 3
    29. #define c_clear 4
    30. #define c_toggle 5

    31. int main()
    32. {
    33.   char *query;
    34.   char *ptype;
    35.   char *paddr;
    36.   char *pdata;
    37.   char *pgarbage;
    38.   const char sep[2] = "&";
    39.   char err = 0;
    40.   char type;
    41.   int fd;
    42.   void *ptr;
    43.   unsigned data;
    44.   unsigned val;
    45.   unsigned addr, page_addr, page_offset;
    46.   unsigned page_size = sysconf(_SC_PAGESIZE);
    47.   printf("Content-Type: text/plain;charset=us-ascii\n\n");
    48.   query = getenv("QUERY_STRING");
    49.   if (query) {
    50.     if (ptype = strtok(query, sep)) {
    51.       if (strcmp(ptype, "peek") == 0) {
    52.         type = c_peek;
    53.       } else if (strcmp(ptype, "poke") == 0) {
    54.         type = c_poke;
    55.       } else if (strcmp(ptype, "set") == 0) {
    56.         type = c_set;
    57.       } else if (strcmp(ptype, "clear") == 0) {
    58.         type = c_clear;
    59.       } else if (strcmp(ptype, "toggle") == 0) {
    60.         type = c_toggle;
    61.       } else {
    62.         type = c_unknown;
    63.       }
    64.       if (type) {
    65.         if (paddr = strtok(NULL, sep)) {
    66.           addr = strtoul(paddr, NULL, 0); // Needs error checking added!
    67.           // Get data for Poke, Set, Clear & Toggle
    68.           if (type != c_peek) {
    69.             if (pdata = strtok(NULL, sep)) {
    70.               data = strtoul(pdata, NULL, 0); // Needs error checking added!
    71.             } else {
    72.               err = 1;
    73.             }
    74.           }
    75.           if (!err) {
    76.             if (!(paddr = strtok(NULL, sep))) {
    77.               fd = open("/dev/mem", O_RDWR);
    78.               if (fd > 0) {
    79.                 page_addr = (addr & ~(page_size - 1));
    80.                 page_offset = addr - page_addr;
    81.                 ptr = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, page_addr);
    82.                 if (ptr != MAP_FAILED) {

    83.                   // Read existing data if Set, Clear or Toggle
    84.                   if (type >= c_set) {
    85.                     val = *((volatile unsigned int*)(ptr + page_offset));
    86.                   }

    87.                   // Set updated data
    88.                   switch (type) {
    89.                     case c_peek:
    90.                       break;
    91.                     case c_poke:
    92.                       val = data;
    93.                       break;
    94.                     case c_set:
    95.                       val = val | data;
    96.                       break;
    97.                     case c_clear:
    98.                       val = val & ~data;
    99.                       break;
    100.                     case c_toggle:
    101.                       val = val ^ data;
    102.                       break;
    103.                     default:
    104.                       break;
    105.                   }

    106.                   // Write updated data & read it back if Poke, Set, Clear or Toggle
    107.                   if (type != c_peek) {
    108.                     *((volatile unsigned int*)(ptr + page_offset)) = val;
    109.                   }

    110.                   // Read data, existing for Peek, updated for Poke, Set, Clear and Toggle
    111.                   val = *((volatile unsigned int*)(ptr + page_offset));

    112.                   // Pass read data back to user
    113.                   printf("0x%08X", val);

    114.                 } else printf("Error: Failed to mmap");
    115.               } else printf("Error: Failed to open /dev/mem");
    116.             } else printf("Error: Unexpected field found");
    117.           } else printf("Error: Data field not found");
    118.         } else printf("Error: Address field not found");
    119.       } else printf("Error: Command field not recognised");
    120.     } else printf("Error: Command field not found");
    121.   } else printf("Error: No QUERY_STRING");
    122. }

    Life too short? Save the above file in the appropriate location and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0: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

    1. PEEK = peek
    2. POKE = poke
    3. PEEKSTRING = peekstring
    4. LOADFIRMWARE = loadfirmware
    5. BITBASH = bitbash

    6. # Add any other object files to this list below
    7. PEEK_OBJS = peek.o
    8. POKE_OBJS = poke.o
    9. PEEKSTRING_OBJS = peekstring.o
    10. LOADFIRMWARE_OBJS = loadfirmware.o
    11. BITBASH_OBJS = bitbash.o

    12. all: $(PEEK) $(POKE) $(PEEKSTRING) $(LOADFIRMWARE) $(BITBASH)

    13. $(LOADFIRMWARE): $(LOADFIRMWARE_OBJS)
    14.   $(CC) $(LDFLAGS) -o $@ $(LOADFIRMWARE_OBJS) $(LDLIBS)

    15. $(BITBASH): $(BITBASH_OBJS)
    16.   $(CC) $(LDFLAGS) -o $@ $(BITBASH_OBJS) $(LDLIBS)

    17. $(PEEKSTRING): $(PEEKSTRING_OBJS)
    18.   $(CC) $(LDFLAGS) -o $@ $(PEEKSTRING_OBJS) $(LDLIBS)

    19. $(POKE): $(POKE_OBJS)
    20.   $(CC) $(LDFLAGS) -o $@ $(POKE_OBJS) $(LDLIBS)

    21. $(PEEK): $(PEEK_OBJS)
    22.   $(CC) $(LDFLAGS) -o $@ $(PEEK_OBJS) $(LDLIBS)

    23. clean:
    24.   -rm -f $(BITBASH) $(LOADFIRMWARE) $(PEEKSTRING) $(POKE) $(PEEK) *.elf *.gdb *.o

    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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$ git difftool v13.0:os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile 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

    1. #
    2. # This is the peekpokecgi aplication recipe
    3. #
    4. #

    5. SUMMARY = "peekpokecgi application"
    6. SECTION = "PETALINUX/apps"
    7. LICENSE = "MIT"
    8. LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
    9. SRC_URI = "file://peek.c \
    10.            file://poke.c \
    11.            file://peekstring.c \
    12.            file://loadfirmware.c \
    13.            file://bitbash.c \
    14.            file://Makefile \
    15.           "
    16. FILES_${PN} += "/srv/www/cgi-bin"
    17. S = "${WORKDIR}"
    18. CFLAGS_prepend = "-I ${S}/include"
    19. do_compile() {
    20.         oe_runmake
    21. }
    22. do_install() {
    23.         install -d ${D}/srv/www/cgi-bin
    24.         install -m 0755 ${S}/peek ${D}/srv/www/cgi-bin
    25.         install -m 0755 ${S}/poke ${D}/srv/www/cgi-bin
    26.         install -m 0755 ${S}/peekstring ${D}/srv/www/cgi-bin
    27.         install -m 0755 ${S}/loadfirmware ${D}/srv/www/cgi-bin
    28.         install -m 0755 ${S}/bitbash ${D}/srv/www/cgi-bin
    29. }

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb -O os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb 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

    1. <?php
    2. //
    3. // File .......... index.php
    4. // Author ........ Steve Haywood
    5. // Website ....... http://www.spacewire.co.uk
    6. // Project ....... SpaceWire UK Tutorial
    7. // Version ....... 1.0
    8. // Conception .... 27 February 2024
    9. // Standard ...... PHP 7
    10. // Description ...
    11. //   Forwarder page for use in root www directory.
    12. //
    13. ?>

    14. <?php
    15. header("Location: home");
    16. exit;
    17. ?>

    Life too short? Grab the file :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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

    1. <?php
    2. //
    3. // File .......... footer.php
    4. // Author ........ Steve Haywood
    5. // Website ....... http://www.spacewire.co.uk
    6. // Project ....... SpaceWire UK Tutorial
    7. // Version ....... 1.0
    8. // Conception .... 27 February 2024
    9. // Standard ...... PHP 7
    10. // Description ...
    11. //   Footer include for website pages.
    12. //
    13. ?>

    14. <div class="section">Designed by Steve Haywood @ 2021-<?php echo date("Y") ?></div>
    15. <?php
    16. // Add global JS if one exists
    17. if (file_exists("../share/script.js")) {
    18.     echo '<script src="../share/script.js"></script>';
    19. }
    20. // Add local JS if one exists
    21. if (file_exists("script.js")) {
    22.     echo '<script src="script.js"></script>';
    23. }
    24. ?>
    25. </body>
    26. </html>

    Life too short? Grab the file :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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

    1. <?php
    2. //
    3. // File .......... header.php
    4. // Author ........ Steve Haywood
    5. // Website ....... http://www.spacewire.co.uk
    6. // Project ....... SpaceWire UK Tutorial
    7. // Version ....... 1.0
    8. // Conception .... 27 February 2024
    9. // Standard ...... PHP 7
    10. // Description ...
    11. //   Header include for website pages.
    12. //
    13. ?>

    14. <?php

    15. // Menu Key = Subdirectory Name
    16. const c_tab      = 0; // Menu Name
    17. const c_title    = 1; // Page Tile & Header Text
    18. const c_onload   = 2; // OnLoad Calls
    19. const c_submenu  = 3; // Submenu Array
    20. // Sub-Menu Key = Webpage
    21. const c_sub_tab  = 0; // Menu Name

    22. // Define pages
    23. $pages = array(
    24.   "home" => array (
    25.     "Home",
    26.     "Home",
    27.     "",
    28.     array ()
    29.     ),
    30.   "system" => array (
    31.     "System",
    32.     "System Information &amp; Firmware Load",
    33.     "read_os_ids(); read_ids()",
    34.     array ()
    35.     ),
    36.   "peekpoke" => array (
    37.     "Peek &amp; Poke",
    38.     "Peek &amp; Poke Addresses",
    39.     "add_register()",
    40.     array ()
    41.     ),
    42.   "zedboard" => array (
    43.     "Zedboard",
    44.     "Interactive Zedboard",
    45.     "",
    46.     array ()
    47.     ),
    48.   "misc" => array (
    49.     "Misc",
    50.     "Miscellaneous",
    51.     "",
    52.     array (
    53.            "test-cgi" => array("CGI Basic Test"),
    54.            "hello_world.php" => array("PHP Basic Test"),
    55.            "sqlite_test.php" => array ("SQLite Basic Test"),
    56.            "phpliteadmin.php" => array("PHP Lite Admin"),
    57.           ),
    58.     )
    59. );

    60. // Get subdirectory webpage is being served from
    61. $base = basename(dirname($_SERVER['PHP_SELF']));
    62. ?>

    63. <!DOCTYPE html>
    64. <html lang="en">
    65. <head>
    66. <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    67. <?php
    68. // Add global CSS if one exists
    69. if (file_exists("../share/style.css")) {
    70.     echo '<link href="../share/style.css" rel="stylesheet">';
    71. }
    72. // Add local CSS if one exists
    73. if (file_exists("style.css")) {
    74.     echo '<link href="style.css" rel="stylesheet">';
    75. }
    76. ?>
    77. <title><?php echo $pages[$base][c_title] ?></title>
    78. </head>
    79. <body onload="<?php echo $pages[$base][c_onload] ?>">
    80. <div class="section"><h2><?php echo $pages[$base][c_title] ?></h2></div>

    81. <div class="menu">
    82. <ul class="menu_ul">
    83. <?php
    84. foreach ($pages as $key => $fields) {
    85.   if ($key == $base) {
    86.     $colour = "000";
    87.   } else {
    88.     $colour = "666";
    89.   }
    90.   if (count($fields[c_submenu]) > 0) {
    91.     echo '<li class="menu_li"><a style="color:#'.$colour.'" href="#">'.$fields[c_tab].'</a>';
    92.     echo '<ul class="menu_ul">';
    93.     foreach ($fields[c_submenu] as $subkey => $subfields) {
    94.       echo '<li class="menu_li"><a href="/cgi-bin/'.$subkey.'" target=_blank>'.$subfields[c_sub_tab].'</a></li>';
    95.     }
    96.     echo '</ul>';
    97.     echo '</li>';
    98.   } else {
    99.     echo '<li class="menu_li"><a style="color:#'.$colour.'" href="../'.$key.'">'.$fields[c_tab].'</a></li>';
    100.   }
    101. }
    102. ?>
    103. </ul>
    104. </div>

    Life too short? Grab the file :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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

    1. /*
    2. * File .......... style.css
    3. * Author ........ Steve Haywood
    4. * Website ....... http://www.spacewire.co.uk
    5. * Project ....... SpaceWire UK Tutorial
    6. * Version ....... 1.1
    7. * Conception .... 17 January 2022
    8. * Standard ...... CSS 3
    9. * Description ...
    10. *   Global Cascading Style Sheet for use in all webpages.
    11. */

    12. body
    13. {
    14.   height: calc(100vh - 3px);
    15.   margin: 0;
    16.   font-family: "Muli", sans-serif;
    17. }

    18. h2, h3
    19. {
    20.   margin: 0;
    21. }

    22. .section
    23. {
    24.   margin: 3px;
    25.   padding: 16px;
    26.   background-color: #EFEFFF;
    27.   border: 1px solid #CCCCFF;
    28.   text-align: center;
    29. }

    30. .content
    31. {
    32.   min-height:calc(100vh - 194px);
    33.   margin: 3px;
    34.   padding: 16px;
    35.   background-color: #EFEFFF;
    36.   border: 1px solid #CCCCFF;
    37.   text-align: center;
    38. }

    39. .inner
    40. {
    41.   margin: 3px;
    42.   padding: 16px;
    43.   border: 1px dashed #AAAAFF;
    44.   text-align: center;
    45. }

    46. table, th, td {
    47.   border: 1px solid #AAA;
    48.   border-collapse: collapse;
    49.   padding: 0.1em 0.5em;
    50. }

    51. table {
    52.   display: inline-block;
    53.   border: 0;
    54. }


    55. th {
    56.   background-color: #E0E0FF;
    57. }

    58. th, td {
    59.   height: 1.4em;
    60. }

    61. form {
    62.   display: inline-block;
    63. }

    64. .menu
    65. {
    66.   margin: 3px;
    67.   height: 27px;
    68.   background-color: #EFEFFF;
    69.   border: 1px solid #CCCCFF;
    70.   text-align: center;
    71. }

    72. ul.menu_ul
    73. {
    74.   font-size: 14px;
    75.   font-weight: bold;
    76.   color: #666;
    77.   margin: 0;
    78.   padding: 0;
    79.   list-style: none;
    80. }

    81. ul.menu_ul li
    82. {
    83.   display: block;
    84.   position: relative;
    85.   float: left;
    86. }

    87. li.menu_li ul
    88. {
    89.   display: none;
    90. }

    91. ul.menu_ul li a
    92. {
    93.   display: block;
    94.   text-decoration: none;
    95.   color: #666;
    96.   border-right: 2px solid #fff;
    97.   padding: 5px 15px 5px 15px;
    98.   background: #EFEFFF;
    99.   margin-left: 1px;
    100.   white-space: nowrap;
    101. }

    102. ul.menu_ul li a:hover
    103. {
    104.   background: #E0E0FF;
    105. }

    106. li.menu_li:hover ul
    107. {
    108.   display: block;
    109.   position: absolute;
    110. }

    111. li.menu_li:hover li
    112. {
    113.   float: none;
    114.   font-size: 14px;
    115. }

    116. li.menu_li:hover a
    117. {
    118.   background: #EAEAFF;
    119. }

    120. li.menu_li:hover li a:hover
    121. {
    122.   background: #E0E0FF;
    123. }

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0: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

    1. <?php
    2. //
    3. // File .......... index.php
    4. // Author ........ Steve Haywood
    5. // Website ....... http://www.spacewire.co.uk
    6. // Project ....... SpaceWire UK Tutorial
    7. // Version ....... 1.0
    8. // Conception .... 27 February 2024
    9. // Standard ...... PHP 7
    10. // Description ...
    11. //   Webpage for Home.
    12. //
    13. ?>

    14. <?php require '../share/header.php'; ?>

    15. <div class="content" style="text-align: left;">
    16. <h3>Welcome</h3>
    17. <ul><li>
    18. Hello and welcome to the Zedboard Webserver tutorial project provided by the guys over at SpaceWire UK.
    19. </li></ul>
    20. <h3>Links</h3>
    21. <ul><li>
    22. <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>
    23. </li></ul>
    24. </div>

    25. <?php require '../share/footer.php'; ?>

    Life too short? Grab the file :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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

    1. <!--
    2. File .......... index.php
    3. Author ........ Steve Haywood
    4. Website ....... http://www.spacewire.co.uk
    5. Project ....... SpaceWire UK Tutorial
    6. Version ....... 1.2
    7. Conception .... 8 August 2023
    8. Standard ...... PHP 7
    9. Description ...
    10.   Webpage for System Information & Firmware Load.
    11. -->

    12. <?php
    13. // Get information
    14. $sys_host = exec('hostname', $retval);
    15. $sys_time = exec('date', $retval);
    16. $sys_load = exec('awk \'{print $1}\' /proc/loadavg', $retval);
    17. $sys_up = exec('awk \'{print $1}\' /proc/uptime', $retval);
    18. $cpu_model = exec('grep model /proc/cpuinfo | cut -d : -f2 | tail -1 | sed \'s/\s//\'', $retval);
    19. $cpu_cores = exec('grep -c ^processor /proc/cpuinfo', $retval);
    20. $mem_total = exec('free -m | awk \'NR==2{print $2}\'', $retval);
    21. $mem_used = exec('free -m | awk \'NR==2{print $3}\'', $retval);
    22. $mem_free = exec('free -m | awk \'NR==2{print $4}\'', $retval);
    23. $net_mac = exec('cat /sys/class/net/eth0/address', $retval);
    24. $net_ip_loc = exec('ip a | grep inet | grep -vw lo | grep -v inet6 | cut -d \/ -f1 | sed \'s/[^0-9\.]*//g\'', $retval);
    25. $net_ip_ext = exec('wget -q -O- http://ipecho.net/plain', $retval);
    26. ?>

    27. <?php require '../share/header.php'; ?>

    28. <div class="content">

    29. <div class="inner">
    30. <?php
    31. $id = array(0 => "o", 1 => "");
    32. $title = array(0 => "Operating System", 1 => "Firmware");
    33. $button = array(0 => "read_os", 1 => "read");
    34. for ($i = 0 ; $i <= 1 ; $i++) {
    35. ?>
    36. <table>
    37. <thead>
    38. <tr><th colspan="3"><?php echo $title[$i] ?> Information <button onclick="<?php echo $button[$i] ?>_ids()">Read ID</button></th>
    39. </tr>
    40. </thead>
    41. <tbody>
    42. <tr>
    43. <td style="text-align:right">Description :</td>
    44. <td style="text-align:left" id="<?php echo $id[$i] ?>id_0">Unknown</td>
    45. <td><img id="<?php echo $id[$i] ?>sid_0" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    46. </tr>
    47. <tr>
    48. <td style="text-align:right">Company :</td>
    49. <td style="text-align:left" id="<?php echo $id[$i] ?>id_1">Unknown</td>
    50. <td><img id="<?php echo $id[$i] ?>sid_1" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    51. </tr>
    52. <tr>
    53. <td style="text-align:right">Author :</td>
    54. <td style="text-align:left" id="<?php echo $id[$i] ?>id_2">Unknown</td>
    55. <td><img id="<?php echo $id[$i] ?>sid_2" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    56. </tr>
    57. <tr>
    58. <td style="text-align:right">Version :</td>
    59. <td style="text-align:left" id="<?php echo $id[$i] ?>id_3">Unknown</td>
    60. <td><img id="<?php echo $id[$i] ?>sid_3" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    61. </tr>
    62. <tr>
    63. <td style="text-align:right">Timestamp :</td>
    64. <td style="text-align:left" id="<?php echo $id[$i] ?>id_4">Unknown</td>
    65. <td><img id="<?php echo $id[$i] ?>sid_4" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    66. </tr>
    67. <tr>
    68. <td style="text-align:right">Hash :</td>
    69. <td style="text-align:left" id="<?php echo $id[$i] ?>id_5">Unknown</td>
    70. <td><img id="<?php echo $id[$i] ?>sid_5" style="vertical-align:middle" src="../share/amber.gif" title="Unknown!" alt="Missing Image!"></td>
    71. </tr>
    72. </tbody>
    73. </table>
    74. <?php
    75. }
    76. ?>
    77. </div>


    78. <div class="inner">
    79. <table>
    80. <thead>
    81. <tr><th colspan="3">Loadable Firmware</th></tr>
    82. </thead>
    83. <tbody>
    84. <?php
    85.   $dirPath = "/media/sd-mmcblk0p1/firmware";
    86.   $files = scandir($dirPath);
    87.   foreach ($files as $file) {
    88.     $filePath = $dirPath . '/' . $file;
    89.     if (is_file($filePath)) {
    90.       echo "<tr><td style=\"text-align:left\">";
    91.       echo $file;
    92.       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>";
    93.     }
    94.   }
    95. ?>
    96. </tbody>
    97. </table>
    98. </div>

    99. <div class="inner">

    100. <table>
    101. <tr><th colspan="2">System</th></tr>
    102. <tr><td>Hostname</td>
    103. <td><?php echo $sys_host ?></td>
    104. </tr><tr><td>Time</td><td><?php echo $sys_time ?></td></tr>
    105. <tr><td>Uptime</td><td><span id="uptime_text"><?php echo $sys_up ?></span> seconds <button onclick="get_uptime()">Refresh</button> Auto :
    106. <select id="uptime" onchange="uptime();">
    107.   <option value="0">Off</option>
    108.   <option value="1">1s</option>
    109.   <option value="5">5s</option>
    110. </select>
    111. </td></tr>
    112. </table>

    113. <table>
    114. <tr><th colspan="2">CPU</th></tr>
    115. <tr><td>Model</td>
    116. <td><?php echo $cpu_model ?></td></tr>
    117. <tr><td>Cores</td><td><?php echo $cpu_cores ?></td></tr>
    118. <tr><td>Load</td><td><?php echo $sys_load ?></td></tr>
    119. </table>

    120. <table>
    121. <tr><th colspan="2">Memory</th></tr>
    122. <tr><td>Total</td><td><?php echo $mem_total ?> Mb</td></tr>
    123. <tr><td>Used</td><td><?php echo $mem_used ?> Mb</td></tr>
    124. <tr><td>Free</td><td><?php echo $mem_free ?> Mb</td></tr>
    125. </table>

    126. <table>
    127. <tr><th colspan="2">Network</th></tr>
    128. <tr><td>MAC Address</td><td><?php echo $net_mac ?></td></tr>
    129. <tr><td>Internal IP</td><td><?php echo $net_ip_loc ?></td></tr>
    130. <tr><td>External IP</td><td><?php echo $net_ip_ext ?></td></tr>
    131. </table>

    132. </div>

    133. </div>

    134. <?php require '../share/footer.php'; ?>

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0: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

    1. //
    2. // File .......... script.js
    3. // Author ........ Steve Haywood
    4. // Website ....... http://www.spacewire.co.uk
    5. // Project ....... SpaceWire UK Tutorial
    6. // Version ....... 1.7
    7. // Conception .... 28 February 2024
    8. // Standard ...... ECMA-262
    9. // Description ...
    10. //   Global Javascript for use in all webpages.
    11. //

    12. // Constants
    13. const c_axi_identification = 0x40000000;
    14. const c_id_description     = 0x0000;
    15. const c_id_company         = 0x0080;
    16. const c_id_author          = 0x00C0;
    17. const c_id_version         = 0x0100;
    18. const c_id_timestamp       = 0x0120;
    19. const c_id_hash            = 0x0140;

    20. const c_axi_gpio_zed       = 0x40010000;
    21. const c_gpio_leds          = 0x0000;
    22. const c_gpio_switches      = 0x0008;
    23. const c_gpio_buttons       = 0x0010;

    24. // Null function
    25. function nullfunc(respText) {
    26. }

    27. //
    28. // Javascript wrapper for call to bitbash CGI
    29. // cmd .... "peek", "poke", "set", "clear", "toggle"
    30. // addr ... 0x40010000 (for example, address to access)
    31. // data ... 0x000000FF (for example, data to write or mask to apply, not used for peek)
    32. // func ... Javascript function to call upto success
    33. //
    34. function bitbash(cmd, addr, data, func) {
    35.   var url = "/cgi-bin/bitbash?" + cmd + "&" + addr;
    36.   if (cmd != "peek") {
    37.     url = url + "&" + data;
    38.   }
    39.   if (window.XMLHttpRequest) {
    40.     var ajaxReq = new XMLHttpRequest();
    41.     ajaxReq.onreadystatechange = function() {
    42.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    43.         var respText = ajaxReq.responseText;
    44.         func(respText);
    45.       }
    46.     }
    47.     ajaxReq.open("POST", url, true);
    48.     ajaxReq.send(null);
    49.   }
    50. }

    Life too short? Grab the file :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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

    1. //
    2. // File .......... script.js
    3. // Author ........ Steve Haywood
    4. // Website ....... http://www.spacewire.co.uk
    5. // Project ....... SpaceWire UK Tutorial
    6. // Version ....... 1.7
    7. // Conception .... 17 January 2022
    8. // Standard ...... ECMA-262
    9. // Description ...
    10. //   Javascript for System Information & Firmware Load.
    11. //

    12. // Interval between Load Firmware & Read Firmware ID's
    13. var timer_fw;

    14. // Interval between PetaLinux uptime reads
    15. var timer_uptime;

    16. // Load PL Firmware
    17. function loadfirmware(filename) {
    18.   var url = "/cgi-bin/loadfirmware?" + filename;
    19.   if (window.XMLHttpRequest) {
    20.     var ajaxReq = new XMLHttpRequest();
    21.     ajaxReq.onreadystatechange = function() {
    22.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    23.         var respText = ajaxReq.responseText;
    24.         var img_obj = document.getElementById("fm_" + filename);
    25.         // Unique number is added to image to avoid caching issues on separate animations
    26.         const now = Date.now();
    27.         if (respText.substr(0,6) == "Error:") {
    28.           img_obj.src = "../share/red.gif?" + now;
    29.           img_obj.title = "Last loadfirmware failed : " + respText.substr(7);
    30.         } else {
    31.           img_obj.src = "../share/green.gif?" + now;
    32.           img_obj.title = "Last loadfirmware successful";
    33.         }
    34.         clearInterval(timer_fw);
    35.         timer_fw = setInterval(read_ids(), 1000);
    36.       }
    37.     }
    38.     ajaxReq.open("POST", url, true);
    39.     ajaxReq.send(null);
    40.   }
    41. }

    42. // Download OS information file & display result
    43. async function read_os_ids() {
    44.   let response = await fetch("/project.txt?" + Date.now());
    45.   if (response.status == 200) {
    46.     let ids = await response.text();
    47.     fields = ids.split(/\r?\n/);
    48.   }
    49.   for (var i = 0; i < 6; i++) {
    50.     const now = Date.now();
    51.     const txt_obj = document.getElementById("oid_" + i);
    52.     const img_obj = document.getElementById("osid_" + i);
    53.     if (response.status == 200) {
    54.       if (i < fields.length && fields[i] != "") {
    55.         img_obj.src = "../share/green.gif?" + now;
    56.         img_obj.title = "Last file fetch successful";
    57.         txt_obj.innerHTML = fields[i];
    58.       } else {
    59.         img_obj.src = "../share/red.gif?" + now;
    60.         img_obj.title = "Missing field information";
    61.         txt_obj.innerHTML = "Unknown";
    62.       }
    63.     } else {
    64.       img_obj.src = "../share/red.gif?" + now;
    65.       img_obj.title = "Last file fetch failed";
    66.       txt_obj.innerHTML = "Unknown";
    67.     }
    68.   }
    69. }

    70. // Peek all strings
    71. function read_ids() {
    72.   read_id(c_id_description, 0);
    73.   read_id(c_id_company, 1);
    74.   read_id(c_id_author, 2);
    75.   read_id(c_id_version, 3);
    76.   read_id(c_id_timestamp, 4);
    77.   read_id(c_id_hash, 5);
    78. }

    79. // Peek string & display result
    80. function read_id(offset, reg) {
    81.   var url = "/cgi-bin/peekstring?" + c_axi_identification + "&" + 4096 + "&" + offset + "&" + 128;
    82.   if (window.XMLHttpRequest) {
    83.     var ajaxReq = new XMLHttpRequest();
    84.     ajaxReq.onreadystatechange = function() {
    85.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    86.         var respText = ajaxReq.responseText;
    87.         var img_obj = document.getElementById("sid_" + reg);
    88.         // Unique number is added to image to avoid caching issues on separate animations
    89.         const now = Date.now();
    90.         if (respText.substr(0,6) == "Error:") {
    91.           img_obj.src = "../share/red.gif?" + now;
    92.           img_obj.title = "Last peekstring failed : " + respText.substr(7);
    93.         } else {
    94.           const now = Date.now();
    95.           img_obj.src = "../share/green.gif?" + now;
    96.           img_obj.title = "Last peekstring successful";
    97.           document.getElementById("id_" + reg).innerHTML = respText;
    98.         }
    99.       }
    100.     }
    101.     ajaxReq.open("POST", url, true);
    102.     ajaxReq.send(null);
    103.   }
    104. }

    105. // Get uptime
    106. function get_uptime() {
    107.   var url = "/cgi-bin/uptime.cgi";
    108.   if (window.XMLHttpRequest) {
    109.     var ajaxReq = new XMLHttpRequest();
    110.     ajaxReq.onreadystatechange = function() {
    111.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    112.         var respText = ajaxReq.responseText;
    113.         var txtObj = document.getElementById("uptime_text");
    114.         if (txtObj) {
    115.           txtObj.innerHTML = respText;
    116.         }
    117.       }
    118.     }
    119.     ajaxReq.open("POST", url, true);
    120.     ajaxReq.send(null);
    121.   }
    122. }

    123. // Update uptime timer
    124. function uptime() {
    125.   clearInterval(timer_uptime);
    126.   var uptime = document.getElementById("uptime");
    127.   if (uptime) {
    128.     var interval = uptime.value;
    129.     if (interval > 0) {
    130.       timer_uptime = setInterval("get_uptime()", 1000 * interval);
    131.     }
    132.   }
    133. }

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0: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

    1. <?php
    2. //
    3. // File .......... index.php
    4. // Author ........ Steve Haywood
    5. // Website ....... http://www.spacewire.co.uk
    6. // Project ....... SpaceWire UK Tutorial
    7. // Version ....... 1.2
    8. // Conception .... 8 August 2023
    9. // Standard ...... PHP 7
    10. // Description ...
    11. //   Webpage for Peek & Poke Addresses.
    12. //
    13. ?>

    14. <?php require '../share/header.php'; ?>

    15. <div class="content">

    16. <table id="registers">
    17. <tr>
    18. <th>Address</th>
    19. <th>Hex</th>
    20. <th>Peek Value</th>
    21. <th>Sel</th>
    22. <th>Peek</th>
    23. <th>Status</th>
    24. <th>Copy</th>
    25. <th colspan="2">Poke Value</th>
    26. <th>Sel</th>
    27. <th>Poke</th>
    28. <th>Status</th>
    29. <th>Description</th>
    30. </tr>
    31. </table>
    32. <br><br>
    33. <input title="Add new row to end of address table" type="button" value="Add" onclick="add_row()">
    34. <select title="Set type of row to add to address table" id="type">
    35.   <option value="0">Register</option>
    36.   <option value="1">Section</option>
    37. </select>
    38. <input title="Remove last address from table" type="button" value="Remove" onclick="rem_register()">
    39. <input title="Peek all selected addresses in table" type="button" value="Peek All" onclick="peek_all()">
    40. <input title="Copy all table peek values into poke values" type="button" value="Copy All" onclick="copy_all()">
    41. <input title="Poke all selected addresses in table" type="button" value="Poke All" onclick="poke_all()">
    42. Peek Refresh :
    43. <select title="Set timer interval for automatic peek of table addresses" id="timer" onchange="timer()">
    44.   <option value="0">Off</option>
    45.   <option value="1">1s</option>
    46.   <option value="5">5s</option>
    47. </select>
    48. Configuration :
    49. <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>
    50. <input title="Read configuration file into table" type="file" id="load_config">

    51. </div>

    52. <?php require '../share/footer.php'; ?>

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0: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

    1. //
    2. // File .......... script.js
    3. // Author ........ Steve Haywood
    4. // Website ....... http://www.spacewire.co.uk
    5. // Project ....... SpaceWire UK Tutorial
    6. // Version ....... 1.7
    7. // Conception .... 17 January 2022
    8. // Standard ...... ECMA-262
    9. // Description ...
    10. //   Javascript Peek & Poke Addresses.
    11. //

    12. // Constants
    13. const c_uns = 0; // Unsigned string
    14. const c_hex = 1; // Hexadecimal string

    15. // Requests
    16. var timer_peek_all;

    17. // Update peek_all timer
    18. function timer() {
    19.   clearInterval(timer_peek_all);
    20.   var timer = document.getElementById("timer");
    21.   interval = timer.value;
    22.   if (interval > 0) {
    23.     timer_peek_all = setInterval("peek_all()", 1000 * interval);
    24.   }
    25. }

    26. // Convert unsigned/hexadecimal number string into a specified number string.
    27. function fmtUnsignedLong(string, format) {
    28.   var hexStr;
    29.   const value = parseInt(string);
    30.   switch(format) {
    31.     case c_uns:
    32.       hexStr = value.toString(10);
    33.       break;
    34.     case c_hex:
    35.       hexStr = value.toString(16).toUpperCase();
    36.       hexStr = "0x" + "00000000".substr(0, 8 - hexStr.length) + hexStr;
    37.       break;
    38.     default:
    39.       break;
    40.   }
    41.   return hexStr;
    42. }

    43. // Call fmtUnsignedLong with type setting from row in peek/poke address table.
    44. function fmtUnsignedLongReg(reg, value) {
    45.   const disp_obj = document.getElementById("display_" + reg);
    46.   var hexStr = "0xNaN";
    47.   if (disp_obj) {
    48.     const format = (disp_obj.checked) ? (c_hex) : (c_uns);
    49.     hexStr = fmtUnsignedLong(value, format);
    50.   }
    51.   return hexStr;
    52. }

    53. // Copy peek to poke & update any associated widgets
    54. function copy(reg) {
    55.   const peek_obj = document.getElementById("peek_" + reg);
    56.   if (peek_obj) {
    57.     const poke_obj = document.getElementById("poke_" + reg);
    58.     if (poke_obj) {
    59.       poke_obj.value = peek_obj.value;
    60.     }
    61.     const range_obj = document.getElementById("poke_range_" + reg);
    62.     if (range_obj) {
    63.       range_obj.value = parseInt(peek_obj.value);
    64.     }
    65.     const select_obj = document.getElementById("poke_select_" + reg);
    66.     if (select_obj) {
    67.       select_obj.value = peek_obj.value;
    68.     }
    69.   }
    70. }

    71. // Copy selected peek to poke in section
    72. function copy_section(row) {
    73.   var obj_sel;
    74.   do {
    75.     row++;
    76.     obj_sel = document.getElementById("peek_sel_" + row);
    77.     if (obj_sel) {
    78.       if (obj_sel.checked) {
    79.         copy(row);
    80.       }
    81.     }
    82.   } while (obj_sel);
    83. }

    84. // Copy all peek to poke
    85. function copy_all() {
    86.   var table = document.getElementById("registers");
    87.   var rows = table.rows.length - 1;
    88.   for (var index = 0; index < rows; index++) {
    89.     copy(index);
    90.   }
    91. }

    92. // Peek address & display result
    93. function peek(reg) {
    94.   var url = "/cgi-bin/bitbash?" + "peek" + "&" + document.getElementById("addr_" + reg).value;
    95.   if (window.XMLHttpRequest) {
    96.     var ajaxReq = new XMLHttpRequest();
    97.     ajaxReq.onreadystatechange = function() {
    98.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    99.         var respText = ajaxReq.responseText;
    100.         var img_obj = document.getElementById("speek_" + reg);
    101.         // Unique number is added to image to avoid caching issues on separate animations
    102.         const now = Date.now();
    103.         if (respText.substr(0,6) == "Error:") {
    104.           img_obj.src = "../share/red.gif?" + now;
    105.           img_obj.title = "Last peek failed : " + respText.substr(7);
    106.         } else {
    107.           img_obj.src = "../share/green.gif?" + now;
    108.           img_obj.title = "Last peek successful";
    109.           document.getElementById("peek_" + reg).value = fmtUnsignedLongReg(reg, respText);
    110.         }
    111.       }
    112.     }
    113.     ajaxReq.open("POST", url, true);
    114.     ajaxReq.send(null);
    115.   }
    116. }

    117. // Peek all selected addresses in section
    118. function peek_section(row) {
    119.   var obj_sel;
    120.   do {
    121.     row++;
    122.     obj_sel = document.getElementById("peek_sel_" + row);
    123.     if (obj_sel) {
    124.       if (obj_sel.checked) {
    125.         peek(row);
    126.       }
    127.     }
    128.   } while (obj_sel);
    129. }

    130. // Peek all selected addresses in table
    131. function peek_all() {
    132.   var table = document.getElementById("registers");
    133.   var rows = table.rows.length - 1;
    134.   for (var index = 0; index < rows; index++) {
    135.     const obj_sel = document.getElementById("peek_sel_" + index);
    136.     if (obj_sel) {
    137.       if (obj_sel.checked) {
    138.         peek(index);
    139.       }
    140.     }
    141.   }
    142. }

    143. function poke(reg) {
    144.   var url = "/cgi-bin/bitbash?" + "poke" + "&" + document.getElementById("addr_" + reg).value + "&" + document.getElementById("poke_" + reg).value;
    145.   if (window.XMLHttpRequest) {
    146.     var ajaxReq = new XMLHttpRequest();
    147.     ajaxReq.onreadystatechange = function() {
    148.       if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
    149.         var respText = ajaxReq.responseText;
    150.         var img_obj = document.getElementById("spoke_" + reg);
    151.         // Unique number is added to image to avoid caching issues on separate animations
    152.         const now = Date.now();
    153.         if (respText.substr(0,6) == "Error:") {
    154.           img_obj.src = "../share/red.gif?" + now;
    155.           img_obj.title = "Last poke failed : " + respText.substr(7);
    156.         } else {
    157.           img_obj.src = "../share/green.gif?" + now;
    158.           img_obj.title = "Last poke successful";
    159.         }
    160.       }
    161.     }
    162.     ajaxReq.open("POST", url, true);
    163.     ajaxReq.send(null);
    164.   }
    165. }

    166. // Poke value from input widget & update any associated widgets
    167. function poke_widget(reg) {
    168.   const poke_obj = document.getElementById("poke_" + reg);
    169.   if (poke_obj) {
    170.     const range_obj = document.getElementById("poke_range_" + reg);
    171.     if (range_obj) {
    172.       range_obj.value = parseInt(poke_obj.value);
    173.     }
    174.     const select_obj = document.getElementById("poke_select_" + reg);
    175.     if (select_obj) {
    176.       select_obj.value = poke_obj.value;
    177.     }
    178.   }
    179.   poke(reg);
    180. }

    181. // Poke value on Enter key from within input widget
    182. // Holding down Enter keeps on poking which is not desired - needs improvement!
    183. function poke_key(reg) {
    184.   if (event.key === 'Enter') {
    185.     poke_widget(reg);
    186.   }
    187. }

    188. // Update radix for peek & poke input widgets
    189. function update_radix(reg) {
    190.   const poke_obj = document.getElementById("poke_" + reg);
    191.   if (poke_obj) {
    192.     poke_obj.value = fmtUnsignedLongReg(reg, poke_obj.value);
    193.   }
    194.   const peek_obj = document.getElementById("peek_" + reg);
    195.   if (peek_obj) {
    196.     peek_obj.value = fmtUnsignedLongReg(reg, peek_obj.value);
    197.   }
    198. }

    199. // Poke value from range widget & update any associated widgets
    200. function poke_range(reg) {
    201.   const range_obj = document.getElementById("poke_range_" + reg);
    202.   if (range_obj) {
    203.     const poke_obj = document.getElementById("poke_" + reg);
    204.     if (poke_obj) {
    205.       poke_obj.value = fmtUnsignedLongReg(reg, range_obj.value);
    206.     }
    207.   }
    208.   poke(reg);
    209. }

    210. // Poke value from select widget & update any associated widgets
    211. function poke_select(reg) {
    212.   const select_obj = document.getElementById("poke_select_" + reg);
    213.   if (select_obj) {
    214.     const poke_obj = document.getElementById("poke_" + reg);
    215.     if (poke_obj) {
    216.       poke_obj.value = fmtUnsignedLongReg(reg, select_obj.value);
    217.     }
    218.   }
    219.   poke(reg);
    220. }

    221. // Poke all selected addresses in section
    222. function poke_section(row) {
    223.   var obj_sel;
    224.   do {
    225.     row++;
    226.     obj_sel = document.getElementById("poke_sel_" + row);
    227.     if (obj_sel) {
    228.       if (obj_sel.checked) {
    229.         poke_widget(row);
    230.       }
    231.     }
    232.   } while (obj_sel);
    233. }

    234. // Poke all selected addresses in table
    235. function poke_all() {
    236.   var table = document.getElementById("registers");
    237.   var rows = table.rows.length - 1;
    238.   for (var index = 0; index < rows; index++) {
    239.     const obj_sel = document.getElementById("poke_sel_" + index);
    240.     if (obj_sel) {
    241.       if (obj_sel.checked) {
    242.         poke_widget(index);
    243.       }
    244.     }
    245.   }
    246. }

    247. // Add row to table
    248. function add_row() {
    249.   const obj_type = document.getElementById("type");
    250.   const row_type = obj_type.value;
    251.   switch(row_type) {
    252.     case "0":
    253.       add_register();
    254.       break;
    255.     case "1":
    256.       add_section();
    257.       break;
    258.     default:
    259.       break;
    260.   }
    261. }

    262. // Add peek/poke row to table
    263. function add_register(reg) {
    264.   const table = document.getElementById("registers");
    265.   const next = table.rows.length - 1;
    266.   var addr = c_axi_gpio_zed;
    267.   if (next > 0) {
    268.     const obj_addr = document.getElementById("addr_" + (next - 1));
    269.     if (obj_addr) {
    270.       addr = parseInt(obj_addr.value) + 4;
    271.     }
    272.   }
    273.   const fields = ["reg", addr.toString(), "true", "true", "0x00000000", "true", "Register @ " + fmtUnsignedLong(addr.toString(), c_hex)];
    274.   add_row_raw(fields);
    275. }

    276. // Add section row to table
    277. function add_section(reg) {
    278.   const fields = ["sec", "Section Description"];
    279.   add_row_raw(fields);
    280. }

    281. // Add row to table
    282. function add_row_raw(fields) {
    283.   const table = document.getElementById("registers");
    284.   const next = table.rows.length - 1;
    285.   const row = table.insertRow(-1);
    286.   var newcell;

    287.   // Separate out fields
    288.   var type            = fields[0];
    289.   if (type == "sec") {
    290.     var description   = fields[1];
    291.   } else {
    292.     var address       = fields[1];
    293.     var display_type  = fields[2];
    294.     var peek_select   = fields[3];
    295.     var options       = fields[4].split("#");
    296.     var poke_select   = fields[5];
    297.     var description   = fields[6];
    298.     // Decode fields
    299.     var disp_checked  = (display_type == "true") ? ("checked") : ("");
    300.     var format        = (display_type == "true") ? (c_hex) : (c_uns);
    301.     var peek_checked  = (peek_select == "true") ? ("checked") : ("");
    302.     var poke_checked  = (poke_select == "true") ? ("checked") : ("");
    303.   }

    304.   newcell = row.insertCell(-1);
    305.   if (type == "sec") {
    306.     newcell.colSpan = "4";
    307.     newcell.innerHTML = "--- Section ---";
    308.   } else {
    309.     newcell.innerHTML = '<input title="Address to peek/poke" type="text" id="addr_' + next + '" value="' + fmtUnsignedLong(address, c_hex) + '" size="10">';

    310.     newcell = row.insertCell(-1);
    311.     newcell.innerHTML = '<input title="Select peak/poke display type (unsigned/hexadecimal)" type="checkbox" id="display_' + next + '" ' + disp_checked + ' onchange="update_radix(' + next + ')">';

    312.     newcell = row.insertCell(-1);
    313.     newcell.innerHTML = '<input title="Value peeked at address" type="text" id="peek_' + next + '" value="'+ fmtUnsignedLong("0x0", format) +'" size="10" readonly="readonly">';

    314.     newcell = row.insertCell(-1);
    315.     newcell.innerHTML = '<input title="Select address for peeking" type="checkbox" id="peek_sel_' + next + '" ' + peek_checked + '>';
    316.   }

    317.   newcell = row.insertCell(-1);
    318.   if (type == "sec") {
    319.     newcell.innerHTML = '<input title="Peek all selected addresses in section" type="submit" value="Peek" onclick="peek_section(' + next + ')">';
    320.   } else {
    321.     newcell.innerHTML = '<input title="Peek address" type="submit" value="Peek" onclick="peek(' + next + ')">';
    322.   }

    323.   newcell = row.insertCell(-1);
    324.   if (type == "sec") {
    325.     newcell.innerHTML = '<img style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
    326.   } else {
    327.     newcell.innerHTML = '<img title="Peek status" id="speek_' + next + '" style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';
    328.   }

    329.   newcell = row.insertCell(-1);
    330.   if (type == "sec") {
    331.     newcell.innerHTML = '<input title="Copy all selected peek values into poke values in section" type="submit" value=">>" onclick="copy_section(' + next + ')">';
    332.   } else {
    333.     newcell.innerHTML = '<input title="Copy peek value into poke value" type="submit" value=">>" onclick="copy(' + next + ')">';
    334.   }

    335.   newcell = row.insertCell(-1);
    336.   switch (type) {
    337.     case "sec":
    338.       newcell.colSpan = "3";
    339.       newcell.innerHTML = "--- Section ---";
    340.       break;
    341.     case "reg":
    342.       const poke_value    = fields[4];
    343.       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 + ')">';
    344.       newcell = row.insertCell(-1);
    345.       newcell.style.padding = "0";
    346.       break;
    347.     case "range":
    348.       var range_value   = options[0];
    349.       var range_min     = options[1];
    350.       var range_max     = options[2];
    351.       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 + ')">';
    352.       newcell = row.insertCell(-1);
    353.       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 + ')">';
    354.       break;
    355.     case "select":
    356.       var opt_str = "";
    357.       var opt_sel;
    358.       var opt_pair;
    359.       const sel = options[0];
    360.       for (var index = 1; index < options.length; index++) {
    361.         opt_pair = options[index].split("^");
    362.         opt_sel = (sel == index - 1) ? (" selected") : ("");
    363.         opt_str = opt_str.concat('<option value="', opt_pair[0], '"', opt_sel + '>', opt_pair[1], '</option>');
    364.       }
    365.       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 + ')">';
    366.       newcell = row.insertCell(-1);
    367.       newcell.innerHTML = '<select title="Value to poke at address" style="width:100%" id="poke_select_' + next + '" onchange="poke_select(' + next + ')">' + opt_str + '</select>';
    368.       break;
    369.     default:
    370.       break;
    371.   }

    372.   if (type == "sec") {
    373.     newcell = row.insertCell(-1);
    374.     newcell.innerHTML = '<input title="Poke all selected addresses in section" type="submit" value="Poke" onclick="poke_section(' + next + ')">';

    375.     newcell = row.insertCell(-1);
    376.     newcell.innerHTML = '<img style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';

    377.     newcell = row.insertCell(-1);
    378.     newcell.innerHTML = '<input title="Description of section" type="text" id="name_' + next + '" value="' + description + '" size="40">';
    379.   } else {
    380.     newcell = row.insertCell(-1);
    381.     newcell.innerHTML = '<input title="Select address for poking" type="checkbox" id="poke_sel_' + next + '" ' + poke_checked + '>';

    382.     newcell = row.insertCell(-1);
    383.     newcell.innerHTML = '<input title="Poke address" type="submit" value="Poke" onclick="poke_widget(' + next + ')">';

    384.     newcell = row.insertCell(-1);
    385.     newcell.innerHTML = '<img title="Poke status" id="spoke_' + next + '" style="vertical-align:middle" src="../share/amber.gif" alt="Missing Image!">';

    386.     newcell = row.insertCell(-1);
    387.     newcell.innerHTML = '<input title="Description of address" type="text" id="name_' + next + '" value="' + description + '" size="40">';
    388.   }
    389. }

    390. // Remove row from table
    391. function rem_register(reg) {
    392.   var table = document.getElementById("registers");
    393.   if (table.rows.length > 1) {
    394.     table.deleteRow(-1);
    395.   }
    396. }

    397. // Remove all rows from table
    398. function remove_all() {
    399.   var table = document.getElementById("registers");
    400.   var rows = table.rows.length - 1;
    401.   for (var index = 0; index < rows; index++) {
    402.     table.deleteRow(-1);
    403.   }
    404. }

    405. // Note: browser file access is made difficult for security reasons - there maybe a better way of doing file read & write.

    406. var config_file = null;

    407. // Create virtual configuration file from address table for user to download
    408. function create_config() {
    409.   var text = "";
    410.   var table = document.getElementById("registers");
    411.   const rows = table.rows.length - 1;
    412.   for (var index = 0; index < rows; index++) {
    413.     const obj_addr = document.getElementById("addr_" + index);
    414.     if (obj_addr) { // Register type
    415.       var addr = document.getElementById("addr_" + index).value;
    416.       var display = document.getElementById("display_" + index).checked;
    417.       var peek_sel = document.getElementById("peek_sel_" + index).checked;
    418.       var poke = document.getElementById("poke_" + index).value;
    419.       var poke_sel = document.getElementById("poke_sel_" + index).checked;
    420.       var name = document.getElementById("name_" + index).value;
    421.       text += "reg" + "|" + addr + "|" + display + "|" + peek_sel + "|" + poke + "|" + poke_sel + "|"+ name + "\n";
    422.     } else { // Section type
    423.       var name = document.getElementById("name_" + index).value;
    424.       text += "sec" + "|" + name + "\n";
    425.     }
    426.   }
    427.   const data = new Blob([text], {type: 'text/plain'});
    428.   if (config_file !== null) {
    429.     URL.revokeObjectURL(config_file);
    430.   }
    431.   config_file = URL.createObjectURL(data);
    432.   var link = document.getElementById('download');
    433.   link.href = config_file;
    434.   link.style.display = 'inline';
    435. }

    436. // Read configuration file and update address table
    437. function load_config(input) {
    438.   var file = input.target.files[0];
    439.   if (file) {
    440.     var reader = new FileReader();
    441.     reader.onload = function(input) {
    442.       var contents = input.target.result;
    443.       const lines = contents.split(/\r\n|\n/);
    444.       remove_all();
    445.       lines.forEach((line) => {
    446.         if (line.length > 0) {
    447.           var table = document.getElementById("registers");
    448.           var next = table.rows.length - 1;
    449.           const values = line.split("|");
    450.           switch(values[0]) {
    451.             case "reg":
    452.             case "sec":
    453.             case "range":
    454.             case "select":
    455.               add_row_raw(values);
    456.               break;
    457.             default:
    458.               alert("Error: Unrecognized table type found (" + values[0] + "), ignoring.");
    459.           }
    460.         }
    461.       });
    462.     };
    463.     reader.readAsText(file);
    464.   }
    465. }

    466. document.getElementById('load_config').addEventListener('change', load_config, false);

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0: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

    1. <?php
    2. /*
    3. *
    4. * File .......... index.php
    5. * Author ........ Steve Haywood
    6. * Website ....... http://www.spacewire.co.uk
    7. * Project ....... SpaceWire UK Tutorial
    8. * Version ....... 1.0
    9. * Conception .... 26 February 2024
    10. * Standard ...... PHP 7
    11. * Description ...
    12. *   Webpage for Interactive Zedboard.
    13. *
    14. */
    15. ?>

    16. <?php require '../share/header.php'; ?>

    17. <div class="content" style="text-align:left">

    18. <h3>Interactive Zedboard :-</h3>
    19. <ul>
    20. <li><button onclick="get_status()">Read</button> board state for LEDs, switches &amp; push buttons.</li>
    21. <li>Automatically refresh board state every <select id="status" onchange="status();">
    22.   <option value="0">Never</option>
    23.   <option value="1">100</option>
    24.   <option value="10">1000</option>
    25. </select> ms.</li>
    26. <li>Toggle LEDs on/off by clicking on them.</li>
    27. <li>Clear latched push button states by clicking on them.</li>
    28. </ul>

    29. <div class="zedboard">

    30. <img src="zedboard.png" alt="Missing Image!" usemap="#zedmap">

    31. <img id="id_led_0" class="led_0" src="led_off.png" alt="">
    32. <img id="id_led_1" class="led_1" src="led_off.png" alt="">
    33. <img id="id_led_2" class="led_2" src="led_off.png" alt="">
    34. <img id="id_led_3" class="led_3" src="led_off.png" alt="">
    35. <img id="id_led_4" class="led_4" src="led_off.png" alt="">
    36. <img id="id_led_5" class="led_5" src="led_off.png" alt="">
    37. <img id="id_led_6" class="led_6" src="led_off.png" alt="">
    38. <img id="id_led_7" class="led_7" src="led_off.png" alt="">

    39. <img id="id_sw_0" class="sw_0" src="sw_none.png" alt="">
    40. <img id="id_sw_1" class="sw_1" src="sw_none.png" alt="">
    41. <img id="id_sw_2" class="sw_2" src="sw_none.png" alt="">
    42. <img id="id_sw_3" class="sw_3" src="sw_none.png" alt="">
    43. <img id="id_sw_4" class="sw_4" src="sw_none.png" alt="">
    44. <img id="id_sw_5" class="sw_5" src="sw_none.png" alt="">
    45. <img id="id_sw_6" class="sw_6" src="sw_none.png" alt="">
    46. <img id="id_sw_7" class="sw_7" src="sw_none.png" alt="">

    47. <img id="id_btn_0" class="btn_0" src="btn_none.png" alt="">
    48. <img id="id_btn_1" class="btn_1" src="btn_none.png" alt="">
    49. <img id="id_btn_2" class="btn_2" src="btn_none.png" alt="">
    50. <img id="id_btn_3" class="btn_3" src="btn_none.png" alt="">
    51. <img id="id_btn_4" class="btn_4" src="btn_none.png" alt="">

    52. </div>

    53. <map id="zedmap" name="zedmap">

    54. <area title="Toggle LED 0 On/Off" shape="rect" coords="382,443,405,461" alt="Missing Image!" href="#" onclick="toggle_led(1)">
    55. <area title="Toggle LED 1 On/Off" shape="rect" coords="356,443,379,461" alt="Missing Image!" href="#" onclick="toggle_led(2)">
    56. <area title="Toggle LED 2 On/Off" shape="rect" coords="330,443,353,461" alt="Missing Image!" href="#" onclick="toggle_led(4)">
    57. <area title="Toggle LED 3 On/Off" shape="rect" coords="304,443,327,461" alt="Missing Image!" href="#" onclick="toggle_led(8)">
    58. <area title="Toggle LED 4 On/Off" shape="rect" coords="278,443,301,461" alt="Missing Image!" href="#" onclick="toggle_led(16)">
    59. <area title="Toggle LED 5 On/Off" shape="rect" coords="252,443,275,461" alt="Missing Image!" href="#" onclick="toggle_led(32)">
    60. <area title="Toggle LED 6 On/Off" shape="rect" coords="226,443,249,461" alt="Missing Image!" href="#" onclick="toggle_led(64)">
    61. <area title="Toggle LED 7 On/Off" shape="rect" coords="200,443,223,461" alt="Missing Image!" href="#" onclick="toggle_led(128)">

    62. <area title="Clear top button state" shape="rect" coords="486,416,519,441" alt="Missing Image!" href="#" onclick="clear_button(16)">
    63. <area title="Clear centre button state" shape="rect" coords="486,446,519,471" alt="Missing Image!" href="#" onclick="clear_button(1)">
    64. <area title="Clear bottom button state" shape="rect" coords="486,477,519,502" alt="Missing Image!" href="#" onclick="clear_button(2)">
    65. <area title="Clear left button state" shape="rect" coords="337,446,480,471" alt="Missing Image!" href="#" onclick="clear_button(4)">
    66. <area title="Clear right button state" shape="rect" coords="525,446,558,471" alt="Missing Image!" href="#" onclick="clear_button(8)">

    67. </map>

    68. </div>

    69. <?php require '../share/footer.php'; ?>

    Life too short? Grab the file :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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

    1. //
    2. // File .......... script.js
    3. // Author ........ Steve Haywood
    4. // Website ....... http://www.spacewire.co.uk
    5. // Project ....... SpaceWire UK Tutorial
    6. // Version ....... 1.7
    7. // Conception .... 28 February 2024
    8. // Standard ...... ECMA-262
    9. // Description ...
    10. //   Javascript for Interactive Zedboard.
    11. //

    12. // Interval between status reads
    13. let timer_status;

    14. // Read LEDs, switches & buttons registers and update image overlays
    15. function get_status() {
    16.   bitbash("peek", c_axi_gpio_zed + c_gpio_leds, 0, update_leds);
    17.   bitbash("peek", c_axi_gpio_zed + c_gpio_switches, 0, update_switches);
    18.   bitbash("peek", c_axi_gpio_zed + c_gpio_buttons, 0, update_buttons);
    19. }

    20. // Update status timer
    21. function status() {
    22.   clearInterval(timer_status);
    23.   var status = document.getElementById("status");
    24.   if (status) {
    25.     var interval = status.value;
    26.     if (interval > 0) {
    27.       timer_status = setInterval("get_status()", 100 * interval);
    28.     }
    29.   }
    30. }

    31. // Update Zedboard LEDs image overlays
    32. function update_leds(respText) {
    33.   for (var i = 0; i < 8; i++) {
    34.     var img_obj = document.getElementById("id_led_" + i);
    35.     if (img_obj) {
    36.       const now = Date.now();
    37.       const value = parseInt(respText);
    38.       if (value & (2**i)) {
    39.         img_obj.src = "led_on.png?" + now;
    40.       } else {
    41.         img_obj.src = "led_off.png?" + now;
    42.       }
    43.     }
    44.   }
    45. }

    46. // Update Zedboard switches overlays
    47. function update_switches(respText) {
    48.   for (var i = 0; i < 8; i++) {
    49.     var img_obj = document.getElementById("id_sw_" + i);
    50.     if (img_obj) {
    51.       const now = Date.now();
    52.       const value = parseInt(respText);
    53.       if (value & (2**i)) {
    54.         img_obj.src = "sw_on.png?" + now;
    55.       } else {
    56.         img_obj.src = "sw_off.png?" + now;
    57.       }
    58.     }
    59.   }
    60. }

    61. // Update Zedboard buttons image overlays
    62. function update_buttons(respText) {
    63.   for (var i = 0; i < 5; i++) {
    64.     var img_obj = document.getElementById("id_btn_" + i);
    65.     const value = parseInt(respText);
    66.     if (img_obj) {
    67.       const now = Date.now();
    68.       if (value & (2**i)) {
    69.         img_obj.src = "btn_on.png?" + now;
    70.       } else {
    71.         img_obj.src = "btn_off.png?" + now;
    72.       }
    73.     }
    74.   }
    75. }

    76. // Toggle LED on/off
    77. function toggle_led(mask) {
    78.   bitbash('toggle', c_axi_gpio_zed + c_gpio_leds, mask, nullfunc);
    79. }

    80. // Clear button state
    81. function clear_button(mask) {
    82.   bitbash('clear', c_axi_gpio_zed + c_gpio_buttons, mask, nullfunc);
    83. }

    Life too short? Grab the file :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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

    1. /*
    2. * File .......... style.css
    3. * Author ........ Steve Haywood
    4. * Website ....... http://www.spacewire.co.uk
    5. * Project ....... SpaceWire UK Tutorial
    6. * Version ....... 1.0
    7. * Conception .... 28 February 2024
    8. * Standard ...... CSS 3
    9. * Description ...
    10. *   Cascading Style Sheet for Interactive Zedboard.
    11. */

    12. .zedboard {
    13.   border: 0;
    14.   float: left;
    15.   position: relative;
    16. }

    17. .led_0 { position: absolute; left: 391px; top: 446px; }
    18. .led_1 { position: absolute; left: 365px; top: 446px; }
    19. .led_2 { position: absolute; left: 339px; top: 446px; }
    20. .led_3 { position: absolute; left: 313px; top: 446px; }
    21. .led_4 { position: absolute; left: 287px; top: 446px; }
    22. .led_5 { position: absolute; left: 261px; top: 446px; }
    23. .led_6 { position: absolute; left: 235px; top: 446px; }
    24. .led_7 { position: absolute; left: 209px; top: 446px; }

    25. .sw_0 { position: absolute; left: 388px; top: 475px; }
    26. .sw_1 { position: absolute; left: 361px; top: 475px; }
    27. .sw_2 { position: absolute; left: 335px; top: 475px; }
    28. .sw_3 { position: absolute; left: 308px; top: 475px; }
    29. .sw_4 { position: absolute; left: 281px; top: 475px; }
    30. .sw_5 { position: absolute; left: 255px; top: 475px; }
    31. .sw_6 { position: absolute; left: 228px; top: 475px; }
    32. .sw_7 { position: absolute; left: 201px; top: 475px; }

    33. .btn_0 { position: absolute; left: 497px; top: 454px; }
    34. .btn_1 { position: absolute; left: 497px; top: 485px; }
    35. .btn_2 { position: absolute; left: 458px; top: 454px; }
    36. .btn_3 { position: absolute; left: 536px; top: 454px; }
    37. .btn_4 { position: absolute; left: 497px; top: 424px; }

    Life too short? Grab the file :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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

    1. #
    2. # This file is the website recipe.
    3. #

    4. SUMMARY = "Simple website application"
    5. SECTION = "PETALINUX/apps"
    6. LICENSE = "MIT"
    7. LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

    8. SRC_URI = "file://project.txt"
    9. SRC_URI += "file://index.php"

    10. SRC_URI += "file://share/header.php"
    11. SRC_URI += "file://share/footer.php"
    12. SRC_URI += "file://share/style.css"
    13. SRC_URI += "file://share/script.js"
    14. SRC_URI += "file://share/amber.gif"
    15. SRC_URI += "file://share/green.gif"
    16. SRC_URI += "file://share/red.gif"

    17. SRC_URI += "file://cgi-bin/uptime.cgi"
    18. SRC_URI += "file://cgi-bin/test-cgi"
    19. SRC_URI += "file://cgi-bin/hello_world.php"
    20. SRC_URI += "file://cgi-bin/sqlite_test.php"
    21. SRC_URI += "file://cgi-bin/phpliteadmin.php"
    22. SRC_URI += "file://cgi-bin/phpliteadmin.config.php"

    23. SRC_URI += "file://home/index.php"

    24. SRC_URI += "file://system/index.php"
    25. SRC_URI += "file://system/script.js"

    26. SRC_URI += "file://peekpoke/index.php"
    27. SRC_URI += "file://peekpoke/script.js"

    28. SRC_URI += "file://zedboard/index.php"
    29. SRC_URI += "file://zedboard/script.js"
    30. SRC_URI += "file://zedboard/style.css"
    31. SRC_URI += "file://zedboard/zedboard.png"
    32. SRC_URI += "file://zedboard/sw_on.png"
    33. SRC_URI += "file://zedboard/sw_off.png"
    34. SRC_URI += "file://zedboard/sw_none.png"
    35. SRC_URI += "file://zedboard/btn_on.png"
    36. SRC_URI += "file://zedboard/btn_off.png"
    37. SRC_URI += "file://zedboard/btn_none.png"
    38. SRC_URI += "file://zedboard/led_on.png"
    39. SRC_URI += "file://zedboard/led_off.png"

    40. FILES_${PN} += "/srv/www"

    41. S = "${WORKDIR}"

    42. do_install() {
    43.   install -d ${D}/srv/www
    44.   install -m 0644 ${S}/project.txt ${D}/srv/www
    45.   install -m 0644 ${S}/index.php ${D}/srv/www

    46.   install -d ${D}/srv/www/share
    47.   install -m 0644 ${S}/share/header.php ${D}/srv/www/share
    48.   install -m 0644 ${S}/share/footer.php ${D}/srv/www/share
    49.   install -m 0644 ${S}/share/style.css ${D}/srv/www/share
    50.   install -m 0644 ${S}/share/script.js ${D}/srv/www/share
    51.   install -m 0644 ${S}/share/amber.gif ${D}/srv/www/share
    52.   install -m 0644 ${S}/share/green.gif ${D}/srv/www/share
    53.   install -m 0644 ${S}/share/red.gif ${D}/srv/www/share

    54.   install -d ${D}/srv/www/cgi-bin
    55.   install -m 0777 -d ${D}/srv/www/cgi-bin/db
    56.   install -m 0755 ${S}/cgi-bin/uptime.cgi ${D}/srv/www/cgi-bin
    57.   install -m 0755 ${S}/cgi-bin/test-cgi ${D}/srv/www/cgi-bin
    58.   install -m 0644 ${S}/cgi-bin/hello_world.php ${D}/srv/www/cgi-bin
    59.   install -m 0644 ${S}/cgi-bin/sqlite_test.php ${D}/srv/www/cgi-bin
    60.   install -m 0644 ${S}/cgi-bin/phpliteadmin.php ${D}/srv/www/cgi-bin
    61.   install -m 0644 ${S}/cgi-bin/phpliteadmin.config.php ${D}/srv/www/cgi-bin

    62.   install -d ${D}/srv/www/home
    63.   install -m 0644 ${S}/home/index.php ${D}/srv/www/home

    64.   install -d ${D}/srv/www/system
    65.   install -m 0644 ${S}/system/index.php ${D}/srv/www/system
    66.   install -m 0644 ${S}/system/script.js ${D}/srv/www/system

    67.   install -d ${D}/srv/www/peekpoke
    68.   install -m 0644 ${S}/peekpoke/index.php ${D}/srv/www/peekpoke
    69.   install -m 0644 ${S}/peekpoke/script.js ${D}/srv/www/peekpoke

    70.   install -d ${D}/srv/www/zedboard
    71.   install -m 0644 ${S}/zedboard/index.php ${D}/srv/www/zedboard
    72.   install -m 0644 ${S}/zedboard/style.css ${D}/srv/www/zedboard
    73.   install -m 0644 ${S}/zedboard/script.js ${D}/srv/www/zedboard
    74.   install -m 0644 ${S}/zedboard/zedboard.png ${D}/srv/www/zedboard
    75.   install -m 0644 ${S}/zedboard/sw_on.png ${D}/srv/www/zedboard
    76.   install -m 0644 ${S}/zedboard/sw_off.png ${D}/srv/www/zedboard
    77.   install -m 0644 ${S}/zedboard/sw_none.png ${D}/srv/www/zedboard
    78.   install -m 0644 ${S}/zedboard/btn_on.png ${D}/srv/www/zedboard
    79.   install -m 0644 ${S}/zedboard/btn_off.png ${D}/srv/www/zedboard
    80.   install -m 0644 ${S}/zedboard/btn_none.png ${D}/srv/www/zedboard
    81.   install -m 0644 ${S}/zedboard/led_on.png ${D}/srv/www/zedboard
    82.   install -m 0644 ${S}/zedboard/led_off.png ${D}/srv/www/zedboard
    83. }

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb -O os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0:os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb 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'
    Update the SQLite test PHP to use the new location.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test.php

    sqlite_test_v1.1.php

    1. <?php
    2. /*
    3. * File .......... sqlite_test.php
    4. * Author ........ Steve Haywood
    5. * Website ....... http://www.spacewire.co.uk
    6. * Project ....... SpaceWire UK Tutorial
    7. * Version ....... 1.1
    8. * Conception .... 8 August 2023
    9. * Standard ...... PHP 7
    10. * Description ...
    11. *   Simple HTML, PHP & SQLite example code that checks the basic operation of
    12. * SQLite. SQL queries used are :-
    13. *
    14. * 1. Create/open database
    15. * 1. Create table
    16. * 3. Insert row
    17. * 4. Close database
    18. */
    19. ?>

    20. <!DOCTYPE html>
    21. <html lang="en">
    22. <head>
    23. <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    24. <title>SQLite Test</title>
    25. </head>

    26. <?php

    27. class MyDB extends SQLite3 {
    28.   function __construct() {
    29.     $this->open('/media/sd-mmcblk0p1/database/test.db');
    30.   }
    31. }

    32. echo 'Creating/opening database<br>';
    33. $db = new MyDB();
    34. if ($db) {
    35.   echo 'Success';
    36. } else {
    37.   echo 'Failure : ' . $db->lastErrorMsg();
    38. }
    39. echo '<br><br>';

    40. echo 'Creating table<br>';
    41. $return = $db->exec("CREATE TABLE fruit (item VARCHAR(30) NOT NULL PRIMARY KEY UNIQUE, quantity int unsigned NOT NULL)");
    42. if ($return) {
    43.   echo 'Success';
    44. } else {
    45.   echo 'Failure : ' . $db->lastErrorMsg();
    46. }
    47. echo '<br><br>';

    48. echo 'Inserting 1st row in table<br>';
    49. $result = $db->exec("INSERT INTO fruit (item, quantity) VALUES ('Apple', '5')");
    50. if ($result) {
    51.   echo 'Success';
    52. } else {
    53.   echo 'Failure : ' . $db->lastErrorMsg();
    54. }
    55. echo '<br><br>';

    56. echo 'Inserting 2nd row in table<br>';
    57. $result = $db->exec("INSERT INTO fruit (item, quantity) VALUES ('Orange', '12')");
    58. if ($result) {
    59.   echo 'Success';
    60. } else {
    61.   echo 'Failure : ' . $db->lastErrorMsg();
    62. }
    63. echo '<br><br>';

    64. echo 'Closing database<br>';
    65. $result = $db->close();
    66. if ($result) {
    67.   echo 'Success';
    68. } else {
    69.   echo 'Failure : ' . $db->lastErrorMsg();
    70. }

    71. ?>

    72. </body>
    73. </html>

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test_v1.1.php.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/sqlite_test.php
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0: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! 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

    1. <?php
    2. /*
    3. * File .......... sqlite_test.php
    4. * Author ........ Steve Haywood
    5. * Website ....... http://www.spacewire.co.uk
    6. * Project ....... SpaceWire UK Tutorial
    7. * Version ....... 1.2
    8. * Conception .... 8 August 2023
    9. * Standard ...... PHP 7
    10. * Description ...
    11. *   Simple HTML, PHP & SQLite example code that offers some degree of error
    12. * checking on the form inputs. Reports SQL operations and success/failure
    13. * status. Demonstrates some of the common SQL queries :-
    14. *
    15. * 1. Create/open database
    16. * 2. Create table
    17. * 3. Drop table
    18. * 4. Insert row
    19. * 5. Delete row
    20. * 6. Update row
    21. * 7. Close database
    22. */
    23. ?>

    24. <!DOCTYPE html>
    25. <html lang="en">
    26. <head>
    27. <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    28. <title>Simple PHP &amp; SQLite Demonstration</title>
    29. </head>

    30. <style>
    31. table, th, td {
    32.   border: 1px solid;
    33. }

    34. .success {
    35.   color: green;
    36. }

    37. .fail {
    38.   color: red;
    39. }
    40. </style>

    41. <body>

    42. <script>
    43. function row_delete(obj_submit) {
    44.   if (obj_submit) {
    45.     const row = obj_submit.id.substr(7);
    46.     const obj_item = document.getElementById("item_" + row);
    47.     if (obj_item) {
    48.       location.replace("<?php echo $whoami; ?>?action=delete&item=" + obj_item.value);
    49.     }
    50.   }
    51. }

    52. function row_insert(obj_submit) {
    53.   if (obj_submit) {
    54.     const obj_item = document.getElementById("insert_item");
    55.     if (obj_item) {
    56.       const obj_quantity = document.getElementById("insert_quantity");
    57.       if (obj_quantity) {
    58.         location.replace("<?php echo $whoami; ?>?action=insert&item=" + obj_item.value + "&quantity=" + obj_quantity.value);
    59.       }
    60.     }
    61.   }
    62. }

    63. function row_update(obj_submit) {
    64.   if (obj_submit) {
    65.     const row = obj_submit.id.substr(7);
    66.     const obj_item = document.getElementById("item_" + row);
    67.     if (obj_item) {
    68.       const obj_quantity = document.getElementById("quantity_" + row);
    69.       if (obj_quantity) {
    70.         location.replace("<?php echo $whoami; ?>?action=update&item=" + obj_item.value + "&quantity=" + obj_quantity.value);
    71.       }
    72.     }
    73.   }
    74. }
    75. </script>

    76. <?php
    77. $whoami = basename($_SERVER['PHP_SELF']);
    78. $db_name = 'test.db';
    79. $table = "fruit";
    80. $indent = "&nbsp;&nbsp;&nbsp;... ";

    81. echo '<h3>Simple PHP &amp; SQLite Demonstration</h3>';

    82. $db_exists = file_exists('/media/sd-mmcblk0p1/database/test.db');

    83. if ($db_exists) {
    84.   echo "Attempting to open existing database<br>";
    85. } else {
    86.   echo "Attempting to create new database<br>";
    87. }

    88. class MyDB extends SQLite3 {
    89.   function __construct() {
    90.     $this->open('/media/sd-mmcblk0p1/database/test.db');
    91.   }
    92. }

    93. if ($db_exists) {
    94.   echo $indent . 'Opening of existing';
    95. } else {
    96.   echo $indent . 'Creation of new';
    97. }

    98. $db = new MyDB();
    99. if ($db) {
    100.   echo ' database <span class="success">successful</span>';
    101. } else {
    102.   echo ' database <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    103. }

    104. echo '<br><br>';

    105. echo 'Validating form submission (if any)<br>';

    106. $form_action_key = "action";
    107. $form_action_values = array("insert", "delete", "create", "drop", "update");
    108. if (isset($_GET[$form_action_key])) {
    109.   $form_action_value = $_GET[$form_action_key];
    110.   echo $indent . 'Form key (' . $form_action_key . ') <span class="success">detected</span> with value (' . $form_action_value . ') which ';
    111.   if (in_array($form_action_value, $form_action_values)) {
    112.     echo '<span class="success">exists</span>';
    113.     $process_form = 1; // Good form submission
    114.   } else {
    115.     echo '<span class="fail">does not exist</span>';
    116.     $process_form = 0; // Bad form submission
    117.   }
    118.   echo ' in expected set (';
    119.   $last_value = end(array_values($form_action_values));
    120.   foreach ($form_action_values as $value) {
    121.     echo $value;
    122.     if ($value != $last_value) {
    123.       echo (', ');
    124.     }
    125.   }
    126.   echo ')';
    127. } else {
    128.   echo $indent . 'Form key (' . $form_action_key . ') <span class="success">not detected</span>';
    129.   $process_form = 0; // No form submission
    130. }

    131. if (!$process_form) {
    132.   echo ', treating page as a non-form/normal page';
    133. }

    134. if ($process_form) {

    135.   echo '<br><br>';

    136.   switch($form_action_value) {
    137.     case "create" :

    138.       echo 'Attempting to create table<br>';
    139.       $query = "CREATE TABLE $table (
    140.         item VARCHAR(30) NOT NULL PRIMARY KEY UNIQUE,
    141.         quantity int unsigned NOT NULL
    142.       )";
    143.       $return = $db->exec($query);
    144.       if ($return) {
    145.         echo $indent . 'Table created <span class="success">successfully</span>';
    146.       } else {
    147.         echo $indent . 'Table creation <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    148.       }
    149.       break;

    150.     case "drop" :

    151.       echo 'Attempting to drop table<br>';
    152.       $query = "DROP TABLE $table";
    153.       $result = $db->exec($query);
    154.       if ($result) {
    155.         echo $indent . 'Table dropped <span class="success">successfully</span>';
    156.       } else {
    157.         echo $indent . 'Table drop <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    158.       }
    159.       break;

    160.     case 'insert' :
    161.       echo 'Attempting to insert into table<br>';
    162.       $proceed = 1;
    163.       $item = $_GET['item'];
    164.       if ((isset($item) == 0) || ($item == "")) {
    165.         echo $indent . 'Row insertion <span class="fail">failed</span> due to missing/empty item field<br>';
    166.         $proceed = 0;
    167.       }
    168.       $quantity = $_GET['quantity'];
    169.       if ((isset($quantity) == 0) || ($quantity == "")) {
    170.         echo $indent . 'Row insertion <span class="fail">failed</span> due to missing/empty quantity field<br>';
    171.         $proceed = 0;
    172.       }
    173.       if ($proceed) {
    174.         $query = "INSERT INTO $table (item, quantity) VALUES ('$item', '$quantity')";
    175.         $result = $db->exec($query);
    176.         if ($result) {
    177.           echo $indent . 'Table insertion <span class="success">successful</span>';
    178.         } else {
    179.           echo $indent . 'Table insertion <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    180.         }
    181.       }
    182.       break;

    183.     case 'delete' :
    184.       $item = $_GET['item'];
    185.       echo 'Attempting to delete row with Unique ID ' . $item . ' from table<br>';
    186.       if ($item) {
    187.         $query = "DELETE FROM $table WHERE item='$item'";
    188.         $result = $db->exec($query);
    189.         if ($result) {
    190.           echo $indent . 'Row deletion <span class="success">successful</span>';
    191.         } else {
    192.           echo $indent . 'Row deletion <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    193.         }
    194.       } else {
    195.         echo $indent . 'Row deletion <span class="fail">failed</span> due to missing/empty item field<br>';
    196.       }
    197.       break;

    198.     case 'update' :
    199.       echo 'Attempting to update row in table<br>';
    200.       $proceed = 1;
    201.       $item = $_GET['item'];
    202.       if ((isset($item) == 0) || ($item == "")) {
    203.         echo $indent . 'Row insertion <span class="fail">failed</span> due to missing/empty item field<br>';
    204.         $proceed = 0;
    205.       }
    206.       $quantity = $_GET['quantity'];
    207.       if ((isset($quantity) == 0) || ($quantity == "")) {
    208.         echo $indent . 'Row insertion <span class="fail">failed</span> due to missing/empty quantity field<br>';
    209.         $proceed = 0;
    210.       }
    211.       if ($proceed) {
    212.         $query = "UPDATE $table SET quantity = $quantity WHERE item = '$item'";
    213.         $result = $db->exec($query);
    214.         if ($result) {
    215.           echo $indent . 'Table row update <span class="success">successful</span>';
    216.         } else {
    217.           echo $indent . 'Table row update <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    218.         }
    219.       }
    220.       break;

    221.   }

    222. }
    223. ?>

    224. <br><br>

    225. <?php
    226. echo 'Attempting to select all data from table (if any)<br>';
    227. $query = "SELECT * FROM $table";
    228. $result = $db->query($query);
    229. if ($result) {
    230.   echo $indent . 'Table data selection <span class="success">successful</span>';
    231. } else {
    232.   echo $indent . 'Table data selection <span class="fail">failed</span> : ' . $db->lastErrorMsg();
    233. }

    234. if ($result) {

    235.   echo '<br><br>';
    236.   echo 'Attempting to fetch data from table (if any)';
    237.   echo '<br><br>';

    238.   echo '<table>';
    239.   echo '<tr>';
    240.   echo '<th>Item</th>';
    241.   echo '<th>Quantity</th>';
    242.   echo '<th>Action</th>';
    243.   echo '</tr>';

    244.   $pos = 0;
    245.   while($row = $result->fetchArray(SQLITE3_ASSOC))
    246.   {
    247.     echo '<tr>';

    248.     echo '<td>';
    249.     echo '<input id="item_' . $pos . '" type="text" value="'.$row['item'].'" readonly>';
    250.     echo '</td>';

    251.     echo '<td>';
    252.     echo '<input id="quantity_' . $pos . '" type="text" value="'.$row['quantity'].'">';
    253.     echo '</td>';

    254.     echo '<td>';
    255.     echo '<input id="delete_' . $pos . '" type="submit" value="Delete" onclick="row_delete(this)">';
    256.     echo '<input id="update_' . $pos . '" type="submit" value="Update" onclick="row_update(this)">';
    257.     echo '</td>';

    258.     echo '</tr>';
    259.     $pos++;
    260.   }

    261.   echo '<tr>';
    262.   echo '  <td>';
    263.   echo '  <input id="insert_item" type="text">';
    264.   echo '  </td>';
    265.   echo '  <td>';
    266.   echo '  <input id="insert_quantity" type="text">';
    267.   echo '  </td>';
    268.   echo '  <td>';
    269.   echo '  <input id="insert" type="submit" value="Insert" onclick="row_insert(this)">';
    270.   echo '  </td>';
    271.   echo '</tr>';
    272.   echo '</table>';
    273. }

    274. echo '<h3>Select option below :-</h3>';

    275. echo '<ul>';
    276. echo '<li><a href="'.$whoami.'?action=create">Create table</a></li>';
    277. echo '<li><a href="'.$whoami.'?action=drop">Drop table</a></li>';
    278. echo '</ul>';

    279. ?>

    280. <?php
    281. $db->close();
    282. ?>

    283. </body>
    284. </html>

    Life too short? Grab the file and check out the changes :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/petalinux_webserver_spruce/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
    steve@Desktop:~/projects/zedboard_linux$ git difftool v13.0: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 = '/media/sd-mmcblk0p1/database';
    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 -am "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, 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

    34. Final checks

    Rebuild PetaLinux to include the updated GIT status, package it and deploy on the Zedboard.
    steve@Desktop:~/projects/zedboard_linux$ cd os/petalinux
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-build
    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
    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
    root@petalinux:~# reboot
    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 unsavoury comments after the version numbers! Missing Image!