387 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			387 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<html>
 | 
						||
    <head>
 | 
						||
        <title>ESP32 timed Switch</title>
 | 
						||
        <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.js"></script>
 | 
						||
        <link rel="manifest" href="manifest.json" />
 | 
						||
 | 
						||
    </head>
 | 
						||
    <body>
 | 
						||
<h1>Scheduler</h1>
 | 
						||
 | 
						||
                <script>
 | 
						||
 | 
						||
class Schedule {
 | 
						||
 | 
						||
  constructor(scheduleArray) {
 | 
						||
 | 
						||
    if (scheduleArray && scheduleArray.length === 24) {
 | 
						||
      // Copy the array to prevent direct mutation
 | 
						||
      this.schedule = scheduleArray.slice();
 | 
						||
    } else {
 | 
						||
      // Initialize with all hours turned off if no valid array is provided
 | 
						||
      this.schedule = new Array(24).fill(0x000);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // Load a new schedule from an array of 24 integers
 | 
						||
  loadFromArrayBuffer = function(buffer) {
 | 
						||
    // Load from binary data
 | 
						||
    var view = new DataView(buffer);
 | 
						||
    for (var i = 0; i < 24; i++) {
 | 
						||
      this.schedule[i] = view.getUint16(i * 2);
 | 
						||
    }
 | 
						||
  };
 | 
						||
 | 
						||
  // Update a single 5-minute segment for a single hour
 | 
						||
  updateSegment(hour, segment, state) {
 | 
						||
    if (hour < 0 || hour >= 24) {
 | 
						||
      throw new Error('Hour must be between 0 and 23.');
 | 
						||
    }
 | 
						||
    if (segment < 0 || segment >= 12) {
 | 
						||
      throw new Error('Segment must be between 0 and 11.');
 | 
						||
    }
 | 
						||
 | 
						||
    // Calculate the bit position from the segment (0 for first segment, 11 for last)
 | 
						||
    const bitPosition = 11 - segment;
 | 
						||
    if (state) {
 | 
						||
      // Turn the segment on by setting the bit at the bit position
 | 
						||
      this.schedule[hour] |= (1 << bitPosition);
 | 
						||
    } else {
 | 
						||
      // Turn the segment off by clearing the bit at the bit position
 | 
						||
      this.schedule[hour] &= ~(1 << bitPosition);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  serializeForDisplay() {
 | 
						||
    // Converts the schedule array into a display format with the appropriate class for coloring
 | 
						||
    return this.schedule.map(hour => {
 | 
						||
      // All segments on
 | 
						||
      if (hour === 0xFFF) {
 | 
						||
        return { displayValue: ' ', class: 'on' };
 | 
						||
      // All segments off
 | 
						||
      } else if (hour === 0x00) {
 | 
						||
        return { displayValue: ' ', class: 'off' };
 | 
						||
      } else {
 | 
						||
        return { displayValue: ' ', class: 'partial' };
 | 
						||
      }
 | 
						||
    });
 | 
						||
  }
 | 
						||
 | 
						||
  prepareEditMatrix() {
 | 
						||
    // Creates a 24x12 matrix for editing the schedule
 | 
						||
    const editMatrix = [];
 | 
						||
    for (let hour = 0; hour < 24; hour++) {
 | 
						||
      const hourSegments = [];
 | 
						||
      for (let segment = 0; segment < 12; segment++) {
 | 
						||
        // Check if the segment is on or off
 | 
						||
        hourSegments.push({
 | 
						||
          value: (this.schedule[hour] & (1 << segment)) !== 0 ? 1 : 0
 | 
						||
        });
 | 
						||
      }
 | 
						||
      // Reverse the array to start with segment 0 on the left
 | 
						||
      editMatrix.push(hourSegments.reverse());
 | 
						||
    }
 | 
						||
    return editMatrix;
 | 
						||
  }
 | 
						||
 | 
						||
  updateFromEditMatrix(editMatrix) {
 | 
						||
    // Update the schedule array based on the edit matrix
 | 
						||
    for (let hour = 0; hour < 24; hour++) {
 | 
						||
      let hourValue = 0;
 | 
						||
      for (let segment = 0; segment < 12; segment++) {
 | 
						||
        if (editMatrix[hour][11 - segment].value) {
 | 
						||
          hourValue |= (1 << segment);
 | 
						||
        }
 | 
						||
      }
 | 
						||
      this.schedule[hour] = hourValue;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  serializeToArrayBuffer = function() {
 | 
						||
    // Convert to binary data
 | 
						||
    var buffer = new ArrayBuffer(24 * 2);
 | 
						||
    var view = new DataView(buffer);
 | 
						||
    for (var i = 0; i < this.schedule.length; i++) {
 | 
						||
      view.setUint16(i * 2, this.schedule[i]);
 | 
						||
    }
 | 
						||
    return view;
 | 
						||
  };
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
// End of Class
 | 
						||
 | 
						||
// Start of Angular
 | 
						||
 | 
						||
var app = angular.module('plugSchedulerApp', []);
 | 
						||
app.value('api_host', window.location.host);
 | 
						||
app.factory("data", function(){
 | 
						||
    var sharedData = { api_host: window.location.host};
 | 
						||
    return {
 | 
						||
        getSharedData: function () {
 | 
						||
            return sharedData;
 | 
						||
        }
 | 
						||
    };
 | 
						||
});
 | 
						||
 | 
						||
app.controller('ApiHostController', ['$scope', 'api_host', 'data', function( $scope, api_host, data) {
 | 
						||
    var ctrl = this;
 | 
						||
    $scope.data = data.getSharedData();
 | 
						||
    window.ctrl = $scope
 | 
						||
}]);
 | 
						||
 | 
						||
app.controller('PlugScheduleController', ['$scope', '$http', 'data', function( $scope, $http, data) {
 | 
						||
  var ctrl = this;
 | 
						||
  ctrl.editMode = false;
 | 
						||
  $scope.data = data.getSharedData();
 | 
						||
 | 
						||
  ctrl.$onInit = function() {
 | 
						||
    // Initialize with a default schedule
 | 
						||
    ctrl.scheduleObj = new Schedule();
 | 
						||
    ctrl.getSchedule();
 | 
						||
  };
 | 
						||
 | 
						||
  ctrl.getSchedule = function() {
 | 
						||
    if (ctrl.plugId === undefined) {
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    // Fetch the schedule from the server for a given plug ID
 | 
						||
    var url = `http://${$scope.data.api_host}/api/schedule/${ctrl.plugId}`;
 | 
						||
    $http.get(url, { responseType: 'arraybuffer' }).then(function(response) {
 | 
						||
      ctrl.scheduleObj.loadFromArrayBuffer(response.data);
 | 
						||
      ctrl.displaySchedule = ctrl.scheduleObj.serializeForDisplay();
 | 
						||
    }).catch(function(error) {
 | 
						||
      console.error('Error fetching schedule:', error);
 | 
						||
    });
 | 
						||
  };
 | 
						||
 | 
						||
  ctrl.modifySchedule = function() {
 | 
						||
    ctrl.editMode = true;
 | 
						||
    // Prepare a matrix for editing
 | 
						||
    ctrl.editMatrix = ctrl.scheduleObj.prepareEditMatrix();
 | 
						||
  };
 | 
						||
 | 
						||
  ctrl.saveSchedule = function() {
 | 
						||
    ctrl.editMode = false;
 | 
						||
    // Convert the edit matrix back to schedule format
 | 
						||
    ctrl.scheduleObj.updateFromEditMatrix(ctrl.editMatrix);
 | 
						||
    ctrl.updateSchedule();
 | 
						||
  };
 | 
						||
 | 
						||
  // Send the updated schedule to the server
 | 
						||
  ctrl.updateSchedule = function() {
 | 
						||
    var url = `http://${$scope.data.api_host}/api/schedule/${ctrl.plugId}`;
 | 
						||
    var serializedSchedule = ctrl.scheduleObj.serializeToArrayBuffer();
 | 
						||
 | 
						||
    $http.post(url, serializedSchedule, {
 | 
						||
      headers: {
 | 
						||
        'Content-Type': 'application/octet-stream'
 | 
						||
      },
 | 
						||
      responseType: 'arraybuffer',
 | 
						||
      transformRequest: []
 | 
						||
    }).then(function(response) {
 | 
						||
      // ctrl.getSchedule();
 | 
						||
      console.log("successfully updated");
 | 
						||
      ctrl.displaySchedule = ctrl.scheduleObj.serializeForDisplay();
 | 
						||
    }, function(error) {
 | 
						||
      alert('Error updating schedule:', error);
 | 
						||
    });
 | 
						||
  };
 | 
						||
 | 
						||
  // check all segments for a specific hour
 | 
						||
  ctrl.checkAll = function(hourIndex) {
 | 
						||
    ctrl.editMatrix[hourIndex].forEach(segment => {
 | 
						||
      segment.value = 1;
 | 
						||
    });
 | 
						||
  };
 | 
						||
 | 
						||
  // uncheck all segments for a specific hour
 | 
						||
  ctrl.uncheckAll = function(hourIndex) {
 | 
						||
    ctrl.editMatrix[hourIndex].forEach(segment => {
 | 
						||
      segment.value = 0;
 | 
						||
    });
 | 
						||
  };
 | 
						||
  ctrl.invertAll = function(hourIndex) {
 | 
						||
    ctrl.editMatrix[hourIndex].forEach(segment => {
 | 
						||
      var inverted = Math.abs( segment.value - 1);
 | 
						||
      segment.value = inverted;
 | 
						||
    });
 | 
						||
  };
 | 
						||
 | 
						||
  ctrl.checkPattern = function(hourIndex, results) {
 | 
						||
      for( index = 0; index < ctrl.editMatrix[hourIndex].length; index++){
 | 
						||
        ctrl.editMatrix[hourIndex][index] = { value : results[index] };
 | 
						||
      }
 | 
						||
  };
 | 
						||
 | 
						||
  ctrl.checkHalf = function(hourIndex) {
 | 
						||
    ctrl.checkPattern( hourIndex, [1,1,1,1,1,1,0,0,0,0,0,0]);
 | 
						||
  };
 | 
						||
 | 
						||
  ctrl.checkFourth = function(hourIndex) {
 | 
						||
    ctrl.checkPattern( hourIndex, [1,1,1,0,0,0,1,1,1,0,0,0]);
 | 
						||
  };
 | 
						||
 | 
						||
  ctrl.checkSixth = function(hourIndex) {
 | 
						||
    ctrl.checkPattern( hourIndex, [1,1,0,0,1,1,0,0,1,1,0,0]);
 | 
						||
  };
 | 
						||
 | 
						||
  ctrl.checkTwelveth = function(hourIndex) {
 | 
						||
    ctrl.checkPattern( hourIndex, [1,0,1,0,1,0,1,0,1,0,1,0]);
 | 
						||
  };
 | 
						||
 | 
						||
 | 
						||
 | 
						||
}]);
 | 
						||
 | 
						||
app.component('plugScheduleWidget', {
 | 
						||
  bindings: {
 | 
						||
    plugId: '<'
 | 
						||
  },
 | 
						||
  template: `
 | 
						||
      <div class="plug-schedule-widget">
 | 
						||
        <h3>Plug {{ $ctrl.plugId }}</h3>
 | 
						||
        <div ng-if="!$ctrl.editMode">
 | 
						||
          <div ng-repeat="hour in $ctrl.displaySchedule track by $index" class="hour-cell" ng-class="hour.class">
 | 
						||
            {{ hour.displayValue }}
 | 
						||
          </div>
 | 
						||
          <button class="modify" ng-click="$ctrl.modifySchedule()">Modify</button>
 | 
						||
        </div>
 | 
						||
        <div ng-if="$ctrl.editMode">
 | 
						||
          <table>
 | 
						||
            <tr>
 | 
						||
            <td class=></td>
 | 
						||
            <td class="minutes" ng-repeat="segment in [0,1,2,3,4,5,6,7,8,9,10,11]">{{ segment * 5 }}</td>
 | 
						||
            </tr>
 | 
						||
            <tr ng-repeat="hourSegments in $ctrl.editMatrix track by $index">
 | 
						||
              <td class="hours">{{ $index }}</td>
 | 
						||
              <td class="cell" ng-repeat="segment in hourSegments track by $index">
 | 
						||
                <input type="checkbox" ng-model="segment.value" ng-true-value="1" ng-false-value="0">
 | 
						||
              </td>
 | 
						||
              <td>
 | 
						||
                <button ng-click="$ctrl.checkAll($index)">1</button>
 | 
						||
                <button ng-click="$ctrl.uncheckAll($index)">0</button>
 | 
						||
                <button ng-click="$ctrl.checkHalf($index)">½</button>
 | 
						||
                <button ng-click="$ctrl.checkFourth($index)">¼</button>
 | 
						||
                <button ng-click="$ctrl.checkSixth($index)">⅙</button>
 | 
						||
                <button ng-click="$ctrl.checkTwelveth($index)">¹⁄₁₂</button>
 | 
						||
                <button ng-click="$ctrl.invertAll($index)">∨</button>
 | 
						||
              </td>
 | 
						||
            </tr>
 | 
						||
          </table>
 | 
						||
 | 
						||
          <button ng-click="$ctrl.saveSchedule()">Save Schedule</button>
 | 
						||
        </div>
 | 
						||
      </div>
 | 
						||
`,
 | 
						||
  controller: 'PlugScheduleController'
 | 
						||
});
 | 
						||
 | 
						||
// End of Angular
 | 
						||
 | 
						||
</script>
 | 
						||
 | 
						||
<div ng-app="plugSchedulerApp" id="main_container">
 | 
						||
    <div ng-controller="PlugScheduleController">
 | 
						||
      <plug-schedule-widget plug-id="1"></plug-schedule-widget>
 | 
						||
      <plug-schedule-widget plug-id="2"></plug-schedule-widget>
 | 
						||
      <plug-schedule-widget plug-id="3"></plug-schedule-widget>
 | 
						||
      <plug-schedule-widget plug-id="4"></plug-schedule-widget>
 | 
						||
      <plug-schedule-widget plug-id="5"></plug-schedule-widget>
 | 
						||
      <plug-schedule-widget plug-id="6"></plug-schedule-widget>
 | 
						||
      <plug-schedule-widget plug-id="7"></plug-schedule-widget>
 | 
						||
      <plug-schedule-widget plug-id="8"></plug-schedule-widget>
 | 
						||
    </div>
 | 
						||
    <!--
 | 
						||
    <div  ng-controller="ApiHostController">
 | 
						||
        <h3>
 | 
						||
            Change Host
 | 
						||
            <input type="text" ng-model="data.api_host" value="{{data.api_host}}" style="margin: 10px;padding: 3px; font-size: 14pt; color: #333340; border-radius: 6px;">
 | 
						||
        </h3>
 | 
						||
    </div>
 | 
						||
    -->
 | 
						||
</div>
 | 
						||
 | 
						||
<style>
 | 
						||
* {
 | 
						||
  font-family: Calibri, Candara, Segoe, Segoe UI, Optima, Arial, sans-serif;
 | 
						||
  padding: 0px;
 | 
						||
  border-spacing: 0px;
 | 
						||
  border: transparent;
 | 
						||
  text-align: center;
 | 
						||
  color: #aaa;
 | 
						||
  font-weight: normal;
 | 
						||
  font-size: 20pt;
 | 
						||
}
 | 
						||
body {
 | 
						||
  margin: auto;
 | 
						||
  background: #333340;
 | 
						||
  }
 | 
						||
button {
 | 
						||
  background: #696983;
 | 
						||
  border-radius: 6px;
 | 
						||
  box-shadow: 1px 1px 3px rgba(0,0,0,0.6);
 | 
						||
  color: eee;
 | 
						||
  font-size: 0.8em;
 | 
						||
  margin-left: 4px;
 | 
						||
  margin-top: 16px;
 | 
						||
  padding: 14px;
 | 
						||
  vertical-align: top;
 | 
						||
}
 | 
						||
button.modify {
 | 
						||
  margin: 4px;
 | 
						||
}
 | 
						||
button.modifiers {
 | 
						||
  width: 40px;
 | 
						||
}
 | 
						||
 | 
						||
button:hover {
 | 
						||
  background: #9f9fc4;
 | 
						||
  box-shadow: inset 1px 1px 3px rgba(0,0,0,0.6);
 | 
						||
 | 
						||
}
 | 
						||
 | 
						||
.hour-cell {
 | 
						||
display: inline-block;
 | 
						||
  width: 25px;
 | 
						||
  height: 50px;
 | 
						||
  margin-top: 4px;
 | 
						||
}
 | 
						||
.minutes, .hours {
 | 
						||
  font-size: 0.6em;
 | 
						||
  color: rgba(255, 255, 255, .5);
 | 
						||
  padding: 6px;
 | 
						||
}
 | 
						||
table tr td {
 | 
						||
  padding: 3px;
 | 
						||
}
 | 
						||
tr:nth-child(2n) {
 | 
						||
  box-shadow: inset 1px 1px 4px 1px rgba(0,0,0,0.54);
 | 
						||
  background: rgba(0, 0, 0, 0.07);
 | 
						||
}
 | 
						||
tr td.cell {
 | 
						||
  border-left: 1px solid rgba(61, 61, 78, .61);
 | 
						||
}
 | 
						||
div#main_container {
 | 
						||
  margin: auto;
 | 
						||
}
 | 
						||
 | 
						||
.off {
 | 
						||
  background-color: #eee;
 | 
						||
}
 | 
						||
 | 
						||
.on {
 | 
						||
  background-color: #8e8;
 | 
						||
}
 | 
						||
 | 
						||
.partial {
 | 
						||
  background-color: #bdc;
 | 
						||
}
 | 
						||
table {
 | 
						||
  margin: auto;
 | 
						||
}
 | 
						||
</style>
 | 
						||
    </body>
 | 
						||
</html>
 |