wip: html
This commit is contained in:
parent
63ee301d2d
commit
f2bc769a43
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
.vscode
|
.vscode
|
||||||
|
.idea
|
||||||
|
venv
|
||||||
|
@ -8,7 +8,10 @@
|
|||||||
#include "scheduler_prefs.h"
|
#include "scheduler_prefs.h"
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
// Init const and global objects
|
// Init const and global objects
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
// PREFRENCES
|
// PREFRENCES
|
||||||
Preferences preferences;
|
Preferences preferences;
|
||||||
@ -63,11 +66,15 @@ unsigned int pin_list[2] = {
|
|||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
WiFiClient http_client;
|
WiFiClient http_client;
|
||||||
WiFiServer http_server(80);
|
WiFiServer http_server(3000);
|
||||||
String request;
|
String request;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
// Local functions
|
// Local functions
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
// Time helper
|
// Time helper
|
||||||
uint32_t get_timestamp( uint8_t hour, uint8_t minute, uint8_t second){
|
uint32_t get_timestamp( uint8_t hour, uint8_t minute, uint8_t second){
|
||||||
@ -179,6 +186,22 @@ void run_scheduler(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// HTTP
|
||||||
|
void deserializeSchedule(String content, int schedule[24]) {
|
||||||
|
client.readBytes((char*)schedule, 24 * sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sendSchedule(WiFiClient &client, int schedule[24]) {
|
||||||
|
client.write((const uint8_t*)schedule, 24 * sizeof(int));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------
|
||||||
|
// ARDUINO Functions
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
void setup(){
|
void setup(){
|
||||||
|
|
||||||
// HARDWARE
|
// HARDWARE
|
||||||
@ -272,27 +295,56 @@ void loop(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (http_client.connected()) {
|
String request = client.readStringUntil('\r');
|
||||||
if (http_client.available()) {
|
client.flush();
|
||||||
char c = http_client.read();
|
|
||||||
|
|
||||||
request += c;
|
// GET request
|
||||||
|
if (request.indexOf("GET /api/schedule/") >= 0) {
|
||||||
|
int apiIndex = request.indexOf("/api/schedule/");
|
||||||
|
int startIdIndex = apiIndex + strlen("/api/schedule/");
|
||||||
|
int endIdIndex = request.indexOf(" ", startIdIndex);
|
||||||
|
String plugIdStr = request.substring(startIdIndex, endIdIndex);
|
||||||
|
int plugId = plugIdStr.toInt() - 1; // Adjust for 0 index
|
||||||
|
|
||||||
if (c == '\n') {
|
if (plugId >= 0 && plugId < 8) {
|
||||||
String postres = http_client.readString();
|
client.println("HTTP/1.1 200 OK");
|
||||||
int indp = postres.indexOf("progo=")+6;
|
client.println("Content-Type: text/plain");
|
||||||
Serial.println(postres);
|
client.println("Connection: close");
|
||||||
Serial.println(request);
|
client.println();
|
||||||
|
|
||||||
|
sendSchedule(client, scheduler_list[plugId]);
|
||||||
html();
|
} else {
|
||||||
break;
|
client.println("HTTP/1.1 404 Not Found");
|
||||||
|
client.println("Connection: close");
|
||||||
|
client.println();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST request
|
||||||
|
int slashIndex = request.indexOf("POST /api/schedule/");
|
||||||
|
if (slashIndex != -1) {
|
||||||
|
int nextSlashIndex = request.indexOf("/", slashIndex + 14);
|
||||||
|
if (nextSlashIndex == -1) {
|
||||||
|
nextSlashIndex = request.indexOf(" ", slashIndex + 14);
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(1);
|
// Extract the plug ID
|
||||||
request = "";
|
String plugIdStr = request.substring(slashIndex + 14, nextSlashIndex);
|
||||||
|
int plugId = plugIdStr.toInt();
|
||||||
|
|
||||||
|
// Read the next line which should have the POST body/content
|
||||||
|
String postBody = client.readStringUntil('\n');
|
||||||
|
|
||||||
|
// Update the schedule for the specified plug
|
||||||
|
if (plugId >= 0 && plugId < 8) {
|
||||||
|
deserializeSchedule(postBody, scheduler_list[plugId]);
|
||||||
|
Serial.println("Schedule updated for plug " + plugIdStr);
|
||||||
|
} else {
|
||||||
|
Serial.println("Invalid plug ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
http_client.stop();
|
http_client.stop();
|
||||||
|
|
||||||
|
|
||||||
|
281
html/index.html
Normal file
281
html/index.html
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ESP32 timed Switch</title>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Scheduler</h1>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
class Schedule {
|
||||||
|
|
||||||
|
constructor(scheduleArray) {
|
||||||
|
|
||||||
|
if (scheduleArray && scheduleArray.length === 24) {
|
||||||
|
this.schedule = scheduleArray.slice(); // Copy the array to prevent direct mutation
|
||||||
|
} 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.getUint32(i * 4);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 => {
|
||||||
|
if (hour === 0xFFF) { // All segments on
|
||||||
|
return { displayValue: ' ', class: 'on' };
|
||||||
|
} else if (hour === 0x000) { // All segments off
|
||||||
|
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 * 4);
|
||||||
|
var view = new DataView(buffer);
|
||||||
|
for (var i = 0; i < this.schedule.length; i++) {
|
||||||
|
view.setUint32(i * 4, this.schedule[i]);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of Class
|
||||||
|
|
||||||
|
// Start of Angular
|
||||||
|
|
||||||
|
var app = angular.module('plugSchedulerApp', []);
|
||||||
|
|
||||||
|
app.controller('PlugScheduleController', ['$http', function($http) {
|
||||||
|
var ctrl = this;
|
||||||
|
ctrl.editMode = false;
|
||||||
|
|
||||||
|
ctrl.$onInit = function() {
|
||||||
|
ctrl.scheduleObj = new Schedule(); // Initialize with a default schedule
|
||||||
|
ctrl.getSchedule();
|
||||||
|
};
|
||||||
|
|
||||||
|
ctrl.getSchedule = function() {
|
||||||
|
if (ctrl.plugId === undefined) {
|
||||||
|
throw new Error('plugId is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the schedule from the server for a given plug ID
|
||||||
|
var url = `/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 = `/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) {
|
||||||
|
console.error('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;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}]);
|
||||||
|
|
||||||
|
app.component('plugScheduleWidget', {
|
||||||
|
bindings: {
|
||||||
|
plugId: '<'
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class="plug-schedule-widget">
|
||||||
|
<h3>Schedule for 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 ng-click="$ctrl.modifySchedule()">Modify Schedule</button>
|
||||||
|
</div>
|
||||||
|
<div ng-if="$ctrl.editMode">
|
||||||
|
<table>
|
||||||
|
<tr ng-repeat="hourSegments in $ctrl.editMatrix track by $index">
|
||||||
|
<td 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)">Check All</button>
|
||||||
|
<button ng-click="$ctrl.uncheckAll($index)">Uncheck All</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<button ng-click="$ctrl.saveSchedule()">Save Schedule</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
controller: 'PlugScheduleController'
|
||||||
|
});
|
||||||
|
|
||||||
|
// End of Angular
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div ng-app="plugSchedulerApp">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
font-family: sans;
|
||||||
|
padding: 0px;
|
||||||
|
border-spacing: 0px;
|
||||||
|
border: transparent;
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-left: 5px;
|
||||||
|
vertical-align: top;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #696983;
|
||||||
|
color: eee;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.hour-cell {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 25px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.off {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.on {
|
||||||
|
background-color: #8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partial {
|
||||||
|
background-color: #bdc;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
</html>
|
76
html/mockup.py
Executable file
76
html/mockup.py
Executable file
@ -0,0 +1,76 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
from flask import Flask, request, send_from_directory, Response
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
PORT_FROM_ENV = os.environ.get('PORT')
|
||||||
|
if PORT_FROM_ENV:
|
||||||
|
PORT = PORT_FROM_ENV
|
||||||
|
else:
|
||||||
|
PORT = "3000"
|
||||||
|
|
||||||
|
static_file_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/', methods=['GET'])
|
||||||
|
def serve_index():
|
||||||
|
return send_from_directory(static_file_dir, 'index.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/<path:path>', methods=['GET'])
|
||||||
|
def serve_file_in_dir(path):
|
||||||
|
if not os.path.isfile(os.path.join(static_file_dir, path)):
|
||||||
|
path = os.path.join(path, 'index.html')
|
||||||
|
print("Serving local file : {}".format(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
|
||||||
|
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
|
||||||
|
def binary_stream_to_ints(binary_data):
|
||||||
|
integers = []
|
||||||
|
# Every 32-bit integer is represented by 2 bytes
|
||||||
|
for i in range(0, len(binary_data), 4):
|
||||||
|
integers.append(int.from_bytes(binary_data[i:i+4], byteorder='big') & 0xFFF) # Extract 32-bit value
|
||||||
|
return integers
|
||||||
|
|
||||||
|
@app.route('/api/schedule/<int:plug_id>', methods=['GET'])
|
||||||
|
def get_schedule(plug_id):
|
||||||
|
# Generate 24 random 32-bit integers
|
||||||
|
schedule = [random_32_bit_int() for _ in range(24)]
|
||||||
|
# Convert to binary stream
|
||||||
|
binary_data = ints_to_binary_stream(schedule)
|
||||||
|
# Return as a binary response
|
||||||
|
return Response(binary_data, content_type='application/octet-stream')
|
||||||
|
|
||||||
|
@app.route('/api/schedule/<int:plug_id>', methods=['POST'])
|
||||||
|
def update_schedule(plug_id):
|
||||||
|
# Get binary data from the request
|
||||||
|
binary_data = request.data
|
||||||
|
# Convert binary stream to integers
|
||||||
|
new_schedule = binary_stream_to_ints(binary_data)
|
||||||
|
print(f"Received new schedule for plug {plug_id}: {new_schedule}")
|
||||||
|
# Respond with a success message in binary format
|
||||||
|
return Response(b'Update successful', status=200, mimetype='text/plain')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=PORT,
|
||||||
|
use_debugger=True
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user