esp32-timed-switch/html/index.html

387 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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" />
<meta charset="UTF-8">
</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, true);
}
};
// 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 < 24; i++) {
view.setUint8( i*2 , (this.schedule[i] & 65280) >> 8, true);
view.setUint8( i*2 + 1, this.schedule[i] & 255 , true);
}
return buffer;
};
}
// 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;
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>