feat: add schedule buttons and tweak CSS

This commit is contained in:
alban 2024-02-27 00:25:12 +01:00
parent 718c10b7aa
commit 8162aa3c8e
4 changed files with 177 additions and 60 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -2,6 +2,8 @@
<head> <head>
<title>ESP32 timed Switch</title> <title>ESP32 timed Switch</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.js"></script>
<link rel="manifest" href="manifest.json" />
</head> </head>
<body> <body>
<h1>Scheduler</h1> <h1>Scheduler</h1>
@ -24,7 +26,7 @@ class Schedule {
// Load from binary data // Load from binary data
var view = new DataView(buffer); var view = new DataView(buffer);
for (var i = 0; i < 24; i++) { for (var i = 0; i < 24; i++) {
this.schedule[i] = view.getUint32(i * 4); this.schedule[i] = view.getUint16(i * 2);
} }
}; };
@ -53,7 +55,7 @@ class Schedule {
return this.schedule.map(hour => { return this.schedule.map(hour => {
if (hour === 0xFFF) { // All segments on if (hour === 0xFFF) { // All segments on
return { displayValue: ' ', class: 'on' }; return { displayValue: ' ', class: 'on' };
} else if (hour === 0x000) { // All segments off } else if (hour === 0x00) { // All segments off
return { displayValue: ' ', class: 'off' }; return { displayValue: ' ', class: 'off' };
} else { } else {
return { displayValue: ' ', class: 'partial' }; return { displayValue: ' ', class: 'partial' };
@ -93,12 +95,12 @@ class Schedule {
serializeToArrayBuffer = function() { serializeToArrayBuffer = function() {
// Convert to binary data // Convert to binary data
var buffer = new ArrayBuffer(24 * 4); var buffer = new ArrayBuffer(24 * 2);
var view = new DataView(buffer); var view = new DataView(buffer);
for (var i = 0; i < this.schedule.length; i++) { for (var i = 0; i < this.schedule.length; i++) {
view.setUint32(i * 4, this.schedule[i]); view.setUint16(i * 2, this.schedule[i]);
} }
return buffer; return view;
}; };
} }
@ -108,10 +110,26 @@ class Schedule {
// Start of Angular // Start of Angular
var app = angular.module('plugSchedulerApp', []); 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('PlugScheduleController', ['$http', function($http) { 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; var ctrl = this;
ctrl.editMode = false; ctrl.editMode = false;
$scope.data = data.getSharedData();
ctrl.$onInit = function() { ctrl.$onInit = function() {
ctrl.scheduleObj = new Schedule(); // Initialize with a default schedule ctrl.scheduleObj = new Schedule(); // Initialize with a default schedule
@ -120,11 +138,11 @@ app.controller('PlugScheduleController', ['$http', function($http) {
ctrl.getSchedule = function() { ctrl.getSchedule = function() {
if (ctrl.plugId === undefined) { if (ctrl.plugId === undefined) {
throw new Error('plugId is not defined'); return;
} }
// Fetch the schedule from the server for a given plug ID // Fetch the schedule from the server for a given plug ID
var url = `/api/schedule/${ctrl.plugId}` var url = `http://${$scope.data.api_host}/api/schedule/${ctrl.plugId}`
$http.get(url, { responseType: 'arraybuffer' }).then(function(response) { $http.get(url, { responseType: 'arraybuffer' }).then(function(response) {
ctrl.scheduleObj.loadFromArrayBuffer(response.data); ctrl.scheduleObj.loadFromArrayBuffer(response.data);
ctrl.displaySchedule = ctrl.scheduleObj.serializeForDisplay(); ctrl.displaySchedule = ctrl.scheduleObj.serializeForDisplay();
@ -148,7 +166,7 @@ app.controller('PlugScheduleController', ['$http', function($http) {
// Send the updated schedule to the server // Send the updated schedule to the server
ctrl.updateSchedule = function() { ctrl.updateSchedule = function() {
var url = `/api/schedule/${ctrl.plugId}` var url = `http://${$scope.data.api_host}/api/schedule/${ctrl.plugId}`
var serializedSchedule = ctrl.scheduleObj.serializeToArrayBuffer(); var serializedSchedule = ctrl.scheduleObj.serializeToArrayBuffer();
$http.post(url, serializedSchedule, { $http.post(url, serializedSchedule, {
@ -162,11 +180,10 @@ app.controller('PlugScheduleController', ['$http', function($http) {
console.log("successfully updated"); console.log("successfully updated");
ctrl.displaySchedule = ctrl.scheduleObj.serializeForDisplay(); ctrl.displaySchedule = ctrl.scheduleObj.serializeForDisplay();
}, function(error) { }, function(error) {
console.error('Error updating schedule:', error); alert('Error updating schedule:', error);
}); });
}; };
// check all segments for a specific hour // check all segments for a specific hour
ctrl.checkAll = function(hourIndex) { ctrl.checkAll = function(hourIndex) {
ctrl.editMatrix[hourIndex].forEach(segment => { ctrl.editMatrix[hourIndex].forEach(segment => {
@ -180,6 +197,37 @@ app.controller('PlugScheduleController', ['$http', function($http) {
segment.value = 0; 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', { app.component('plugScheduleWidget', {
@ -188,22 +236,32 @@ app.component('plugScheduleWidget', {
}, },
template: ` template: `
<div class="plug-schedule-widget"> <div class="plug-schedule-widget">
<h3>Schedule for Plug {{ $ctrl.plugId }}</h3> <h3>Plug {{ $ctrl.plugId }}</h3>
<div ng-if="!$ctrl.editMode"> <div ng-if="!$ctrl.editMode">
<div ng-repeat="hour in $ctrl.displaySchedule track by $index" class="hour-cell" ng-class="hour.class"> <div ng-repeat="hour in $ctrl.displaySchedule track by $index" class="hour-cell" ng-class="hour.class">
{{ hour.displayValue }} {{ hour.displayValue }}
</div> </div>
<button ng-click="$ctrl.modifySchedule()">Modify Schedule</button> <button class="modify" ng-click="$ctrl.modifySchedule()">Modify</button>
</div> </div>
<div ng-if="$ctrl.editMode"> <div ng-if="$ctrl.editMode">
<table> <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"> <tr ng-repeat="hourSegments in $ctrl.editMatrix track by $index">
<td ng-repeat="segment in hourSegments 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"> <input type="checkbox" ng-model="segment.value" ng-true-value="1" ng-false-value="0">
</td> </td>
<td> <td>
<button ng-click="$ctrl.checkAll($index)">Check All</button> <button ng-click="$ctrl.checkAll($index)">1</button>
<button ng-click="$ctrl.uncheckAll($index)">Uncheck All</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> </td>
</tr> </tr>
</table> </table>
@ -217,21 +275,29 @@ app.component('plugScheduleWidget', {
// End of Angular // End of Angular
</script> </script>
<div ng-app="plugSchedulerApp" id="main_container">
<div ng-app="plugSchedulerApp"> <div ng-controller="PlugScheduleController">
<plug-schedule-widget plug-id="1"></plug-schedule-widget> <plug-schedule-widget plug-id="1"></plug-schedule-widget>
<plug-schedule-widget plug-id="2"></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="3"></plug-schedule-widget>
<plug-schedule-widget plug-id="4"></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="5"></plug-schedule-widget>
<plug-schedule-widget plug-id="6"></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="7"></plug-schedule-widget>
<plug-schedule-widget plug-id="8"></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> </div>
<style> <style>
* { * {
font-family: Calibri, Candara, Segoe, Segoe UI, Optima, Arial, sans-serif; font-family: Calibri, Candara, Segoe, Segoe UI, Optima, Arial, sans-serif;
@ -241,27 +307,60 @@ app.component('plugScheduleWidget', {
text-align: center; text-align: center;
color: #aaa; color: #aaa;
font-weight: normal; font-weight: normal;
font-size: 20pt;
} }
body { body {
width: 80%;
margin: auto; margin: auto;
background: #333340; background: #333340;
} }
button { button {
padding: 4px 8px;
margin-left: 5px;
vertical-align: top;
border-radius: 12px;
background: #696983; background: #696983;
border-radius: 6px;
box-shadow: 1px 1px 3px rgba(0,0,0,0.6);
color: eee; color: eee;
margin-top: 5px; 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 { .hour-cell {
display: inline-block; display: inline-block;
width: 10px; width: 25px;
height: 25px; height: 50px;
margin-top: 4px; 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 { .off {
background-color: #eee; background-color: #eee;

16
html/manifest.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "ESP32 Electrical Switch",
"short_name": "E32 Switch",
"start_url": ".",
"display": "standalone",
"background_color": "#333340",
"description": "A configuration tool for ESP32 electrical switch.",
"icons": [
{
"src": "images/touch/homescreen128.png",
"sizes": "128x128",
"type": "image/png"
}
]
}

View File

@ -16,46 +16,48 @@ static_file_dir = os.path.dirname(os.path.realpath(__file__))
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def serve_index(): def serve_index():
return send_from_directory(static_file_dir, 'index.html') return send_from_directory(static_file_dir, 'index.html')
@app.route('/<path:path>', methods=['GET']) @app.route('/<path:path>', methods=['GET'])
def serve_file_in_dir(path): def serve_file_in_dir(path):
if not os.path.isfile(os.path.join(static_file_dir, path)): if not os.path.isfile(os.path.join(static_file_dir, path)):
path = os.path.join(path, 'index.html') path = os.path.join(path, 'index.html')
print("Serving local file : {}".format(path)) print("Serving local file : {}".format(path))
return send_from_directory(static_file_dir, path) return send_from_directory(static_file_dir, path)
# Generate a random 12-bit integer (0 to 4095) # Generate a random 12-bit integer (0 to 4095)
def random_32_bit_int(): def random_12_bit_int():
return random.randint(0, 0xFFF) return random.randint(0, 0b111111111111)
# Convert 24 integers to a binary stream
def ints_to_binary_stream(integers):
binary_data = bytearray()
for integer in integers:
# Ensure integer is in 32-bit range
integer &= 0xFFF
# Convert to bytes and append
binary_data.extend(integer.to_bytes(4, byteorder='big')) # 2 bytes per integer (32-bit)
return binary_data
# Convert a binary stream to 24 integers # Convert a binary stream to 24 integers
def binary_stream_to_ints(binary_data): def binary_stream_to_ints(binary_data):
integers = [] integers = []
# Every 32-bit integer is represented by 2 bytes # Every 32-bit integer is represented by 2 bytes
for i in range(0, len(binary_data), 4): for i in range(0, len(binary_data), 2):
integers.append(int.from_bytes(binary_data[i:i+4], byteorder='big') & 0xFFF) # Extract 32-bit value integers.append(int.from_bytes(binary_data[i:i + 2], byteorder='big') & 0b111111111111) # Extract 32-bit value
return integers return integers
@app.route('/api/schedule/<int:plug_id>', methods=['GET']) @app.route('/api/schedule/<int:plug_id>', methods=['GET'])
def get_schedule(plug_id): def get_schedule(plug_id):
# Generate 24 random 32-bit integers # Generate 24 random 32-bit integers
schedule = [random_32_bit_int() for _ in range(24)] schedule = [random_12_bit_int() for _ in range(24)]
# Convert to binary stream
binary_data = ints_to_binary_stream(schedule) # Convert 24 integers to a binary stream
# Return as a binary response def ints_to_binary_stream(integers):
return Response(binary_data, content_type='application/octet-stream') for integer in integers:
# Ensure integer is in 12-bit range
integer &= 0b111111111111
# Convert to bytes and append
yield integer.to_bytes(2, byteorder='big') # 2 bytes per integer
# Return as a binary stream
return Response(ints_to_binary_stream(schedule), status=200, content_type='application/octet-stream')
@app.route('/api/schedule/<int:plug_id>', methods=['POST']) @app.route('/api/schedule/<int:plug_id>', methods=['POST'])
def update_schedule(plug_id): def update_schedule(plug_id):
@ -65,7 +67,7 @@ def update_schedule(plug_id):
new_schedule = binary_stream_to_ints(binary_data) new_schedule = binary_stream_to_ints(binary_data)
print(f"Received new schedule for plug {plug_id}: {new_schedule}") print(f"Received new schedule for plug {plug_id}: {new_schedule}")
# Respond with a success message in binary format # Respond with a success message in binary format
return Response(b'Update successful', status=200, mimetype='text/plain') return Response(b'Update successful', status=200, content_type='text/plain')
if __name__ == '__main__': if __name__ == '__main__':