Compare commits
No commits in common. "b9e57453316969a2d03c31f3da899a091a5eadb7" and "fec387cf6c57d03fc60c199d1b74489b5b32b770" have entirely different histories.
b9e5745331
...
fec387cf6c
92
juice.py
92
juice.py
|
@ -3,11 +3,10 @@
|
||||||
A hub for controlling IOT devices.
|
A hub for controlling IOT devices.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import copy
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from flask import Flask, render_template, request, abort, jsonify
|
from flask import Flask, render_template, request, abort
|
||||||
|
|
||||||
|
|
||||||
class RelayDevice:
|
class RelayDevice:
|
||||||
|
@ -55,14 +54,17 @@ class RelayDevice:
|
||||||
).groups()[0] == 'High'
|
).groups()[0] == 'High'
|
||||||
sub_dev.state = state
|
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):
|
def from_dict(self, data):
|
||||||
"""
|
"""
|
||||||
Initializes self with data from JSON dict.
|
Initializes self with data from JSON dict.
|
||||||
"""
|
"""
|
||||||
for key, value in data.items():
|
self.id = data['id']
|
||||||
setattr(self, key, value)
|
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():
|
for sub_dev_type, sub_devs in data['sub_devices'].items():
|
||||||
if sub_dev_type == 'RelayOutlet':
|
if sub_dev_type == 'RelayOutlet':
|
||||||
self.sub_devices[sub_dev_type] = []
|
self.sub_devices[sub_dev_type] = []
|
||||||
|
@ -88,24 +90,18 @@ class RelayOutlet:
|
||||||
"""
|
"""
|
||||||
Initializes self with data from JSON dict.
|
Initializes self with data from JSON dict.
|
||||||
"""
|
"""
|
||||||
for key, value in data.items():
|
self.id = data['id']
|
||||||
setattr(self, key, value)
|
self.description = data['description']
|
||||||
|
self.gpio = data['gpio']
|
||||||
|
self.state = data['state']
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class DeviceEncoder(json.JSONEncoder):
|
def init_network():
|
||||||
"""
|
|
||||||
A custom json encoder for dumping devices to file.
|
|
||||||
"""
|
|
||||||
def default(self, o):
|
|
||||||
return vars(o)
|
|
||||||
|
|
||||||
|
|
||||||
def init_network(filepath="devices.json"):
|
|
||||||
"""
|
"""
|
||||||
Initializes the network of IOT devices.
|
Initializes the network of IOT devices.
|
||||||
"""
|
"""
|
||||||
with open(filepath, 'r') as file:
|
with open("devices.json", 'r') as file:
|
||||||
data = file.read()
|
data = file.read()
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
network = {}
|
network = {}
|
||||||
|
@ -120,14 +116,6 @@ def init_network(filepath="devices.json"):
|
||||||
return network
|
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__)
|
app = Flask(__name__)
|
||||||
network = init_network()
|
network = init_network()
|
||||||
|
|
||||||
|
@ -151,60 +139,10 @@ def toggle():
|
||||||
if device.id == device_id:
|
if device.id == device_id:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
return make_error(404, "device_id not found")
|
abort(404)
|
||||||
res = device.toggle(sub_dev_id)
|
res = device.toggle(sub_dev_id)
|
||||||
if not res:
|
if not res:
|
||||||
return make_error(404, "sub_dev_id not found")
|
abort(404)
|
||||||
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
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -2,26 +2,15 @@ body {
|
||||||
padding: 5%;
|
padding: 5%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-family: Helvetica,sans-serif;
|
|
||||||
background-color: lightblue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.device {
|
.device {
|
||||||
border: 2px solid darkgray;
|
border: 1px solid #ccc;
|
||||||
border-radius: 0.5em;
|
padding: 3%;
|
||||||
padding: 3em;
|
margin: 3%;
|
||||||
margin: 3em;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.ip_address {
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: darkgray;
|
|
||||||
margin: 0.2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub_devices {
|
.sub_devices {
|
||||||
|
@ -29,8 +18,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub_device {
|
.sub_device {
|
||||||
border: 2px solid darkgray;
|
border: 1px solid #ccc;
|
||||||
border-radius: 0.5em;
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -54,19 +42,3 @@ path {
|
||||||
.off {
|
.off {
|
||||||
stroke: black;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ function toggle_outlet(svg) {
|
||||||
.join('&');
|
.join('&');
|
||||||
fetch(window.location.href + 'toggle?' + query)
|
fetch(window.location.href + 'toggle?' + query)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
|
if (!response.ok) { throw new Error('HTTP error, status = ' + response.status); }
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(function(json) {
|
.then(function(json) {
|
||||||
if (!json.ok) { throw new Error('HTTP error, status = ' + json.status + ', message = ' + json.message); }
|
|
||||||
if (json[sub_dev.id]) {
|
if (json[sub_dev.id]) {
|
||||||
svg.classList.remove('off');
|
svg.classList.remove('off');
|
||||||
svg.classList.add('on');
|
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
{% for device in devices %}
|
{% for device in devices %}
|
||||||
<div class="device {{ dev_type }}" id="{{ device.id }}">
|
<div class="device {{ dev_type }}" id="{{ device.id }}">
|
||||||
<div class="id">{{ device.id }}</div>
|
<div class="id">{{ device.id }}</div>
|
||||||
<div class="description"><span class="field_value">{{ device.description }}</span> <span class="edit" onclick="edit_field(this.parentElement)"></span></div>
|
<div class="description">{{ device.description }}</div>
|
||||||
<div class="location"><span class="field_value">{{ device.location }}</span> <span class="edit" onclick="edit_field(this.parentElement)"></span></div>
|
<div class="location">{{ device.location }}</div>
|
||||||
<div class="ip_address"><i>{{ device.ip_address }}</i></div>
|
<div class="ip_address">{{ device.ip_address }}</div>
|
||||||
<div class="sub_devices">
|
<div class="sub_devices">
|
||||||
{% for sub_dev_type, sub_devs in device.sub_devices.items() %}
|
{% for sub_dev_type, sub_devs in device.sub_devices.items() %}
|
||||||
{% for sub_dev in sub_devs %}
|
{% 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="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)" />
|
<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>
|
</svg>
|
||||||
<div class="description"><span class="field_value">{{ sub_dev.description }}</span> <span class="edit" onclick="edit_field(this.parentElement)"></span></div>
|
<div class="description">{{ sub_dev.description }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user