Compare commits

..

No commits in common. "b9e57453316969a2d03c31f3da899a091a5eadb7" and "fec387cf6c57d03fc60c199d1b74489b5b32b770" have entirely different histories.

5 changed files with 24 additions and 166 deletions

View File

@ -3,11 +3,10 @@
A hub for controlling IOT devices.
"""
import re
import copy
import json
import requests
from flask import Flask, render_template, request, abort, jsonify
from flask import Flask, render_template, request, abort
class RelayDevice:
@ -55,14 +54,17 @@ class RelayDevice:
).groups()[0] == 'High'
sub_dev.state = state
return json.dumps({'ok': True, sub_dev_id: state})
return json.dumps({sub_dev_id: state})
def from_dict(self, data):
"""
Initializes self with data from JSON dict.
"""
for key, value in data.items():
setattr(self, key, value)
self.id = data['id']
self.description = data['description']
self.location = data['location']
self.ip_address = data['ip_address']
self.sub_devices = {}
for sub_dev_type, sub_devs in data['sub_devices'].items():
if sub_dev_type == 'RelayOutlet':
self.sub_devices[sub_dev_type] = []
@ -88,24 +90,18 @@ class RelayOutlet:
"""
Initializes self with data from JSON dict.
"""
for key, value in data.items():
setattr(self, key, value)
self.id = data['id']
self.description = data['description']
self.gpio = data['gpio']
self.state = data['state']
return self
class DeviceEncoder(json.JSONEncoder):
"""
A custom json encoder for dumping devices to file.
"""
def default(self, o):
return vars(o)
def init_network(filepath="devices.json"):
def init_network():
"""
Initializes the network of IOT devices.
"""
with open(filepath, 'r') as file:
with open("devices.json", 'r') as file:
data = file.read()
data = json.loads(data)
network = {}
@ -120,14 +116,6 @@ def init_network(filepath="devices.json"):
return network
def save_network(filepath="devices.json"):
"""
Dumps the network to file.
"""
with open(filepath, 'w') as file:
file.write(json.dumps(network, cls=DeviceEncoder, indent='\t'))
app = Flask(__name__)
network = init_network()
@ -151,60 +139,10 @@ def toggle():
if device.id == device_id:
break
else:
return make_error(404, "device_id not found")
abort(404)
res = device.toggle(sub_dev_id)
if not res:
return make_error(404, "sub_dev_id not found")
save_network()
return res
@app.route('/edit')
def edit():
"""
Edits the text of a particular field.
"""
device_id = request.args.get('device_id')
sub_dev_id = request.args.get('sub_dev_id')
field = request.args.get('field')
value = request.args.get('value')
for device in sum(network.values(), []):
if device.id == device_id:
break
else:
return make_error(404, "device_id not found")
if sub_dev_id:
for sub_dev in sum(device.sub_devices.values(), []):
if sub_dev.id == sub_dev_id:
break
else:
return make_error(404, "sub_dev_id not found")
if hasattr(sub_dev, field):
setattr(sub_dev, field, value)
else:
return make_error(404, "sub_device field not found")
else:
if hasattr(device, field):
setattr(device, field, value)
else:
return make_error(404, "device field not found")
data = {
'ok': True,
'field': field,
'value': value
}
save_network()
return json.dumps(data)
def make_error(code, message):
"""
Returns a JSON error.
"""
res = jsonify(ok=False, status=code, message=message)
res.status_code = code
abort(404)
return res

Binary file not shown.

View File

@ -2,26 +2,15 @@ body {
padding: 5%;
display: flex;
justify-content: center;
font-family: Helvetica,sans-serif;
background-color: lightblue;
}
.device {
border: 2px solid darkgray;
border-radius: 0.5em;
padding: 3em;
margin: 3em;
border: 1px solid #ccc;
padding: 3%;
margin: 3%;
display: flex;
flex-direction: column;
align-items: center;
background-color: whitesmoke;
}
.ip_address {
font-size: 0.8em;
color: darkgray;
margin: 0.2em;
}
.sub_devices {
@ -29,8 +18,7 @@ body {
}
.sub_device {
border: 2px solid darkgray;
border-radius: 0.5em;
border: 1px solid #ccc;
padding: 1em;
margin: 1em;
display: flex;
@ -54,19 +42,3 @@ path {
.off {
stroke: black;
}
@font-face {
font-family: FontAwesome;
src: url("/static/fontawesome-webfont.woff2");
}
.edit, .save {
font-family: FontAwesome;
font-size: 0.8em;
color: dimgrey;
}
.edit:hover, .save:hover {
color: red;
cursor: pointer;
}

View File

@ -9,10 +9,10 @@ function toggle_outlet(svg) {
.join('&');
fetch(window.location.href + 'toggle?' + query)
.then(function(response) {
if (!response.ok) { throw new Error('HTTP error, status = ' + response.status); }
return response.json();
})
.then(function(json) {
if (!json.ok) { throw new Error('HTTP error, status = ' + json.status + ', message = ' + json.message); }
if (json[sub_dev.id]) {
svg.classList.remove('off');
svg.classList.add('on');
@ -22,55 +22,3 @@ function toggle_outlet(svg) {
}
});
}
function edit_field(field) {
let value = field.firstElementChild.innerText;
let input = document.createElement('input');
input.value = value;
field.firstElementChild.replaceWith(input);
let save = document.createElement('span');
save.innerHTML = '';
save.className = 'save';
save.setAttribute('onclick', 'save_field(this.parentElement)');
field.children[1].replaceWith(save);
}
function save_field(field) {
let value = field.firstElementChild.value;
let device_id = field.parentElement.id;
let sub_dev_id;
if (field.parentElement.className.includes('sub_device')) {
sub_dev_id = device_id;
device_id = field.parentElement.parentElement.parentElement.id;
} else {
sub_dev_id = '';
}
let params = {
device_id: device_id,
sub_dev_id: sub_dev_id,
field: field.className,
value: value
};
let query = Object.keys(params)
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
.join('&');
fetch(window.location.href + 'edit?' + query)
.then(function(response) {
return response.json();
})
.then(function(json) {
if (!json.ok) { throw new Error('HTTP error, status = ' + json.status + ', message = ' + json.message); }
let span = document.createElement('span');
span.innerText = json['value'];
span.className = 'field_value';
field.firstElementChild.replaceWith(span);
let edit = document.createElement('span');
edit.innerHTML = '';
edit.className = 'edit';
edit.setAttribute('onclick', 'edit_field(this.parentElement)');
field.children[1].replaceWith(edit);
});
}

View File

@ -10,9 +10,9 @@
{% for device in devices %}
<div class="device {{ dev_type }}" id="{{ device.id }}">
<div class="id">{{ device.id }}</div>
<div class="description"><span class="field_value">{{ device.description }}</span> <span class="edit" onclick="edit_field(this.parentElement)">&#xe800;</span></div>
<div class="location"><span class="field_value">{{ device.location }}</span> <span class="edit" onclick="edit_field(this.parentElement)">&#xe800;</span></div>
<div class="ip_address"><i>{{ device.ip_address }}</i></div>
<div class="description">{{ device.description }}</div>
<div class="location">{{ device.location }}</div>
<div class="ip_address">{{ device.ip_address }}</div>
<div class="sub_devices">
{% for sub_dev_type, sub_devs in device.sub_devices.items() %}
{% for sub_dev in sub_devs %}
@ -24,7 +24,7 @@
<path fill="none" d="M 43, 75 A 1 1 0 0 1 57,75 L 57,84 L 43,84 Z" />
<path fill="white" fill-opacity="0.0" d="M 23,10 L 77,10 A 50 49 0 0 1 77,91 L 23,91 A 50 49 0 0 1 23,10 Z" onclick="toggle_outlet(this.parentElement)" />
</svg>
<div class="description"><span class="field_value">{{ sub_dev.description }}</span> <span class="edit" onclick="edit_field(this.parentElement)">&#xe800;</span></div>
<div class="description">{{ sub_dev.description }}</div>
</div>
{% endfor %}
{% endfor %}