Skip to content

note 3 ‐ dynamic HTML pages

Marco Sillano edited this page Nov 8, 2023 · 9 revisions

Using dynamic HTML pages to create user interfaces with TuyaDAEMON (and other IoT applications) has both advantages and disadvantages.

Advantages:

  • Design Flexibility: Dynamic HTML pages offer a wide range of design and customization options. You can use CSS styles, JavaScript, and other technologies to create attractive and interactive user interfaces.

  • Multi-platform Compatibility: HTML pages are supported by almost all modern devices and browsers, making the interfaces accessible from various devices like computers, tablets, and smartphones.

Disadvantages:

  • Development Complexity: Creating dynamic HTML pages requires web programming skills in HTML, CSS, and JavaScript. While there are tools available to simplify development, it may still take time and effort to learn and implement these technologies.

  • Limited Responsiveness: The responsiveness of dynamic HTML pages depends on the network connection and server response speed. Slow or unstable connections may lead to delays in updating the user interface or receiving data from devices.


As an example, let's consider an application that manages the irrigation of a garden in a seaside villa. The garden is divided into sectors, and there are eight Tuya-compatible electro valves controlled by two devices with 4 relays each. Users can control the irrigation using SmartLife, which allows automatic scheduling or a 3-button remote utilizing Tuya-cloud automation for immediate local commands. Additionally, there's a web interface acting as the front end for the application.

This web interface provides an intuitive and interactive visualization of the irrigation status, allowing users to monitor the system's activity and make changes easily. Thanks to dynamic HTML, the interface can update in real-time without requiring a full page reload, improving the user experience.

To achieve this, the PHP code generates dynamic HTML, making development easier. JavaScript is used for refreshing the status periodically (polling) to keep the interface up-to-date. Custom functions handle user actions on buttons and toggle the state of the relays.


a) HTML (static) with image placement

    <div class="main" width="1783" height="679" style="position:relative;">
  
  <!-- the main image -->
    // I used Gardena 'MyGarden' online:  https://www.gardena.com/int/garden-life/garden-planner/
    <img src="img/garden02.png" width="1783" height="679" alt=“garden map” border="0">

   <!-- builds 8 img buttons on top -->
   <img src="img/pointOFF.png" alt='A1' name='A1' id='A1' onclick='togglePoint("A1");'  data-toggle='tooltip' title='T: 35 min'  
           style='cursor: pointer; position: absolute; top: 380px; left:1370px;'  />

   <img src="img/pointOFF.png" alt='A2' name='A2' id='A2' onclick='togglePoint("A2");' data-toggle='tooltip' title='T: 18 min' 
          style='cursor: pointer; position: absolute; top: 500px; left:1300px;'  /> 
   …..  more ……
  <!— code for meteo widget —>  
  <table width="330" style="position: absolute; top:370px; left:420px;"><tr><td>
     <a class="weatherwidget-io" href="[https://forecast7.com/it/12345abcd/località/](https://forecast7.com/it/12345abcd/localit%C3%A0/)" data-icons="Climacons" data-days="3" data-theme="original" data-basecolor="rgba(255, 255, 255, 0)" data-textcolor="#f9f9f9" data-lowcolor="#b4d2fe" >here</a>
     <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src='[https://weatherwidget.io/js/widget.min. ... );</script](https://weatherwidget.io/js/widget.min.js%27;fjs.parentNode.insertBefore%28js,fjs%29;%7D%7D%28document,%27script%27,%27weatherwidget-io-js%27%29;%3C/script)>
     </td></tr></table>
 </div>

b) PHP code for dynamic creation of (a)

Actually, the previous code is not written on an HTML page but is generated by the php. This solution has some advantages, especially in the development phase. The PHP is placed in the head, or in the point of the body where the product code is needed.

<?php       // in head
  $pointdata = array (
 // note: flow rate (L/m) and surface are used to calculate the time (taget: 6 litres/m2/day)
   //  id,           x,    y,  L/m,  sur, time    (A4 not used)
     "A1" => array( 1370,  380,  8,    40,  35),
     "A2" => array( 1300,  500,  7.8,  20,  18),
     "A3" => array( 1240,   80,  7.2,  21,  20),
     "B1" => array( 1020,   80,  7.1,  15,  15),
     "B2" => array(  770,  240,  0.4,   4,  70),
     "B3" => array(  440,   80,  4,    15,  30),
     "B4" => array(  860,   80,  5.3,  19,  25));
?>
…  more …
 <!-- the main image -->
    <img src="img/garden02.png" width="1783" height="679" alt=“garden map” border="0">
<?php            // in body
// adds irrigation points to HTML, based on pointdata table:
    foreach($pointdata as $id => $data){
     echo '<img src="img/pointOFF.png" ';
     echo "alt='$id' name='$id' id='$id' onclick='togglePoint(\"$id\");' style='cursor: pointer;
       position: absolute; top: ".$data[1]."px; left:".$data[0]."px;' data-toggle='tooltip'
              title='T: ".$data[4]." min' />\n";
     }
?>
<! ---- meteo widget standard -->
—   more   …

c) Polling to refresh status

Via REST the situation of the individual relays is read from tuyastatus. Since reading all 8 together would be too heavy, a round-robin is used, reading only two at a time, in JScript (placed at the end of the HTML code). With a base interval of 1 second, a button is updated after 5 seconds.

The main function is as follows:

var auto_refresh = setInterval(readRefresh, 1021);    // refresh time quantum
var roundrobin =3;

