Compare commits
4 Commits
e680c55fae
...
921163f454
Author | SHA1 | Date | |
---|---|---|---|
921163f454 | |||
2a628908bb | |||
bd61bb6819 | |||
c6fe1216c2 |
147
juice.py
147
juice.py
|
@ -7,142 +7,15 @@ import re
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import requests
|
|
||||||
from flask import Flask, render_template, request, abort, jsonify
|
from flask import Flask, render_template, request, abort, jsonify
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
import auth
|
import auth
|
||||||
import config
|
import config
|
||||||
|
import models
|
||||||
from auth import auth_required
|
from auth import auth_required
|
||||||
from tools import make_error
|
from tools import make_error
|
||||||
|
|
||||||
class RelayDevice:
|
|
||||||
"""
|
|
||||||
Represents a relay receptacle device controlled by an ESP-01. The
|
|
||||||
ESP-01 acts a simple switch to turn each receptable on or off via
|
|
||||||
a relay. Each device only has two outputs, `OUT0` and `OUT2`.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self.id = ""
|
|
||||||
self.type = "RelayDevice"
|
|
||||||
self.description = ""
|
|
||||||
self.location = ""
|
|
||||||
self.ip_address = ""
|
|
||||||
self.locked = False
|
|
||||||
|
|
||||||
self.sub_devices = []
|
|
||||||
self.sub_devices.append(RelayOutlet())
|
|
||||||
self.sub_devices.append(RelayOutlet())
|
|
||||||
self.sub_devices[0].id = 'OUT0'
|
|
||||||
self.sub_devices[0].gpio = '0'
|
|
||||||
self.sub_devices[1].id = 'OUT2'
|
|
||||||
self.sub_devices[1].gpio = '2'
|
|
||||||
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""
|
|
||||||
Queries the physical device and updates the internal model as
|
|
||||||
appropriate.
|
|
||||||
"""
|
|
||||||
if not self.ip_address:
|
|
||||||
return
|
|
||||||
for sub_dev in self.sub_devices:
|
|
||||||
res = requests.get(self.ip_address)
|
|
||||||
gpio0 = re.search(r"GPIO0: (\bLow|\bHigh)", res.text).groups()[0]
|
|
||||||
sub_dev.state = gpio0 == 'High'
|
|
||||||
|
|
||||||
def toggle(self, sub_dev_id):
|
|
||||||
"""
|
|
||||||
Toggles the state of a sub device.
|
|
||||||
"""
|
|
||||||
for sub_dev in self.sub_devices:
|
|
||||||
if sub_dev.id == sub_dev_id:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
params = {sub_dev.gpio: 'toggle'}
|
|
||||||
res = requests.get('http://' + self.ip_address + '/gpio',params=params)
|
|
||||||
res.raise_for_status()
|
|
||||||
|
|
||||||
state = re.search(
|
|
||||||
rf"GPIO{sub_dev.gpio}: (\bLow|\bHigh)",
|
|
||||||
res.text
|
|
||||||
).groups()[0] == 'High'
|
|
||||||
sub_dev.state = state
|
|
||||||
|
|
||||||
return json.dumps({'ok': True, 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.sub_devices = []
|
|
||||||
for sub_dev_dict in data.get('sub_devices'):
|
|
||||||
if sub_dev_dict.get('type') == 'RelayOutlet':
|
|
||||||
sub_dev = RelayOutlet().from_dict(sub_dev_dict)
|
|
||||||
self.sub_devices.append(sub_dev)
|
|
||||||
else:
|
|
||||||
typ = sub_dev.get('type')
|
|
||||||
raise ValueError(f"Unknown sub_device type {typ}")
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class RelayOutlet:
|
|
||||||
"""
|
|
||||||
Represents a single outlet on a RelayDevice.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self.id = ""
|
|
||||||
self.type = "RelayOutlet"
|
|
||||||
self.description = ""
|
|
||||||
self.gpio = ""
|
|
||||||
self.state = False
|
|
||||||
|
|
||||||
def from_dict(self, data):
|
|
||||||
"""
|
|
||||||
Initializes self with data from JSON dict.
|
|
||||||
"""
|
|
||||||
for key, value in data.items():
|
|
||||||
setattr(self, key, value)
|
|
||||||
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"):
|
|
||||||
"""
|
|
||||||
Initializes the network of IOT devices.
|
|
||||||
"""
|
|
||||||
with open(filepath, 'r') as file:
|
|
||||||
data = file.read()
|
|
||||||
data = json.loads(data)
|
|
||||||
network = []
|
|
||||||
for device_dict in data:
|
|
||||||
if device_dict.get('type') == 'RelayDevice':
|
|
||||||
device = RelayDevice().from_dict(device_dict)
|
|
||||||
network.append(device)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unknown device type {device.get('type')}")
|
|
||||||
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_views = Blueprint('app_views', __name__)
|
app_views = Blueprint('app_views', __name__)
|
||||||
|
|
||||||
@app_views.route('/')
|
@app_views.route('/')
|
||||||
|
@ -151,6 +24,7 @@ def index():
|
||||||
"""
|
"""
|
||||||
The index page.
|
The index page.
|
||||||
"""
|
"""
|
||||||
|
global network
|
||||||
init_state = {}
|
init_state = {}
|
||||||
for device in network:
|
for device in network:
|
||||||
device_state = {}
|
device_state = {}
|
||||||
|
@ -166,6 +40,7 @@ def toggle():
|
||||||
"""
|
"""
|
||||||
Toggles the state of a RelayDevice.
|
Toggles the state of a RelayDevice.
|
||||||
"""
|
"""
|
||||||
|
global network
|
||||||
device_id = request.args.get('device_id')
|
device_id = request.args.get('device_id')
|
||||||
sub_dev_id = request.args.get('sub_dev_id')
|
sub_dev_id = request.args.get('sub_dev_id')
|
||||||
|
|
||||||
|
@ -177,7 +52,7 @@ def toggle():
|
||||||
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")
|
return make_error(404, "sub_dev_id not found")
|
||||||
save_network()
|
models.save_network(network)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@app_views.route('/edit')
|
@app_views.route('/edit')
|
||||||
|
@ -186,6 +61,7 @@ def edit():
|
||||||
"""
|
"""
|
||||||
Edits the text of a particular field.
|
Edits the text of a particular field.
|
||||||
"""
|
"""
|
||||||
|
global network
|
||||||
device_id = request.args.get('device_id')
|
device_id = request.args.get('device_id')
|
||||||
sub_dev_id = request.args.get('sub_dev_id')
|
sub_dev_id = request.args.get('sub_dev_id')
|
||||||
field = request.args.get('field')
|
field = request.args.get('field')
|
||||||
|
@ -221,7 +97,7 @@ def edit():
|
||||||
'field': field,
|
'field': field,
|
||||||
'value': value
|
'value': value
|
||||||
}
|
}
|
||||||
save_network()
|
models.save_network(network)
|
||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -232,6 +108,7 @@ def new_device():
|
||||||
Allows adding a new device. Accepts device_type parameter, returns
|
Allows adding a new device. Accepts device_type parameter, returns
|
||||||
the device_id.
|
the device_id.
|
||||||
"""
|
"""
|
||||||
|
global network
|
||||||
device_type = request.args.get('device_type')
|
device_type = request.args.get('device_type')
|
||||||
|
|
||||||
if device_type == 'RelayDevice':
|
if device_type == 'RelayDevice':
|
||||||
|
@ -251,7 +128,7 @@ def new_device():
|
||||||
num = str(int(num[0]) + 1).zfill(2)
|
num = str(int(num[0]) + 1).zfill(2)
|
||||||
device.id = device_type + num
|
device.id = device_type + num
|
||||||
network.append(device)
|
network.append(device)
|
||||||
save_network()
|
models.save_network(network)
|
||||||
data = {'device_id': device.id}
|
data = {'device_id': device.id}
|
||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
|
|
||||||
|
@ -262,6 +139,7 @@ def lock_device():
|
||||||
"""
|
"""
|
||||||
Locks or unlocks a device to prevent or allow editing it's fields.
|
Locks or unlocks a device to prevent or allow editing it's fields.
|
||||||
"""
|
"""
|
||||||
|
global network
|
||||||
device_id = request.args.get('device_id')
|
device_id = request.args.get('device_id')
|
||||||
locked = request.args.get('locked') == 'true'
|
locked = request.args.get('locked') == 'true'
|
||||||
|
|
||||||
|
@ -275,7 +153,7 @@ def lock_device():
|
||||||
device.locked = True
|
device.locked = True
|
||||||
else:
|
else:
|
||||||
device.locked = False
|
device.locked = False
|
||||||
save_network()
|
models.save_network(network)
|
||||||
|
|
||||||
return jsonify(device_id=device.id, locked=device.locked)
|
return jsonify(device_id=device.id, locked=device.locked)
|
||||||
|
|
||||||
|
@ -286,6 +164,7 @@ def delete():
|
||||||
"""
|
"""
|
||||||
Deletes a device.
|
Deletes a device.
|
||||||
"""
|
"""
|
||||||
|
global network
|
||||||
device_id = request.args.get('device_id')
|
device_id = request.args.get('device_id')
|
||||||
|
|
||||||
for device in network:
|
for device in network:
|
||||||
|
@ -295,7 +174,7 @@ def delete():
|
||||||
return make_error(404, "device_id not found")
|
return make_error(404, "device_id not found")
|
||||||
|
|
||||||
network.remove(device)
|
network.remove(device)
|
||||||
save_network()
|
models.save_network(network)
|
||||||
|
|
||||||
return jsonify(True)
|
return jsonify(True)
|
||||||
|
|
||||||
|
@ -312,7 +191,7 @@ else:
|
||||||
app.secret_key = secret_key
|
app.secret_key = secret_key
|
||||||
with open('secret_key', 'wb') as file:
|
with open('secret_key', 'wb') as file:
|
||||||
file.write(secret_key)
|
file.write(secret_key)
|
||||||
network = init_network()
|
network = models.init_network()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
167
models.py
Normal file
167
models.py
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Data models for different device types managed by the Juice IOT hub.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class RelayDevice:
|
||||||
|
"""
|
||||||
|
Represents a relay receptacle device controlled by an ESP-01. The
|
||||||
|
ESP-01 acts a simple switch to turn each receptable on or off via
|
||||||
|
a relay. Each device only has two outputs, `OUT0` and `OUT2`.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.id = ""
|
||||||
|
self.type = "RelayDevice"
|
||||||
|
self.description = ""
|
||||||
|
self.location = ""
|
||||||
|
self.ip_address = ""
|
||||||
|
self.locked = False
|
||||||
|
|
||||||
|
self.sub_devices = []
|
||||||
|
self.sub_devices.append(RelayOutlet())
|
||||||
|
self.sub_devices.append(RelayOutlet())
|
||||||
|
self.sub_devices[0].id = 'OUT0'
|
||||||
|
self.sub_devices[0].gpio = '0'
|
||||||
|
self.sub_devices[1].id = 'OUT2'
|
||||||
|
self.sub_devices[1].gpio = '2'
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""
|
||||||
|
Queries the physical device and updates the internal model as
|
||||||
|
appropriate.
|
||||||
|
"""
|
||||||
|
if not self.ip_address:
|
||||||
|
return
|
||||||
|
for sub_dev in self.sub_devices:
|
||||||
|
res = requests.get(self.ip_address)
|
||||||
|
gpio0 = re.search(r"GPIO0: (\bLow|\bHigh)", res.text).groups()[0]
|
||||||
|
sub_dev.state = gpio0 == 'High'
|
||||||
|
|
||||||
|
def toggle(self, sub_dev_id):
|
||||||
|
"""
|
||||||
|
Toggles the state of a sub device.
|
||||||
|
"""
|
||||||
|
for sub_dev in self.sub_devices:
|
||||||
|
if sub_dev.id == sub_dev_id:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
params = {sub_dev.gpio: 'toggle'}
|
||||||
|
res = requests.get('http://' + self.ip_address + '/gpio',params=params)
|
||||||
|
res.raise_for_status()
|
||||||
|
|
||||||
|
state = re.search(
|
||||||
|
rf"GPIO{sub_dev.gpio}: (\bLow|\bHigh)",
|
||||||
|
res.text
|
||||||
|
).groups()[0] == 'High'
|
||||||
|
sub_dev.state = state
|
||||||
|
|
||||||
|
return json.dumps({'ok': True, 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.sub_devices = []
|
||||||
|
for sub_dev_dict in data.get('sub_devices'):
|
||||||
|
if sub_dev_dict.get('type') == 'RelayOutlet':
|
||||||
|
sub_dev = RelayOutlet().from_dict(sub_dev_dict)
|
||||||
|
self.sub_devices.append(sub_dev)
|
||||||
|
else:
|
||||||
|
typ = sub_dev.get('type')
|
||||||
|
raise ValueError(f"Unknown sub_device type {typ}")
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class RelayOutlet:
|
||||||
|
"""
|
||||||
|
Represents a single outlet on a RelayDevice.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.id = ""
|
||||||
|
self.type = "RelayOutlet"
|
||||||
|
self.description = ""
|
||||||
|
self.gpio = ""
|
||||||
|
self.state = False
|
||||||
|
|
||||||
|
def from_dict(self, data):
|
||||||
|
"""
|
||||||
|
Initializes self with data from JSON dict.
|
||||||
|
"""
|
||||||
|
for key, value in data.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class LightStrip:
|
||||||
|
"""
|
||||||
|
Represents an RGB LED light strip controller.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.id = ""
|
||||||
|
self.type = "LightStrip"
|
||||||
|
self.description = ""
|
||||||
|
self.location = ""
|
||||||
|
self.ip_address = ""
|
||||||
|
self.locked = False
|
||||||
|
|
||||||
|
def from_dict(self, data):
|
||||||
|
"""
|
||||||
|
Initializes self with data from JSON dict.
|
||||||
|
"""
|
||||||
|
for key, value in data.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
self.sub_devices = []
|
||||||
|
for sub_dev_dict in data.get('sub_devices'):
|
||||||
|
if sub_dev_dict.get('type') == 'RelayOutlet':
|
||||||
|
sub_dev = RelayOutlet().from_dict(sub_dev_dict)
|
||||||
|
self.sub_devices.append(sub_dev)
|
||||||
|
else:
|
||||||
|
typ = sub_dev.get('type')
|
||||||
|
raise ValueError(f"Unknown sub_device type {typ}")
|
||||||
|
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"):
|
||||||
|
"""
|
||||||
|
Initializes the network of IOT devices.
|
||||||
|
"""
|
||||||
|
with open(filepath, 'r') as file:
|
||||||
|
data = file.read()
|
||||||
|
data = json.loads(data)
|
||||||
|
network = []
|
||||||
|
for device_dict in data:
|
||||||
|
if device_dict.get('type') == 'RelayDevice':
|
||||||
|
device = RelayDevice().from_dict(device_dict)
|
||||||
|
network.append(device)
|
||||||
|
elif device_dict.get('type') == 'LightStrip':
|
||||||
|
device = LightStrip().from_dict(device_dict)
|
||||||
|
network.append(device)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown device type {device_dict.get('type')}")
|
||||||
|
return network
|
||||||
|
|
||||||
|
|
||||||
|
def save_network(network, filepath="devices.json"):
|
||||||
|
"""
|
||||||
|
Dumps the network to file.
|
||||||
|
"""
|
||||||
|
with open(filepath, 'w') as file:
|
||||||
|
file.write(json.dumps(network, cls=DeviceEncoder, indent='\t'))
|
Binary file not shown.
21
static/juice-manifest.webmanifest
Normal file
21
static/juice-manifest.webmanifest
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "Juice",
|
||||||
|
"short_name": "Juice",
|
||||||
|
"start_url": "/juice",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "lightblue",
|
||||||
|
"theme_color": "lightblue",
|
||||||
|
"description": "An IOT hub.",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/static/orange-juice-144p.png",
|
||||||
|
"sizes": "144x144",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/static/orange-juice-512p.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -4,6 +4,33 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#logo-line {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
height: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: 0.1em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
color: whitesmoke;
|
||||||
|
text-shadow:
|
||||||
|
1px 1px 0 dimgray,
|
||||||
|
-1px 1px 0 dimgray,
|
||||||
|
1px -1px 0 dimgray,
|
||||||
|
-1px -1px 0 dimgray;
|
||||||
|
}
|
||||||
|
|
||||||
.font-awesome {
|
.font-awesome {
|
||||||
font-family: FontAwesome;
|
font-family: FontAwesome;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
@ -11,12 +38,13 @@ body {
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
background-color: whitesmoke;
|
background-color: whitesmoke;
|
||||||
|
border-top: 1px solid darkgray;
|
||||||
border-bottom: 1px solid darkgray;
|
border-bottom: 1px solid darkgray;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav span {
|
nav span {
|
||||||
color: dimgrey;
|
color: dimgray;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 0.1s;
|
transition: 0.1s;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
@ -77,6 +105,29 @@ nav span:hover {
|
||||||
height: 5em;
|
height: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.LightStrip .sub_devices {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.led {
|
||||||
|
padding: 0.25em;
|
||||||
|
margin: 0.125em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.led_color {
|
||||||
|
border-radius: 0.25em;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.led_color_input {
|
||||||
|
opacity: 0;
|
||||||
|
display: block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: FontAwesome;
|
font-family: FontAwesome;
|
||||||
src: url("/static/fontawesome-webfont.woff2");
|
src: url("/static/fontawesome-webfont.woff2");
|
||||||
|
|
|
@ -10,6 +10,12 @@ function load() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('input[type=color]').forEach((led_input) => {
|
||||||
|
led_input.onchange = function(event) {
|
||||||
|
event.target.parentElement.style.backgroundColor = event.target.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_object_from_svg(svg) {
|
function get_object_from_svg(svg) {
|
||||||
|
|
BIN
static/orange-juice-144p.png
Normal file
BIN
static/orange-juice-144p.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
BIN
static/orange-juice-512p.png
Normal file
BIN
static/orange-juice-512p.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
85
static/orange-juice.svg
Normal file
85
static/orange-juice.svg
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 155.883 155.883" style="enable-background:new 0 0 155.883 155.883;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<circle style="fill:#FF7500;" cx="98.922" cy="49.126" r="27.492"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle style="fill:#FFD01F;" cx="99.304" cy="48.744" r="22.528"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle style="fill:#FFD01F;" cx="99.304" cy="48.744" r="22.528"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFB000;" d="M98.255,47.472V30.184c-9.617,0.434-17.407,7.843-18.452,17.288H98.255z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFB000;" d="M118.805,47.217c-1.584-9.02-8.963-16.036-18.167-17.056v17.056H118.805z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFB000;" d="M98.255,50.018v17.287c-9.617-0.434-17.407-7.842-18.452-17.287
|
||||||
|
C79.803,50.018,98.255,50.018,98.255,50.018z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFB000;" d="M118.805,50.271c-1.584,9.02-8.963,16.037-18.167,17.056V50.271H118.805z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon style="fill:#ABD9D5;" points="94.687,155.883 37.939,155.883 29.469,47.883 103.157,47.883 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon style="fill:#CDE8E6;" points="64.663,155.883 37.939,155.883 29.469,47.883 64.663,47.883 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon style="fill:#DDF0EE;" points="88.758,141.883 43.868,141.883 35.398,47.883 97.228,47.883 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon style="fill:#FF7500;" points="37.304,68.606 43.868,141.449 88.758,141.449 95.321,68.606 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon style="fill:#FF4F0C;" points="66.736,68.606 37.304,68.606 43.868,141.449 66.736,141.449 "/>
|
||||||
|
</g>
|
||||||
|
<path style="fill:#FFFFFF;" d="M84.175,127.346c-0.215,2.329-2.278,4.044-4.606,3.828l0,0c-2.329-0.215-4.043-2.277-3.828-4.606
|
||||||
|
l4.048-43.857c0.215-2.329,2.278-4.043,4.606-3.828l0,0c2.329,0.215,4.043,2.277,3.828,4.606L84.175,127.346z"/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<polygon style="fill:#F62D8D;" points="37.694,0 42.682,47.433 51.565,47.433 46.576,0 "/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -6,15 +6,25 @@
|
||||||
<script type="text/javascript" src="/static/juice.js"></script>
|
<script type="text/javascript" src="/static/juice.js"></script>
|
||||||
<script>var init_state = {{ init_state|tojson|safe }};</script>
|
<script>var init_state = {{ init_state|tojson|safe }};</script>
|
||||||
<script>window.onload = load;</script>
|
<script>window.onload = load;</script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<meta name="description" content="An IOT hub.">
|
<meta name="description" content="An IOT hub.">
|
||||||
|
<meta name="theme-color" content="lightblue">
|
||||||
|
<link rel="manifest" href="/static/juice-manifest.webmanifest">
|
||||||
|
<link rel="icon" href="/static/orange-juice.svg">
|
||||||
|
<link rel="icon" href="/static/orange-juice-144p.png">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
|
<div id="logo-line">
|
||||||
|
<object id="logo" aria-label="Juice: An IOT hub" data="/static/orange-juice.svg"></object>
|
||||||
<h1>Juice</h1>
|
<h1>Juice</h1>
|
||||||
|
<h3>An IOT hub</h3>
|
||||||
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
<span title="Add new device" onclick="new_device()"><span class="font-awesome"></span></span>
|
<span class="font-awesome" title="Home"><a href="./"></a></span>
|
||||||
<span title="Manage security keys and tokens"><a href="./manage">Manage</a></span>
|
<span class="font-awesome" title="Manage security keys and tokens"><a href="./manage"></a></span>
|
||||||
|
<span class="font-awesome" title="Add new device" onclick="new_device()"></span>
|
||||||
|
<span class="font-awesome" title="Logout"><a href="./logout"></a></span>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
|
@ -26,6 +36,7 @@
|
||||||
<div class="location editable"><span class="field_value">{{ device.location }}</span>{% if not device.locked %}<span class="edit font-awesome" onclick="edit_field(this.parentElement)"></span>{% endif %}</div>
|
<div class="location editable"><span class="field_value">{{ device.location }}</span>{% if not device.locked %}<span class="edit font-awesome" onclick="edit_field(this.parentElement)"></span>{% endif %}</div>
|
||||||
<div class="ip_address editable"><span class="field_value">{{ device.ip_address }}</span>{% if not device.locked %}<span class="edit font-awesome" onclick="edit_field(this.parentElement)"></span>{% endif %}</div>
|
<div class="ip_address editable"><span class="field_value">{{ device.ip_address }}</span>{% if not device.locked %}<span class="edit font-awesome" onclick="edit_field(this.parentElement)"></span>{% endif %}</div>
|
||||||
<div class="sub_devices">
|
<div class="sub_devices">
|
||||||
|
{% if device.type == 'RelayDevice' %}
|
||||||
{% for sub_dev in device.sub_devices %}
|
{% for sub_dev in device.sub_devices %}
|
||||||
<div class="sub_device {{ sub_dev.type }} {{ sub_dev.id }}">
|
<div class="sub_device {{ sub_dev.type }} {{ sub_dev.id }}">
|
||||||
<div class="id">{{ sub_dev.id }}</div>
|
<div class="id">{{ sub_dev.id }}</div>
|
||||||
|
@ -33,6 +44,15 @@
|
||||||
<div class="description editable"><span class="field_value">{{ sub_dev.description }}</span>{% if not device.locked %}<span class="edit font-awesome" onclick="edit_field(this.parentElement)"></span>{% endif %}</div>
|
<div class="description editable"><span class="field_value">{{ sub_dev.description }}</span>{% if not device.locked %}<span class="edit font-awesome" onclick="edit_field(this.parentElement)"></span>{% endif %}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% elif device.type == 'LightStrip' %}
|
||||||
|
{% for n in range(48) %}
|
||||||
|
<div class="sub_device led led{{n}}">
|
||||||
|
<label class="led_color">
|
||||||
|
<input class="led_color_input" type="color">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<script>url_prefix = '{{ url_prefix }}';</script>
|
<script>url_prefix = '{{ url_prefix }}';</script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="An IOT hub.">
|
<meta name="description" content="An IOT hub.">
|
||||||
|
<link rel="icon" href="/static/orange-juice.svg">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<script>url_prefix = '{{ url_prefix }}';</script>
|
<script>url_prefix = '{{ url_prefix }}';</script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="An IOT hub.">
|
<meta name="description" content="An IOT hub.">
|
||||||
|
<link rel="icon" href="/static/orange-juice.svg">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<link rel="stylesheet" type="text/css" href="/static/juice.css">
|
<link rel="stylesheet" type="text/css" href="/static/juice.css">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="An IOT hub.">
|
<meta name="description" content="An IOT hub.">
|
||||||
|
<link rel="icon" href="/static/orange-juice.svg">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<script>url_prefix = '{{ url_prefix }}';</script>
|
<script>url_prefix = '{{ url_prefix }}';</script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="An IOT hub.">
|
<meta name="description" content="An IOT hub.">
|
||||||
|
<link rel="icon" href="/static/orange-juice.svg">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user