feat: add schedule buttons and tweak CSS
This commit is contained in:
parent
718c10b7aa
commit
8162aa3c8e
BIN
html/images/homescreen128.png
Normal file
BIN
html/images/homescreen128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
153
html/index.html
153
html/index.html
@ -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>
|
||||||
@ -219,8 +277,8 @@ app.component('plugScheduleWidget', {
|
|||||||
|
|
||||||
</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>
|
||||||
@ -230,7 +288,15 @@ app.component('plugScheduleWidget', {
|
|||||||
<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>
|
||||||
|
<!--
|
||||||
|
<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>
|
<style>
|
||||||
* {
|
* {
|
||||||
@ -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
16
html/manifest.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
@ -26,36 +26,38 @@ def serve_file_in_dir(path):
|
|||||||
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)
|
|
||||||
def random_32_bit_int():
|
|
||||||
return random.randint(0, 0xFFF)
|
|
||||||
|
|
||||||
# Convert 24 integers to a binary stream
|
# Generate a random 12-bit integer (0 to 4095)
|
||||||
def ints_to_binary_stream(integers):
|
def random_12_bit_int():
|
||||||
binary_data = bytearray()
|
return random.randint(0, 0b111111111111)
|
||||||
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__':
|
||||||
|
Loading…
Reference in New Issue
Block a user