function readRefresh(){
 // round robin data refresh to keep fast the Interval handler
getLocalTimeStamp('#nowtime');             // function(html-id)  to update data-time on top page
let res = 0;
let scr ="?";
roundrobin = (++roundrobin)%5;
switch (roundrobin){
   case 0:
// here mapping from A1…B4 to device/dp
      src = daemonREST("irrigazione anteriore","relay4");   // function(device, property, value) to send a request (GET) to tuyaDAEMON
      res = JSON.parse(src);
      if ( res.value)
         setPoint("A1", res.value === "ON");                        // function(id, bool)  to set the correct image on screen

      src = daemonREST("irrigazione anteriore","relay2");  // function(device, property, value) to send a request (GET) to tuyaDAEMON
      res = JSON.parse(src);
      if ( res.value)
         setPoint("A2", res.value === "ON");                        // function(id, bool)  to set the correct image on screen
      break;

  case 1:
…..  more ….

Some custom functions are used, here they are:

   offICO = "img/pointOFF.png";
   onICO  = "img/pointON.png";      // ico name MUST contain 'ON': used as check
//
  function setPoint(ptid, value){
	set_imgbutton(ptid, value, onICO, offICO);
}

// update the HTML, returns value: true (ON) / false (OFF)
  function set_imgbutton(id, value, on_ico, off_ico){
    var ipoint = document.getElementById(id);
    if (value){
 	 ipoint.src = on_ico;
	 return(true);
    } else {
         ipoint.src = off_ico;
	 return(false);
    }
}


// —— time functions
function makeTimeStamp(){
  // If you like different format change here: YYYY-MM-DD hh:mm:ss
  // this is also the format used by MySQL.
var s = this.getFullYear()+ "-";
  s += (this.getMonth() <9? "0"+ (this.getMonth() + 1):(this.getMonth() + 1)) + "-";
  s += (this.getDate() <10? "0"+ this.getDate():this.getDate()) + ' ';
  s += (this.getHours() <10? "0"+ this.getHours():this.getHours()) + ":";
  s += (this.getMinutes() <10? "0"+ this.getMinutes():this.getMinutes()) + ":";
  s += (this.getSeconds() <10? "0"+ this.getSeconds():this.getSeconds());
  return s;
  }

Date.prototype.getTimeStamp = makeTimeStamp;

function getLocalTimeStamp(where){
// use date() to get data and time from local PC,
// then format timeStamp and set it in #where (top right)
    d = new Date();     //Create Date object.
    $(where).html(d.getTimeStamp());
}

d) tuyaDAEMON REST functions

 <?php echo  " daemonURL= 'http://$nrserver'; \n"; ?>  //   $nrserver defined in 'interface.php'.

// to send GET, SET, SCHEMA requests to tuyaDAEMON 
// To have a fast REST:
//   - GET returns the last value in 'tuyastatus' (can be also 'none’)
//   - SET returns always 'sent'. To verify result you must do a GET
//   - SCHEMA returns the last values in 'tuyastatus'
// If value is an object, must be JSON stringified
function daemonREST(device, property = null, value = null) {
    theURL = daemonURL + '/tuyaDAEMON?device=' + encodeURIComponent(device) + ((property)? (('&property=' +      encodeURIComponent(property)) + ((value)? ('&value=' + encodeURIComponent(value)):'')) : '');
    return (httpGet(theURL));
 }

 function httpGet(url) {
 // low level function, used by daemonREST
      let xhr = new XMLHttpRequest();
       xhr.open('GET', url, false);  // fast synchronous implementation
       try {
		xhr.send();
        if (xhr.status != 200) {
           alert(`Error ${xhr.status}: ${xhr.statusText}`);
           } else {
//	     alert('Rquest OK: ' + url);
	       return(xhr.response); }
       } catch(err) {  alert('Request failed: ' + err);}
	}

e) User actions on buttons

This last feature remains, which generates a toggle of the relay status. The main function is as follows:

function togglePoint(ptid){
  // toggle ON/OFF single watering point (used as onclick function)
  // rule: only one relay ON => mode ‘interlock’ for any  device
  // plus code to set off the other device
   switch (ptid) {
      // here mapping from A1…B4 to device/dp
      case "A1":
          if (toggle_imgbutton(ptid, onICO, offICO)) {                                  // function toggle for image
         	daemonREST("irrigazione anteriore","relay4", true); 
          	daemonREST("irrigazione laterale”,”relay all", false); 
                  }
          else {
          	daemonREST("irrigazione anteriore","relay4", "OFF");
              }
          break;
    case "A2":
      … more ….
    }.

function toggle_imgbutton(id, on_ico, off_ico){
  var ipoint = document.getElementById(id);
  nextvalue = !ipoint.src.includes("ON");
  set_imgbutton(id, nextvalue, on_ico, off_ico);
  return(nextvalue);
  }

Please note that this is a simplified explanation, and specific technical details have been omitted for clarity. The full code is in wwwdaemon.zip. If you have any questions or need further assistance, feel free to ask.

Conclusion

I use a Bootstrap admin template (free to use) which guarantees me a homogeneous look, pages adaptable to various media, and a rich library of widgets, to which you can add your own, as in this case (see https://github.com/ColorlibHQ/gentelella): for an excellent result in a short time.

Along these lines, it is also simple to obtain animation effects, by cyclically updating a series of images, and therefore to develop technical interfaces for many contexts, with HW interaction mediated by tuyaDAEMON.

NOTE: In the simplest cases, like the one presented here, the latency inevitably linked to polling is acceptable. In more complex cases or if you want more ready pages or data-driven updates, you can use the AJAX technique to update a dynamic page.

TODO: For now, I use only one dynamic page of this type, but to increase ease of use, one could group all the necessary functions in a JScript library, to be included where necessary.