Reimport projects into this one
34
CitizenWatt-Base/Makefile
Normal file
@ -0,0 +1,34 @@
|
||||
#############################################################################
|
||||
#
|
||||
# Makefile for CitizenWatt install on Raspberry Pi
|
||||
#
|
||||
# License: GPL (General Public License)
|
||||
# Author: AlexFaraino
|
||||
# Date: 2014/10/20 (v1.0)
|
||||
#
|
||||
# Description:
|
||||
# ------------
|
||||
# You can change the install directory by editing the prefix line
|
||||
#
|
||||
prefix=$(DESTDIR)/opt/citizenwatt
|
||||
files=`ls | grep -v debian`
|
||||
sup_prefix=$(DESTDIR)/etc/supervisor/conf.d/
|
||||
|
||||
# The recommended compiler flags for the Raspberry Pi
|
||||
CCFLAGS=-Wall -Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s
|
||||
|
||||
all: receive
|
||||
|
||||
receive: receive.cpp
|
||||
g++ ${CCFLAGS} -lrf24 $@.cpp -o $@
|
||||
|
||||
clean:
|
||||
rm -rf receive
|
||||
|
||||
install: all
|
||||
test -d $(prefix) || mkdir -p $(prefix)
|
||||
test -d $(sup_prefix) || mkdir -p $(sup_prefix)
|
||||
cp -r $(files) $(prefix)/
|
||||
cp system/supervisor_citizenwatt.conf $(sup_prefix)/citizenwatt.conf
|
||||
|
||||
.PHONY: install
|
39
CitizenWatt-Base/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
## Package needed
|
||||
|
||||
* sqlalchemy
|
||||
* cherrypy
|
||||
* numpy
|
||||
* pycrypto
|
||||
* psycopg2 for communication with the PostgreSQL database
|
||||
|
||||
## API
|
||||
|
||||
* /api/sensors
|
||||
* Returns all the available sensors with their types
|
||||
* /api/sensors/<id:int>
|
||||
* Returns the infos for the specified sensor.
|
||||
* /api/types
|
||||
* Returns all the available measure types
|
||||
* /api/time
|
||||
* Returns the current timestamp of the server side.
|
||||
* /api/energy_providers
|
||||
* Returns all available energy providers
|
||||
* /api/energy_providers/<current|<int>>
|
||||
* Returns the targeted energy provider
|
||||
* /api/<sensor:int>/get/watts/by_id/<nb:int>
|
||||
* Get measure with id nb
|
||||
* Get measure nth to last measure if nb < 0 (behaviour of Python lists)
|
||||
* /api/<sensor:int>/get/[watts|kwatthours|euros]/by_id/<nb1:int>/<nb2:int>
|
||||
* Get all the measures with id between nb1 and nb2 (nb1 < nb2)
|
||||
* Get all the measures between nb1 and nb2 starting from the end if nb1, nb2 < 0 (behaviour of Python lists)
|
||||
* Get the energy / cost associated with these measures if kwatthours or euros is specified
|
||||
* /api/<sensor:int>/get/watts/by_time/<time:int>
|
||||
* Idem as above, but with timestamps
|
||||
* /api/<provider:re:current|\d>/watt_to_euros/<tarif:re:night|day>/<consumption:int>
|
||||
* Returns the price associated to the consumption (in kWh) for the specified provider
|
||||
* /api/<sensor:int>/get/[watts|kwatthours|euros]/by_time/<time1:int>/<time2:int>/<timestep:int>
|
||||
* Idem as above, but with timestamps
|
||||
* idem avec id
|
||||
* idem with ids
|
||||
|
||||
step > 0
|
3624
CitizenWatt-Base/bottle.py
Normal file
163
CitizenWatt-Base/bottle_sqlalchemy.py
Normal file
@ -0,0 +1,163 @@
|
||||
'''
|
||||
This bottle-sqlalchemy plugin integrates SQLAlchemy with your Bottle
|
||||
application. It connects to a database at the beginning of a request,
|
||||
passes the database handle to the route callback and closes the connection
|
||||
afterwards.
|
||||
|
||||
The plugin inject an argument to all route callbacks that require a `db`
|
||||
keyword.
|
||||
|
||||
Usage Example::
|
||||
|
||||
import bottle
|
||||
from bottle import HTTPError
|
||||
from bottle.ext import sqlalchemy
|
||||
from sqlalchemy import create_engine, Column, Integer, Sequence, String
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
engine = create_engine('sqlite:///:memory:', echo=True)
|
||||
|
||||
app = bottle.Bottle()
|
||||
plugin = sqlalchemy.Plugin(engine, Base.metadata, create=True)
|
||||
app.install(plugin)
|
||||
|
||||
class Entity(Base):
|
||||
__tablename__ = 'entity'
|
||||
id = Column(Integer, Sequence('id_seq'), primary_key=True)
|
||||
name = Column(String(50))
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return "<Entity('%d', '%s')>" % (self.id, self.name)
|
||||
|
||||
|
||||
@app.get('/:name')
|
||||
def show(name, db):
|
||||
entity = db.query(Entity).filter_by(name=name).first()
|
||||
if entity:
|
||||
return {'id': entity.id, 'name': entity.name}
|
||||
return HTTPError(404, 'Entity not found.')
|
||||
|
||||
@app.put('/:name')
|
||||
def put_name(name, db):
|
||||
entity = Entity(name)
|
||||
db.add(entity)
|
||||
|
||||
|
||||
It is up to you create engine and metadata, because SQLAlchemy has
|
||||
a lot of options to do it. The plugin just handles the SQLAlchemy
|
||||
session.
|
||||
|
||||
Copyright (c) 2011-2012, Iuri de Silvio
|
||||
License: MIT (see LICENSE for details)
|
||||
'''
|
||||
|
||||
import inspect
|
||||
|
||||
import bottle
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.orm.scoping import ScopedSession
|
||||
|
||||
# PluginError is defined to bottle >= 0.10
|
||||
if not hasattr(bottle, 'PluginError'):
|
||||
class PluginError(bottle.BottleException):
|
||||
pass
|
||||
bottle.PluginError = PluginError
|
||||
|
||||
class SQLAlchemyPlugin(object):
|
||||
|
||||
name = 'sqlalchemy'
|
||||
api = 2
|
||||
|
||||
def __init__(self, engine, metadata=None,
|
||||
keyword='db', commit=True, create=False, use_kwargs=False, create_session=None):
|
||||
'''
|
||||
:param engine: SQLAlchemy engine created with `create_engine` function
|
||||
:param metadata: SQLAlchemy metadata. It is required only if `create=True`
|
||||
:param keyword: Keyword used to inject session database in a route
|
||||
:param create: If it is true, execute `metadata.create_all(engine)`
|
||||
when plugin is applied
|
||||
:param commit: If it is true, commit changes after route is executed.
|
||||
:param use_kwargs: plugin inject session database even if it is not
|
||||
explicitly defined, using **kwargs argument if defined.
|
||||
:param create_session: SQLAlchemy session maker created with the
|
||||
'sessionmaker' function. Will create its own if undefined.
|
||||
'''
|
||||
self.engine = engine
|
||||
if create_session is None:
|
||||
create_session = sessionmaker()
|
||||
self.create_session = create_session
|
||||
self.metadata = metadata
|
||||
self.keyword = keyword
|
||||
self.create = create
|
||||
self.commit = commit
|
||||
self.use_kwargs = use_kwargs
|
||||
|
||||
def setup(self, app):
|
||||
''' Make sure that other installed plugins don't affect the same
|
||||
keyword argument and check if metadata is available.'''
|
||||
for other in app.plugins:
|
||||
if not isinstance(other, SQLAlchemyPlugin):
|
||||
continue
|
||||
if other.keyword == self.keyword:
|
||||
raise bottle.PluginError("Found another SQLAlchemy plugin with "\
|
||||
"conflicting settings (non-unique keyword).")
|
||||
elif other.name == self.name:
|
||||
self.name += '_%s' % self.keyword
|
||||
if self.create and not self.metadata:
|
||||
raise bottle.PluginError('Define metadata value to create database.')
|
||||
|
||||
def apply(self, callback, route):
|
||||
# hack to support bottle v0.9.x
|
||||
if bottle.__version__.startswith('0.9'):
|
||||
config = route['config']
|
||||
_callback = route['callback']
|
||||
else:
|
||||
config = route.config
|
||||
_callback = route.callback
|
||||
|
||||
if "sqlalchemy" in config: # support for configuration before `ConfigDict` namespaces
|
||||
g = lambda key, default: config.get('sqlalchemy', {}).get(key, default)
|
||||
else:
|
||||
g = lambda key, default: config.get('sqlalchemy.' + key, default)
|
||||
|
||||
keyword = g('keyword', self.keyword)
|
||||
create = g('create', self.create)
|
||||
commit = g('commit', self.commit)
|
||||
use_kwargs = g('use_kwargs', self.use_kwargs)
|
||||
|
||||
argspec = inspect.getargspec(_callback)
|
||||
if not ((use_kwargs and argspec.keywords) or keyword in argspec.args):
|
||||
return callback
|
||||
|
||||
if create:
|
||||
self.metadata.create_all(self.engine)
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
kwargs[keyword] = session = self.create_session(bind=self.engine)
|
||||
try:
|
||||
rv = callback(*args, **kwargs)
|
||||
if commit:
|
||||
session.commit()
|
||||
except (SQLAlchemyError, bottle.HTTPError):
|
||||
session.rollback()
|
||||
raise
|
||||
except bottle.HTTPResponse:
|
||||
if commit:
|
||||
session.commit()
|
||||
raise
|
||||
finally:
|
||||
if isinstance(self.create_session, ScopedSession):
|
||||
self.create_session.remove()
|
||||
else:
|
||||
session.close()
|
||||
return rv
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
Plugin = SQLAlchemyPlugin
|
191
CitizenWatt-Base/bottlesession.py
Normal file
@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Bottle session manager. See README for full documentation.
|
||||
#
|
||||
# Written by: Sean Reifschneider <jafo@tummy.com>
|
||||
#
|
||||
# License: 3-clause BSD
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import bottle
|
||||
import time
|
||||
|
||||
|
||||
def authenticator(session_manager, login_url='/auth/login'):
|
||||
'''Create an authenticator decorator.
|
||||
|
||||
:param session_manager: A session manager class to be used for storing
|
||||
and retrieving session data. Probably based on
|
||||
:class:`BaseSession`.
|
||||
:param login_url: The URL to redirect to if a login is required.
|
||||
(default: ``'/auth/login'``).
|
||||
'''
|
||||
def valid_user(login_url=login_url):
|
||||
def decorator(handler):
|
||||
import functools
|
||||
|
||||
@functools.wraps(handler)
|
||||
def check_auth(*args, **kwargs):
|
||||
try:
|
||||
data = session_manager.get_session()
|
||||
if not data['valid']:
|
||||
raise KeyError('Invalid login')
|
||||
except (KeyError, TypeError):
|
||||
bottle.response.set_cookie(
|
||||
'validuserloginredirect',
|
||||
bottle.request.fullpath, path='/',
|
||||
expires=(int(time.time()) + 3600))
|
||||
bottle.redirect(login_url)
|
||||
|
||||
# set environment
|
||||
if data.get('name'):
|
||||
bottle.request.environ['REMOTE_USER'] = data['name']
|
||||
|
||||
return handler(*args, **kwargs)
|
||||
return check_auth
|
||||
return decorator
|
||||
return(valid_user)
|
||||
|
||||
|
||||
import pickle
|
||||
import os
|
||||
import uuid
|
||||
|
||||
|
||||
class BaseSession(object):
|
||||
'''Base class which implements some of the basic functionality required for
|
||||
session managers. Cannot be used directly.
|
||||
|
||||
:param cookie_expires: Expiration time of session ID cookie, either `None`
|
||||
if the cookie is not to expire, a number of seconds in the future,
|
||||
or a datetime object. (default: 30 days)
|
||||
'''
|
||||
def __init__(self, cookie_expires=86400 * 30):
|
||||
self.cookie_expires = cookie_expires
|
||||
|
||||
def load(self, sessionid):
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self, sessionid, data):
|
||||
raise NotImplementedError
|
||||
|
||||
def make_session_id(self):
|
||||
return str(uuid.uuid4())
|
||||
|
||||
def allocate_new_session_id(self):
|
||||
# retry allocating a unique sessionid
|
||||
for i in range(100):
|
||||
sessionid = self.make_session_id()
|
||||
if not self.load(sessionid):
|
||||
return sessionid
|
||||
raise ValueError('Unable to allocate unique session')
|
||||
|
||||
def get_session(self):
|
||||
# get existing or create new session identifier
|
||||
sessionid = bottle.request.cookies.get('sessionid')
|
||||
if not sessionid:
|
||||
sessionid = self.allocate_new_session_id()
|
||||
bottle.response.set_cookie(
|
||||
'sessionid', sessionid, path='/',
|
||||
expires=(int(time.time()) + self.cookie_expires))
|
||||
|
||||
# load existing or create new session
|
||||
data = self.load(sessionid)
|
||||
if not data:
|
||||
data = {'sessionid': sessionid, 'valid': False}
|
||||
self.save(data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class PickleSession(BaseSession):
|
||||
'''Class which stores session information in the file-system.
|
||||
|
||||
:param session_dir: Directory that session information is stored in.
|
||||
(default: ``'/tmp'``).
|
||||
'''
|
||||
def __init__(self, session_dir='/tmp', *args, **kwargs):
|
||||
super(PickleSession, self).__init__(*args, **kwargs)
|
||||
self.session_dir = session_dir
|
||||
|
||||
def load(self, sessionid):
|
||||
filename = os.path.join(self.session_dir, 'session-%s' % sessionid)
|
||||
if not os.path.exists(filename):
|
||||
return None
|
||||
with open(filename, 'rb') as fp:
|
||||
session = pickle.load(fp)
|
||||
return session
|
||||
|
||||
def save(self, data):
|
||||
sessionid = data['sessionid']
|
||||
fileName = os.path.join(self.session_dir, 'session-%s' % sessionid)
|
||||
tmpName = fileName + '.' + str(uuid.uuid4())
|
||||
with open(tmpName, 'wb') as fp:
|
||||
self.session = pickle.dump(data, fp)
|
||||
os.rename(tmpName, fileName)
|
||||
|
||||
|
||||
class CookieSession(BaseSession):
|
||||
'''Session manager class which stores session in a signed browser cookie.
|
||||
|
||||
:param cookie_name: Name of the cookie to store the session in.
|
||||
(default: ``session_data``)
|
||||
:param secret: Secret to be used for "secure cookie". If ``None``,
|
||||
a random secret will be generated and written to a temporary
|
||||
file for future use. This may not be suitable for systems which
|
||||
have untrusted users on it. (default: ``None``)
|
||||
:param secret_file: File to read the secret from. If ``secret`` is
|
||||
``None`` and ``secret_file`` is set, the first line of this file
|
||||
is read, and stripped, to produce the secret.
|
||||
'''
|
||||
|
||||
def __init__(
|
||||
self, secret=None, secret_file=None, cookie_name='session_data',
|
||||
*args, **kwargs):
|
||||
super(CookieSession, self).__init__(*args, **kwargs)
|
||||
self.cookie_name = cookie_name
|
||||
|
||||
if not secret and secret_file is not None:
|
||||
with open(secret_file, 'r') as fp:
|
||||
secret = fp.readline().strip()
|
||||
|
||||
if not secret:
|
||||
import string
|
||||
import random
|
||||
import tempfile
|
||||
import sys
|
||||
|
||||
tmpfilename = os.path.join(
|
||||
tempfile.gettempdir(),
|
||||
'%s.secret' % os.path.basename(sys.argv[0]))
|
||||
|
||||
if os.path.exists(tmpfilename):
|
||||
with open(tmpfilename, 'r') as fp:
|
||||
secret = fp.readline().strip()
|
||||
else:
|
||||
# save off a secret to a tmp file
|
||||
secret = ''.join([
|
||||
random.choice(string.ascii_letters)
|
||||
for x in range(32)])
|
||||
|
||||
old_umask = os.umask(0o77)
|
||||
with open(tmpfilename, 'w') as fp:
|
||||
fp.write(secret)
|
||||
os.umask(old_umask)
|
||||
|
||||
self.secret = secret
|
||||
|
||||
def load(self, sessionid):
|
||||
cookie = bottle.request.get_cookie(
|
||||
self.cookie_name,
|
||||
secret=self.secret)
|
||||
if cookie is None:
|
||||
return {}
|
||||
return pickle.loads(cookie)
|
||||
|
||||
def save(self, data):
|
||||
bottle.response.set_cookie(
|
||||
self.cookie_name, pickle.dumps(data), secret=self.secret,
|
||||
path='/', expires=int(time.time()) + self.cookie_expires,
|
||||
secure=True, httponly=True)
|
2
CitizenWatt-Base/libcitizenwatt/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env python2
|
||||
# -*- coding: utf-8 -*-
|
316
CitizenWatt-Base/libcitizenwatt/cache.py
Normal file
@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import bisect
|
||||
import datetime
|
||||
import json
|
||||
import numpy
|
||||
import redis
|
||||
|
||||
from libcitizenwatt import database
|
||||
from libcitizenwatt import tools
|
||||
from sqlalchemy import asc, desc
|
||||
from libcitizenwatt.config import Config
|
||||
|
||||
|
||||
config = Config()
|
||||
|
||||
|
||||
def do_cache_ids(sensor, watt_euros, id1, id2, db, force_refresh=False):
|
||||
"""
|
||||
Computes the cache (if needed) for the API call
|
||||
/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_id/<id1:int>/<id2:int>
|
||||
|
||||
Returns the stored (or computed) data or None if parameters are invalid.
|
||||
"""
|
||||
r = redis.Redis(decode_responses=True)
|
||||
if not force_refresh:
|
||||
data = r.get(watt_euros + "_" + str(sensor) + "_" + "by_id" + "_" +
|
||||
str(id1) + "_" + str(id2))
|
||||
if data:
|
||||
# If found in cache, return it
|
||||
return json.loads(data)
|
||||
|
||||
if id1 >= 0 and id2 >= 0 and id2 >= id1:
|
||||
data = (db.query(database.Measures)
|
||||
.filter(database.Measures.sensor_id == sensor,
|
||||
database.Measures.id >= id1,
|
||||
database.Measures.id < id2)
|
||||
.order_by(asc(database.Measures.timestamp))
|
||||
.all())
|
||||
elif id1 <= 0 and id2 <= 0 and id2 >= id1:
|
||||
data = (db.query(database.Measures)
|
||||
.filter_by(sensor_id=sensor)
|
||||
.order_by(desc(database.Measures.timestamp))
|
||||
.slice(-id2, -id1)
|
||||
.all())
|
||||
data.reverse()
|
||||
else:
|
||||
return None
|
||||
|
||||
if not data:
|
||||
data = None
|
||||
else:
|
||||
time1 = data[0].timestamp
|
||||
time2 = data[-1].timestamp
|
||||
if watt_euros == 'kwatthours' or watt_euros == 'euros':
|
||||
data = tools.energy(data)
|
||||
if watt_euros == 'euros':
|
||||
if data["night_rate"] != 0:
|
||||
night_rate = tools.watt_euros(0,
|
||||
'night',
|
||||
data['night_rate'],
|
||||
db)
|
||||
else:
|
||||
night_rate = 0
|
||||
if data["day_rate"] != 0:
|
||||
day_rate = tools.watt_euros(0,
|
||||
'day',
|
||||
data['day_rate'],
|
||||
db)
|
||||
else:
|
||||
day_rate = 0
|
||||
data = {"value": night_rate + day_rate}
|
||||
else:
|
||||
data = tools.to_dict(data)
|
||||
|
||||
# Store in cache
|
||||
r.set(watt_euros + "_" + str(sensor) + "_" + "by_id" + "_" +
|
||||
str(id1) + "_" + str(id2),
|
||||
json.dumps(data),
|
||||
time2 - time1)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def do_cache_group_id(sensor, watt_euros, id1, id2, step, db,
|
||||
timestep=config.get("default_timestep"),
|
||||
force_refresh=False):
|
||||
"""
|
||||
Computes the cache (if needed) for the API call
|
||||
/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_id/<id1:int>/<id2:int>/<step:int>
|
||||
|
||||
Returns the stored (or computed) data.
|
||||
"""
|
||||
r = redis.Redis(decode_responses=True)
|
||||
if not force_refresh:
|
||||
data = r.get(watt_euros + "_" + str(sensor) + "_" + "by_id" + "_" +
|
||||
str(id1) + "_" + str(id2) + "_" +
|
||||
str(step) + "_" + str(timestep))
|
||||
if data:
|
||||
# If found in cache, return it
|
||||
return json.loads(data)
|
||||
|
||||
steps = [i for i in range(id1, id2, step)]
|
||||
steps.append(id2)
|
||||
|
||||
if id1 >= 0 and id2 >= 0 and id2 >= id1:
|
||||
data = (db.query(database.Measures)
|
||||
.filter(database.Measures.sensor_id == sensor,
|
||||
database.Measures.id >= id1,
|
||||
database.Measures.id < id2)
|
||||
.order_by(asc(database.Measures.timestamp))
|
||||
.all())
|
||||
elif id1 <= 0 and id2 <= 0 and id2 >= id1:
|
||||
data = (db.query(database.Measures)
|
||||
.filter_by(sensor_id=sensor)
|
||||
.order_by(desc(database.Measures.timestamp))
|
||||
.slice(-id2, -id1)
|
||||
.all())
|
||||
data.reverse()
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
time2 = None
|
||||
if not data:
|
||||
data = [None for i in range(len(steps) - 1)]
|
||||
else:
|
||||
time1 = data[0].timestamp
|
||||
time2 = data[-1].timestamp
|
||||
data_dict = tools.to_dict(data)
|
||||
tmp = [[] for i in range(len(steps) - 1)]
|
||||
for i in data_dict:
|
||||
tmp[bisect.bisect_left(steps, i["id"]) - 1].append(i)
|
||||
|
||||
data = []
|
||||
for i in tmp:
|
||||
if len(i) == 0:
|
||||
data.append(None)
|
||||
continue
|
||||
|
||||
energy = tools.energy(i)
|
||||
if watt_euros == "watts":
|
||||
tmp_data = {"value": energy["value"] / (step * timestep) * 1000 * 3600,
|
||||
"day_rate": energy["day_rate"] / (step * timestep) * 1000 * 3600,
|
||||
"night_rate": energy["night_rate"] / (step * timestep) * 1000 * 3600}
|
||||
elif watt_euros == 'kwatthours':
|
||||
tmp_data = energy
|
||||
elif watt_euros == 'euros':
|
||||
if energy["night_rate"] != 0:
|
||||
night_rate = tools.watt_euros(0,
|
||||
'night',
|
||||
energy['night_rate'],
|
||||
db)
|
||||
else:
|
||||
night_rate = 0
|
||||
if energy["day_rate"] != 0:
|
||||
day_rate = tools.watt_euros(0,
|
||||
'day',
|
||||
energy['day_rate'],
|
||||
db)
|
||||
else:
|
||||
day_rate = 0
|
||||
tmp_data = {"value": night_rate + day_rate}
|
||||
data.append(tmp_data)
|
||||
if len(data) == 0:
|
||||
data = None
|
||||
if time2 is not None:
|
||||
# Store in cache
|
||||
if time2 < datetime.datetime.now().timestamp():
|
||||
# If new measures are to come, short lifetime (basically timestep)
|
||||
r.set(watt_euros + "_" + str(sensor) + "_" + "by_id" + "_" +
|
||||
str(id1) + "_" + str(id2) + "_" +
|
||||
str(step) + "_" + str(timestep),
|
||||
json.dumps(data),
|
||||
timestep)
|
||||
else:
|
||||
# Else, store for a greater lifetime (basically time2 - time1)
|
||||
r.set(watt_euros + "_" + str(sensor) + "_" + "by_id" + "_" +
|
||||
str(id1) + "_" + str(id2) + "_" +
|
||||
str(step) + "_" + str(timestep),
|
||||
json.dumps(data),
|
||||
time2 - time1)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def do_cache_times(sensor, watt_euros, time1, time2, db, force_refresh=False):
|
||||
"""
|
||||
Computes the cache (if needed) for the API call
|
||||
/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_time/<time1:float>/<time2:float>
|
||||
Returns the stored (or computed) data.
|
||||
"""
|
||||
r = redis.Redis(decode_responses=True)
|
||||
if not force_refresh:
|
||||
data = r.get(watt_euros + "_" + str(sensor) + "_" + "by_time" + "_" +
|
||||
str(time1) + "_" + str(time2))
|
||||
if data:
|
||||
# If found in cache, return it
|
||||
return json.loads(data)
|
||||
|
||||
data = (db.query(database.Measures)
|
||||
.filter(database.Measures.sensor_id == sensor,
|
||||
database.Measures.timestamp >= time1,
|
||||
database.Measures.timestamp < time2)
|
||||
.order_by(asc(database.Measures.timestamp))
|
||||
.all())
|
||||
|
||||
if not data:
|
||||
data = None
|
||||
else:
|
||||
if watt_euros == "kwatthours" or watt_euros == "euros":
|
||||
data = tools.energy(data)
|
||||
if watt_euros == "euros":
|
||||
data = {"value": (tools.watt_euros(0,
|
||||
'night',
|
||||
data['night_rate'],
|
||||
db) +
|
||||
tools.watt_euros(0,
|
||||
'day',
|
||||
data['day_rate'],
|
||||
db))}
|
||||
|
||||
else:
|
||||
data = tools.to_dict(data)
|
||||
|
||||
# Store in cache
|
||||
r.set(watt_euros + "_" + str(sensor) + "_" + "by_id" + "_" +
|
||||
str(time1) + "_" + str(time2),
|
||||
json.dumps(data),
|
||||
int(time2) - int(time1))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def do_cache_group_timestamp(sensor, watt_euros, time1, time2, step, db,
|
||||
force_refresh=True):
|
||||
"""
|
||||
Computes the cache (if needed) for the API call
|
||||
/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_time/<time1:float>/<time2:float>/<step:float>
|
||||
|
||||
Returns the stored (or computed) data.
|
||||
"""
|
||||
r = redis.Redis(decode_responses=True)
|
||||
if not force_refresh:
|
||||
data = r.get(watt_euros + "_" + str(sensor) + "_" + "by_time" + "_" +
|
||||
str(time1) + "_" + str(time2) + "_" + str(step))
|
||||
if data:
|
||||
# If found in cache, return it
|
||||
return json.loads(data)
|
||||
|
||||
steps = [i for i in numpy.arange(time1, time2, step)]
|
||||
steps.append(time2)
|
||||
|
||||
data = (db.query(database.Measures)
|
||||
.filter(database.Measures.sensor_id == sensor,
|
||||
database.Measures.timestamp
|
||||
.between(time1, time2))
|
||||
.order_by(asc(database.Measures.timestamp))
|
||||
.all())
|
||||
|
||||
if not data:
|
||||
data = [None for i in range(len(steps) - 1)]
|
||||
else:
|
||||
tmp = [[] for i in range(len(steps) - 1)]
|
||||
for i in data:
|
||||
index = bisect.bisect_left(steps, i.timestamp)
|
||||
if index > 0:
|
||||
index -= 1
|
||||
tmp[index].append(i)
|
||||
|
||||
data = []
|
||||
for i in tmp:
|
||||
if len(i) == 0:
|
||||
data.append(None)
|
||||
continue
|
||||
|
||||
energy = tools.energy(i)
|
||||
if watt_euros == "watts":
|
||||
tmp_data = {"value": energy["value"] / step * 1000 * 3600,
|
||||
"day_rate": energy["day_rate"] / step * 1000 * 3600,
|
||||
"night_rate": energy["night_rate"] / step * 1000 * 3600}
|
||||
elif watt_euros == 'kwatthours':
|
||||
tmp_data = energy
|
||||
elif watt_euros == 'euros':
|
||||
if energy["night_rate"] != 0:
|
||||
night_rate = tools.watt_euros(0,
|
||||
'night',
|
||||
energy['night_rate'],
|
||||
db)
|
||||
else:
|
||||
night_rate = 0
|
||||
if energy["day_rate"] != 0:
|
||||
day_rate = tools.watt_euros(0,
|
||||
'day',
|
||||
energy['day_rate'],
|
||||
db)
|
||||
else:
|
||||
day_rate = 0
|
||||
tmp_data = {"value": night_rate + day_rate}
|
||||
data.append(tmp_data)
|
||||
if len(data) == 0:
|
||||
data = None
|
||||
# Store in cache
|
||||
if time2 < datetime.datetime.now().timestamp():
|
||||
# If new measures are to come, short lifetime (basically timestep)
|
||||
r.setex(watt_euros + "_" + str(sensor) + "_" + "by_time" + "_" +
|
||||
str(time1) + "_" + str(time2) + "_" + str(step),
|
||||
json.dumps(data),
|
||||
int(step))
|
||||
else:
|
||||
# Else, store for a greater lifetime (basically time2 - time1)
|
||||
r.setex(watt_euros + "_" + str(sensor) + "_" + "by_time" + "_" +
|
||||
str(time1) + "_" + str(time2) + "_" + str(step),
|
||||
json.dumps(data),
|
||||
int(time2 - time1))
|
||||
|
||||
return data
|
80
CitizenWatt-Base/libcitizenwatt/config.py
Normal file
@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
import crypt
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from libcitizenwatt import tools
|
||||
|
||||
|
||||
def make_sure_path_exists(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
return False
|
||||
except OSError as exception:
|
||||
if exception.errno != errno.EEXIST:
|
||||
raise
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
class Config():
|
||||
def __init__(self, base_config_path="~/.config/citizenwatt/"):
|
||||
self.config_path = os.path.expanduser(base_config_path)
|
||||
self.config = {}
|
||||
self.load()
|
||||
|
||||
def as_dict(self):
|
||||
return self.config
|
||||
|
||||
def get(self, param):
|
||||
return self.config.get(param, False)
|
||||
|
||||
def set(self, param, value):
|
||||
self.config[param] = value
|
||||
|
||||
def initialize(self):
|
||||
self.set("max_returned_values", 500)
|
||||
self.set("database_type", "postgresql+psycopg2")
|
||||
self.set("username", "citizenwatt")
|
||||
self.set("password", "citizenwatt")
|
||||
self.set("database", "citizenwatt")
|
||||
self.set("host", "localhost")
|
||||
self.set("debug", False)
|
||||
self.set("url_energy_providers",
|
||||
"http://dev.citizenwatt.paris/providers/electricity_providers.json")
|
||||
self.set("salt", crypt.mksalt())
|
||||
self.set("named_fifo", "/tmp/sensor")
|
||||
self.set("default_timestep", 8)
|
||||
self.set("port", 8080)
|
||||
self.set("autoreload", False)
|
||||
self.save()
|
||||
|
||||
def load(self):
|
||||
try:
|
||||
folder_exists = make_sure_path_exists(self.config_path)
|
||||
if(folder_exists and
|
||||
os.path.isfile(self.config_path + "config.json")):
|
||||
initialized = True
|
||||
else:
|
||||
initialized = False
|
||||
except OSError:
|
||||
tools.warning("Unable to create ~/.config folder.")
|
||||
sys.exit(1)
|
||||
if not initialized:
|
||||
self.initialize()
|
||||
else:
|
||||
try:
|
||||
with open(self.config_path + "config.json", 'r') as fh:
|
||||
self.config = json.load(fh)
|
||||
except (ValueError, IOError):
|
||||
tools.warning("Config file could not be read.")
|
||||
sys.exit(1)
|
||||
|
||||
def save(self):
|
||||
try:
|
||||
with open(self.config_path + "config.json", 'w') as fh:
|
||||
fh.write(json.dumps(self.config))
|
||||
except IOError:
|
||||
tools.warning("Could not write config file.")
|
||||
sys.exit(1)
|
65
CitizenWatt-Base/libcitizenwatt/database.py
Normal file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
from sqlalchemy import Column, Float
|
||||
from sqlalchemy import ForeignKey, Integer, Text, VARCHAR
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class Sensor(Base):
|
||||
__tablename__ = "sensors"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(VARCHAR(255), unique=True)
|
||||
type_id = Column(Integer,
|
||||
ForeignKey("measures_types.id", ondelete="CASCADE"),
|
||||
nullable=False)
|
||||
measures = relationship("Measures", passive_deletes=True)
|
||||
last_timer = Column(Integer)
|
||||
type = relationship("MeasureType", lazy="joined")
|
||||
aes_key = Column(VARCHAR(255))
|
||||
base_address = Column(VARCHAR(30))
|
||||
|
||||
|
||||
class Measures(Base):
|
||||
__tablename__ = "measures"
|
||||
id = Column(Integer, primary_key=True)
|
||||
sensor_id = Column(Integer,
|
||||
ForeignKey("sensors.id", ondelete="CASCADE"),
|
||||
nullable=False)
|
||||
value = Column(Float)
|
||||
timestamp = Column(Integer, index=True)
|
||||
night_rate = Column(Integer) # Boolean, 1 if night_rate
|
||||
|
||||
|
||||
class Provider(Base):
|
||||
__tablename__ = "providers"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(VARCHAR(length=255), unique=True)
|
||||
type_id = Column(Integer,
|
||||
ForeignKey("measures_types.id", ondelete="CASCADE"),
|
||||
nullable=False)
|
||||
day_slope_watt_euros = Column(Float)
|
||||
day_constant_watt_euros = Column(Float)
|
||||
night_slope_watt_euros = Column(Float)
|
||||
night_constant_watt_euros = Column(Float)
|
||||
current = Column(Integer)
|
||||
threshold = Column(Integer)
|
||||
|
||||
|
||||
class MeasureType(Base):
|
||||
__tablename__ = "measures_types"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(VARCHAR(255), unique=True)
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
id = Column(Integer, primary_key=True)
|
||||
login = Column(VARCHAR(length=255), unique=True)
|
||||
password = Column(Text)
|
||||
is_admin = Column(Integer)
|
||||
# Stored as seconds since beginning of day
|
||||
start_night_rate = Column(Integer)
|
||||
end_night_rate = Column(Integer)
|
108
CitizenWatt-Base/libcitizenwatt/tools.py
Normal file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
import numpy
|
||||
import os
|
||||
import sys
|
||||
|
||||
from libcitizenwatt import database
|
||||
|
||||
|
||||
def warning(*objs):
|
||||
"""Write warnings to stderr"""
|
||||
print("WARNING: ", *objs, file=sys.stderr)
|
||||
|
||||
|
||||
def to_dict(model):
|
||||
"""Returns a JSON representation of an SQLAlchemy-backed object.
|
||||
|
||||
Returns a timestamp for DateTime fields, to be easily JSON serializable.
|
||||
|
||||
TODO : Use runtime inspection API
|
||||
From https://zato.io/blog/posts/converting-sqlalchemy-objects-to-json.html
|
||||
"""
|
||||
if isinstance(model, list):
|
||||
return [to_dict(i) for i in model]
|
||||
else:
|
||||
dict = {}
|
||||
dict['id'] = getattr(model, 'id')
|
||||
|
||||
for col in model._sa_class_manager.mapper.mapped_table.columns:
|
||||
if str(col.type) == "TIMESTAMP":
|
||||
dict[col.name] = getattr(model, col.name).timestamp()
|
||||
else:
|
||||
dict[col.name] = getattr(model, col.name)
|
||||
|
||||
return dict
|
||||
|
||||
|
||||
def last_day(month, year):
|
||||
"""Returns the last day of month <month> of year <year>."""
|
||||
if month in [1, 3, 5, 7, 8, 10, 12]:
|
||||
return 31
|
||||
elif month == 2:
|
||||
if year % 4 == 0 and (not year % 100 or year % 400):
|
||||
return 29
|
||||
else:
|
||||
return 28
|
||||
else:
|
||||
return 30
|
||||
|
||||
|
||||
def energy(powers, default_timestep=8):
|
||||
"""Compute the energy associated to a list of measures (in W)
|
||||
and associated timestamps (in s).
|
||||
"""
|
||||
energy = {'night_rate': 0, 'day_rate': 0, 'value': 0}
|
||||
if len(powers) == 1:
|
||||
if powers[0].night_rate == 1:
|
||||
energy["night_rate"] = (powers[0].value / 1000 *
|
||||
default_timestep / 3600)
|
||||
else:
|
||||
energy["day_rate"] = (powers[0].value / 1000 *
|
||||
default_timestep / 3600)
|
||||
energy['value'] = energy['day_rate'] + energy['night_rate']
|
||||
else:
|
||||
x = []
|
||||
day_rate = []
|
||||
night_rate = []
|
||||
for i in powers:
|
||||
x.append(i.timestamp)
|
||||
if i.night_rate == 1:
|
||||
night_rate.append(i.value)
|
||||
day_rate.append(0)
|
||||
else:
|
||||
day_rate.append(i.value)
|
||||
night_rate.append(0)
|
||||
energy["night_rate"] = numpy.trapz(night_rate, x) / 1000 / 3600
|
||||
energy["day_rate"] = numpy.trapz(day_rate, x) / 1000 / 3600
|
||||
energy['value'] = energy['day_rate'] + energy['night_rate']
|
||||
return energy
|
||||
|
||||
|
||||
def watt_euros(energy_provider, tariff, consumption, db):
|
||||
if energy_provider != 0:
|
||||
provider = (db.query(database.Provider)
|
||||
.filter_by(id=energy_provider)
|
||||
.first())
|
||||
else:
|
||||
provider = (db.query(database.Provider)
|
||||
.filter_by(current=1)
|
||||
.first())
|
||||
if not provider:
|
||||
data = None
|
||||
else:
|
||||
if tariff == "night":
|
||||
data = provider.night_slope_watt_euros * consumption
|
||||
elif tariff == "day":
|
||||
data = provider.day_slope_watt_euros * consumption
|
||||
else:
|
||||
data = None
|
||||
return data
|
||||
|
||||
|
||||
def update_base_address(base_address):
|
||||
"""Update the address of the base stored in
|
||||
~/.config/citizenwatt/base_address
|
||||
"""
|
||||
path = os.path.expanduser("~/.config/citizenwatt/base_address")
|
||||
with open(path, "w+") as fh:
|
||||
fh.write(str(base_address))
|
1
CitizenWatt-Base/post_update.sh
Executable file
@ -0,0 +1 @@
|
||||
#!/bin/sh
|
109
CitizenWatt-Base/process.py
Executable file
@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
from libcitizenwatt import database
|
||||
from libcitizenwatt import tools
|
||||
from Crypto.Cipher import AES
|
||||
from libcitizenwatt.config import Config
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
def get_rate_type(db):
|
||||
"""Returns "day" or "night" according to current time
|
||||
"""
|
||||
user = db.query(database.User).filter_by(is_admin=1).first()
|
||||
now = datetime.datetime.now()
|
||||
now = 3600 * now.hour + 60 * now.minute
|
||||
if user is None:
|
||||
return -1
|
||||
elif user.end_night_rate > user.start_night_rate:
|
||||
if now > user.start_night_rate and now < user.end_night_rate:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
else:
|
||||
if now > user.start_night_rate or now < user.end_night_rate:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def get_cw_sensor():
|
||||
"""Returns the citizenwatt sensor object or None"""
|
||||
db = create_session()
|
||||
sensor = (db.query(database.Sensor)
|
||||
.filter_by(name="CitizenWatt")
|
||||
.first())
|
||||
db.close()
|
||||
return sensor
|
||||
|
||||
|
||||
# Configuration
|
||||
config = Config()
|
||||
|
||||
# DB initialization
|
||||
database_url = (config.get("database_type") + "://" + config.get("username") +
|
||||
":" + config.get("password") + "@" + config.get("host") + "/" +
|
||||
config.get("database"))
|
||||
engine = create_engine(database_url, echo=config.get("debug"))
|
||||
create_session = sessionmaker(bind=engine)
|
||||
database.Base.metadata.create_all(engine)
|
||||
|
||||
sensor = get_cw_sensor()
|
||||
while not sensor or not sensor.aes_key:
|
||||
tools.warning("Install is not complete ! " +
|
||||
"Visit http://citizenwatt.local first.")
|
||||
time.sleep(1)
|
||||
sensor = get_cw_sensor()
|
||||
|
||||
key = json.loads(sensor.aes_key)
|
||||
key = struct.pack("<16B", *key)
|
||||
|
||||
|
||||
try:
|
||||
assert(stat.S_ISFIFO(os.stat(config.get("named_fifo")).st_mode))
|
||||
except (AssertionError, FileNotFoundError):
|
||||
sys.exit("Unable to open fifo " + config.get("named_fifo") + ".")
|
||||
|
||||
try:
|
||||
with open(config.get("named_fifo"), 'rb') as fifo:
|
||||
while True:
|
||||
measure = fifo.read(16)
|
||||
print("New encrypted packet:" + str(measure))
|
||||
|
||||
decryptor = AES.new(key, AES.MODE_ECB)
|
||||
measure = decryptor.decrypt(measure)
|
||||
measure = struct.unpack("<HHHLlH", measure)
|
||||
print("New incoming measure:" + str(measure))
|
||||
|
||||
power = measure[0]
|
||||
voltage = measure[1]
|
||||
battery = measure[2]
|
||||
timer = measure[3]
|
||||
|
||||
if(sensor.last_timer and sensor.last_timer > 0 and
|
||||
sensor.last_timer < 4233600000 and
|
||||
timer < sensor.last_timer):
|
||||
tools.warning("Invalid timer in the last packet, skipping it")
|
||||
else:
|
||||
db = create_session()
|
||||
measure_db = database.Measures(sensor_id=sensor.id,
|
||||
value=power,
|
||||
timestamp=datetime.datetime.now().timestamp(),
|
||||
night_rate=get_rate_type(db))
|
||||
db.add(measure_db)
|
||||
sensor.last_timer = timer
|
||||
(db.query(database.Sensor)
|
||||
.filter_by(name="CitizenWatt")
|
||||
.update({"last_timer": sensor.last_timer}))
|
||||
db.commit()
|
||||
print("Saved successfully.")
|
||||
except KeyboardInterrupt:
|
||||
pass
|
109
CitizenWatt-Base/receive.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <RF24.h>
|
||||
|
||||
volatile sig_atomic_t flag = 0;
|
||||
void quit(int sig) {
|
||||
flag = 1;
|
||||
}
|
||||
|
||||
const bool DEBUG = true;
|
||||
|
||||
// Speed for the nrf module
|
||||
// RF24_250KBPS / RF24_1MBPS / RF24_2MBPS
|
||||
// Reduce it to improve reliability
|
||||
const rf24_datarate_e NRF_SPEED = RF24_1MBPS;
|
||||
|
||||
// PreAmplifier level for the nRF
|
||||
// Lower this to reduce power consumption. This will reduce range.
|
||||
const rf24_pa_dbm_e NRF_PA_LEVEL = RF24_PA_LOW;
|
||||
|
||||
// Channel for the nrf module
|
||||
// 76 is default safe channel in RF24
|
||||
const int NRF_CHANNEL = 0x4c;
|
||||
|
||||
const uint64_t default_addr = 0xE056D446D0LL;
|
||||
|
||||
//RF24 radio(RPI_V2_GPIO_P1_15, RPI_V2_GPIO_P1_24, BCM2835_SPI_SPEED_8MHZ);
|
||||
RF24 radio("/dev/spidev0.0",8000000 , 25);
|
||||
|
||||
// Named pipe
|
||||
int fd;
|
||||
char * myfifo = "/tmp/sensor";
|
||||
|
||||
int main() {
|
||||
uint8_t payload[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// Create FIFO
|
||||
mkfifo(myfifo, 0666);
|
||||
signal(SIGINT, quit);
|
||||
|
||||
// Open FIFO - while wait here until another thread opens the same fifo
|
||||
fd = open(myfifo, O_WRONLY);
|
||||
|
||||
// Get the address to listen on
|
||||
std::ifstream config_addr;
|
||||
config_addr.open("~/.config/citizenwatt/base_address", std::ios::in);
|
||||
uint64_t addr;
|
||||
if (config_addr.is_open()) {
|
||||
config_addr >> addr;
|
||||
config_addr.close();
|
||||
}
|
||||
else {
|
||||
addr = default_addr;
|
||||
}
|
||||
|
||||
// Initialize nRF
|
||||
radio.begin();
|
||||
// Max number of retries and max delay between them
|
||||
radio.setRetries(15, 15);
|
||||
radio.setChannel(NRF_CHANNEL);
|
||||
// Reduce payload size to improve reliability
|
||||
radio.setPayloadSize(16);
|
||||
// Set the datarate
|
||||
radio.setDataRate(NRF_SPEED);
|
||||
// Use the largest CRC
|
||||
radio.setCRCLength(RF24_CRC_16);
|
||||
// Ensure auto ACK is enabled
|
||||
radio.setAutoAck(1);
|
||||
// Use the best PA level
|
||||
radio.setPALevel(NRF_PA_LEVEL);
|
||||
// Open reading pipe
|
||||
radio.openReadingPipe(1, addr);
|
||||
|
||||
radio.startListening();
|
||||
|
||||
while(1) {
|
||||
if(flag) {
|
||||
close(fd);
|
||||
unlink(myfifo);
|
||||
std::cout << "Exiting…\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(radio.available()) {
|
||||
radio.read(&payload, sizeof(payload));
|
||||
|
||||
if(DEBUG) {
|
||||
std::cout << "Received : ";
|
||||
for(int i=0; i<sizeof(payload); i++) {
|
||||
std::cout << std::hex << (int) payload[i];
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
// Send to fifo
|
||||
write(fd, payload, sizeof(payload));
|
||||
// Maybe needed ? fflush(fd)
|
||||
}
|
||||
sleep(2);
|
||||
}
|
||||
close(fd);
|
||||
}
|
20
CitizenWatt-Base/startup.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Starting the webserver…"
|
||||
screen -dmS visu && screen -S visu -p 0 -X stuff "while true; do python3 visu.py; done$(printf \\r)"
|
||||
|
||||
echo "Starting receive script…"
|
||||
screen -dmS receive && screen -S receive -p 0 -X stuff "while true; do ./receive; done$(printf \\r)"
|
||||
echo "Done !\n"
|
||||
sleep 0.2
|
||||
echo "Starting processing script…"
|
||||
screen -dmS process && screen -S process -p 0 -X stuff "while true; do python3 process.py; done$(printf \\r)"
|
||||
echo "Done !\n"
|
||||
|
||||
while ! curl -s --head http://localhost:8080 2>&1 > /dev/null; do
|
||||
echo "Webserver is starting…"
|
||||
sleep 1
|
||||
done
|
||||
echo "Webserver started !\n"
|
||||
|
||||
echo "Ready to start !"
|
425
CitizenWatt-Base/static/css/normalize.css
vendored
Normal file
@ -0,0 +1,425 @@
|
||||
/*! normalize.css v3.0.1 | MIT License | git.io/normalize */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox.
|
||||
* Correct `block` display not defined for `main` in IE 11.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9/10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow not hidden in IE 9/10/11.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
*/
|
||||
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contain overflow in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address odd `em`-unit font size rendering in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
||||
* styling of `select`, unless a `border` property is set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 1. Correct color not being inherited.
|
||||
* Known issue: affects color of disabled elements.
|
||||
* 2. Correct font properties not being inherited.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
||||
* Correct `select` style inheritance in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's recommended that you don't attempt to style these elements.
|
||||
* Firefox's implementation doesn't respect box-sizing, padding, or width.
|
||||
*
|
||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||
* 2. Remove excess padding in IE 8/9/10.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
|
||||
* `font-size` values of the `input`, it causes the cursor style of the
|
||||
* decrement button to change from `default` to `text`.
|
||||
*/
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
||||
* padding (and `textfield` appearance).
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9/10/11.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't inherit the `font-weight` (applied by a rule above).
|
||||
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
|
||||
*/
|
||||
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
896
CitizenWatt-Base/static/css/style.css
Normal file
@ -0,0 +1,896 @@
|
||||
|
||||
/******************************************************************************
|
||||
* 0. Font faces
|
||||
*/
|
||||
|
||||
/*
|
||||
* 0.1 Open Sans
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src:
|
||||
local('Open Sans'),
|
||||
local('OpenSans'),
|
||||
url('../font/OpenSans-Regular.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src:
|
||||
local('Open Sans Light'),
|
||||
local('OpenSans-Light'),
|
||||
url('../font/OpenSans-Light.ttf');
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src:
|
||||
local('Open Sans Semibold'),
|
||||
local('OpenSans-Semibold'),
|
||||
url('../font/OpenSans-Bold.ttf');
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src:
|
||||
local('Open Sans Italic'),
|
||||
local('OpenSans-Italic'),
|
||||
url('../font/OpenSans-Italic.ttf');
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src:
|
||||
local('Open Sans Bold Italic'),
|
||||
local('OpenSans-BoldItalic'),
|
||||
url('../font/OpenSans-BoldItalic.ttf');
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/*
|
||||
* 0.2 Alex Brush
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Alex Brush';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Alex Brush'), local('AlexBrush'), url('../font/AlexBrush-Regular.ttf');
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 1. Styles de base
|
||||
*/
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #231f20;
|
||||
color: #d6d6d6;
|
||||
font-family: "Open Sans", sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #e3834f;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
article {
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
margin: 3em 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
font-weight: 200;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 75%;
|
||||
margin: auto;
|
||||
padding-bottom: 4em;
|
||||
color: #b3abab;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
main {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 2. Classes
|
||||
*/
|
||||
|
||||
/*
|
||||
* 2.1 Color classes
|
||||
*/
|
||||
|
||||
.yellow {
|
||||
color: #ffd35d;
|
||||
}
|
||||
|
||||
.orange {
|
||||
color: #ffb43f;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: #ff7b33;
|
||||
}
|
||||
|
||||
.dark-yellow {
|
||||
color: #
|
||||
}
|
||||
|
||||
.dark-orange {
|
||||
color: #f59b20;
|
||||
}
|
||||
|
||||
.dark-red {
|
||||
color: #f2541b;
|
||||
}
|
||||
|
||||
.blurry {
|
||||
text-shadow: 0 0 0.5em;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.no-day {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.empty-day {
|
||||
background-color: #363032 !important;
|
||||
}
|
||||
|
||||
.dark-red-day {
|
||||
background-color: #f2541b !important;
|
||||
}
|
||||
|
||||
.red-day {
|
||||
background-color: #ff7b33 !important;
|
||||
}
|
||||
|
||||
.orange-day {
|
||||
background-color: #ffb43f !important;
|
||||
}
|
||||
|
||||
.yellow-day {
|
||||
background-color: #ffd35d !important;
|
||||
}
|
||||
|
||||
.dark-blue {
|
||||
color: #2332ff;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: #196cff;
|
||||
}
|
||||
|
||||
.light-blue {
|
||||
color: #5d92ff;
|
||||
}
|
||||
|
||||
.dark-blue-day {
|
||||
background-color: #2332ff !important;
|
||||
}
|
||||
|
||||
.blue-day {
|
||||
background-color: #196cff !important;
|
||||
}
|
||||
|
||||
.light-blue-day {
|
||||
background-color: #5d92ff !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* 2.2 Dialog boxes
|
||||
*/
|
||||
|
||||
.dialog-err {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
border-left: 3px solid #cc624d;
|
||||
|
||||
margin: 10px 0;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.dialog-err h4 {
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
color: #cc624d;
|
||||
}
|
||||
|
||||
.dialog-err p {
|
||||
margin: 3px 0;
|
||||
font-weight: 200;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/*
|
||||
* 2.x Other
|
||||
*/
|
||||
|
||||
.clearfix {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 3. Header
|
||||
*/
|
||||
|
||||
header {
|
||||
background-color: #5f5557;
|
||||
border-bottom: 5px solid #928688;
|
||||
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
-webkit-flex-flow: row wrap;
|
||||
flex-flow: row wrap;
|
||||
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between;
|
||||
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-logo img {
|
||||
margin: 2px 0;
|
||||
margin-left: 3em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/*
|
||||
* 3.1. Menu
|
||||
*/
|
||||
|
||||
#menu {
|
||||
font-size: 1.2em;
|
||||
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
-webkit-flex-flow: row wrap;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
|
||||
#menu>a {
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
font-weight: 200;
|
||||
border-bottom: 3px solid #5f5557;
|
||||
}
|
||||
|
||||
#menu a:hover {
|
||||
text-decoration: none;
|
||||
border-bottom: 3px solid #928688;
|
||||
}
|
||||
|
||||
#menu a.active {
|
||||
border-bottom: 3px solid #e3834f;
|
||||
}
|
||||
|
||||
#menu a.active:hover {
|
||||
border-bottom: 3px solid #ad7556;
|
||||
}
|
||||
|
||||
/*
|
||||
* 3.2. Rate logo
|
||||
*/
|
||||
|
||||
.rate-logo {
|
||||
cursor: help;
|
||||
position: relative;
|
||||
margin-left: 2em;
|
||||
margin-right: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rate-logo img {
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.rate-logo span {
|
||||
position: absolute;
|
||||
width: 8em;
|
||||
left: 50%;
|
||||
margin-left: -4em;
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
border-radius: 3px;
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rate-logo:hover span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 4. Footer
|
||||
*/
|
||||
|
||||
|
||||
footer {
|
||||
padding: 0;
|
||||
|
||||
background-color: #5f5557;
|
||||
border-top: 5px solid #928688;
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
font-size: 0.7em;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
.pre-footer {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 5. Overview
|
||||
*/
|
||||
|
||||
#overview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#overview div {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#now, #day, #week {
|
||||
font-size: 3em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 6. Graphe
|
||||
*/
|
||||
|
||||
|
||||
#graph {
|
||||
background-color: #363032;
|
||||
border-bottom: 3px solid #52484b;
|
||||
margin-bottom: 1em;
|
||||
position: relative;
|
||||
height: 300px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* 6.1. Values
|
||||
*/
|
||||
|
||||
#graph_values_wrapper {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
#graph_values {
|
||||
display: -webkit-inline-flex;
|
||||
display: inline-flex;
|
||||
|
||||
-webkit-flex-flow: row-reverse nowrap;
|
||||
flex-flow: row-reverse nowrap;
|
||||
|
||||
-webkit-justify-content: flex-end;
|
||||
justify-content: flex-end;
|
||||
|
||||
-webkit-align-items: flex-end;
|
||||
align-items: flex-end;
|
||||
|
||||
height: 100%;
|
||||
min-width: 100%;
|
||||
|
||||
}
|
||||
|
||||
#graph_values .rect {
|
||||
display: block;
|
||||
margin-left: 2px;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#graph_values .rect {
|
||||
margin-left: 2px;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#graph_values .rect.undefined {
|
||||
background-color: #5C2D2D;
|
||||
}
|
||||
|
||||
#graph_values .rect.animated {
|
||||
transition: width 1s;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#graph_values .rect-info {
|
||||
color: #ddd;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
border-radius: 3px;
|
||||
padding: 3px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
width: 7em;
|
||||
left: 50%;
|
||||
margin-left: -3.5em;
|
||||
|
||||
z-index: 20;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#graph_values .rect:hover .rect-info {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#graph_values .rect:hover {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
#graph_values .rect-color {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/*
|
||||
* 6.2. Axis
|
||||
*/
|
||||
|
||||
|
||||
#graph hr {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
#graph hr.absolute-graduation-hr {
|
||||
background-color: rgba(255,0,0,0.5);
|
||||
}
|
||||
|
||||
#graph_vertical_axis span {
|
||||
position:absolute;
|
||||
z-index: 20;
|
||||
/*text-shadow: 1px 1px 0px rgba( 0, 0, 0,0.5),
|
||||
-1px -1px 0px rgba(255,255,255,0.5);
|
||||
background-color: #363032;*/
|
||||
}
|
||||
|
||||
/*
|
||||
* 6.3. Loading
|
||||
*/
|
||||
|
||||
#graph_loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
-webkit-flex-flow: column wrap;
|
||||
flex-flow: column wrap;
|
||||
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#graph_loading img {
|
||||
width: 3em;
|
||||
animation-name: rotate;
|
||||
-webkit-animation-name: rotate;
|
||||
|
||||
animation-duration: 5s;
|
||||
-webkit-animation-duration: 5s;
|
||||
|
||||
animation-iteration-count: infinite;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
|
||||
animation-timing-function: linear;
|
||||
-webkit-animation-timing-function: linear;
|
||||
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
/*
|
||||
* 6.4. Prev/Next
|
||||
*/
|
||||
|
||||
#prev {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -2.5em;
|
||||
width: 2em;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
border: none;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
#prev:hover {
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
#next {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: -2.5em;
|
||||
width: 2em;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
border: none;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
#next:hover {
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 780px) {
|
||||
#prev {
|
||||
left:0;
|
||||
}
|
||||
#next {
|
||||
right:0;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 7. Échelle du graphe
|
||||
*/
|
||||
|
||||
#scale {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
-webkit-flex-flow: rowcolumn wrap;
|
||||
flex-flow: row wrap;
|
||||
|
||||
-webkit-justify-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#scale button {
|
||||
background: #363032;
|
||||
border: none;
|
||||
border-bottom: 3px solid #52484b;
|
||||
}
|
||||
|
||||
#scale button.active {
|
||||
color: white;
|
||||
border-bottom: 3px solid #e3834f;
|
||||
}
|
||||
|
||||
#scale button:hover {
|
||||
color: white;
|
||||
background: #60565A;
|
||||
}
|
||||
|
||||
#unit-energy {
|
||||
margin-left: auto;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 8. Menu blocks
|
||||
*/
|
||||
|
||||
.menu {
|
||||
text-transform: uppercase;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.menu img {
|
||||
margin: 3px 0.5em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.menu h1 {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.menu a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.menu>a {
|
||||
color: white;
|
||||
display: block;
|
||||
margin: 0.5em 5em;
|
||||
line-height: 3em;
|
||||
}
|
||||
|
||||
.menu>a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.menu a img {
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
|
||||
.right-column, .left-column {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 780px) {
|
||||
.right-column {
|
||||
width: 100%;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
width: 100%;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 9. Form
|
||||
*/
|
||||
|
||||
|
||||
input, select {
|
||||
color: #444;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
padding: 4px 6px;
|
||||
background-color: #F8F8F8;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
box-shadow: 0 0 2px 2px rgba(185, 103, 59, 0.8);
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
background-color: #BF6C3F;
|
||||
text-transform: uppercase;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.form-item {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
-webkit-flex-flow: row wrap;
|
||||
flex-flow: row wrap;
|
||||
|
||||
-webkit-justify-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-item label {
|
||||
width: 275px;
|
||||
}
|
||||
|
||||
.form-item input {
|
||||
}
|
||||
|
||||
.form-help {
|
||||
text-transform: none;
|
||||
color: #888;
|
||||
margin: 0 1em;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 10. Tableaux
|
||||
*/
|
||||
|
||||
table {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
padding: 0.5em;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
table tr td:first-child, table tr th:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
table tr td:last-child, table tr th:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
table tr:first-child td, table tr:first-child th {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table tr:last-child td, table tr:last-child th {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 11. Coming soon
|
||||
*/
|
||||
|
||||
.coming-soon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
-webkit-flex-flow: column wrap;
|
||||
flex-flow: column wrap;
|
||||
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
.coming-soon img {
|
||||
width: 10em;
|
||||
animation-name: rotate;
|
||||
-webkit-animation-name: rotate;
|
||||
|
||||
animation-duration: 5s;
|
||||
-webkit-animation-duration: 5s;
|
||||
|
||||
animation-iteration-count: infinite;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
|
||||
animation-timing-function: linear;
|
||||
-webkit-animation-timing-function: linear;
|
||||
}
|
||||
|
||||
.coming-soon span {
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
color: white;
|
||||
font-size: 2em;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 12. Content
|
||||
*/
|
||||
|
||||
.content {
|
||||
color: #d7d7d7;
|
||||
max-width: 50em;
|
||||
margin: auto;
|
||||
padding: 0 2em;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 13. Animations
|
||||
*/
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotate {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* X. Autre
|
||||
*/
|
||||
|
||||
#page {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 780px) {
|
||||
#menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
BIN
CitizenWatt-Base/static/font/OpenSans-Bold.ttf
Normal file
BIN
CitizenWatt-Base/static/font/OpenSans-BoldItalic.ttf
Normal file
BIN
CitizenWatt-Base/static/font/OpenSans-Italic.ttf
Normal file
BIN
CitizenWatt-Base/static/font/OpenSans-Light.ttf
Normal file
BIN
CitizenWatt-Base/static/font/OpenSans-Regular.ttf
Normal file
118
CitizenWatt-Base/static/img/bill.svg
Normal file
@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="bill.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="48.713158"
|
||||
inkscape:cy="17.485184"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
r="54.780827"
|
||||
cy="-3386.084"
|
||||
cx="-289.85474"
|
||||
id="path12478"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate" />
|
||||
<g
|
||||
id="g12537"
|
||||
transform="matrix(1.0122887,0,0,1.0122887,-59.921025,43.242058)"
|
||||
style="stroke:#ffffff;stroke-opacity:1">
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -228.00257,-3405.6945 -18.61931,0"
|
||||
id="path12539"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -250.55045,-3411.1013 48.61931,0 0,48.6193 -48.61931,0 z"
|
||||
id="path12541"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12543"
|
||||
d="m -233.18114,-3401.2302 -13.44074,0"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -233.18114,-3397.3016 -13.44074,0"
|
||||
id="path12545"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -206.93114,-3380.5159 -39.69074,0"
|
||||
id="path12547"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -217.40749,-3386.2036 0,16.6818 10.24715,0 0,-16.6818 -39.09242,0 0,11.3187 39.09242,0"
|
||||
id="path12549"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
88
CitizenWatt-Base/static/img/community.svg
Normal file
@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="store.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="9.6000389"
|
||||
inkscape:cy="46.119405"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<circle
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="circle5935"
|
||||
cx="52.162094"
|
||||
cy="42.150745"
|
||||
r="32" />
|
||||
<g
|
||||
id="g5947"
|
||||
transform="matrix(4.2623279,0,0,4.2623279,154.50244,833.91008)">
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
d="m -21.334898,-183.20291 c -0.504859,0.42348 -2.70551,2.4371 -6.265393,-0.37494 3.987068,1.73409 4.74651,-0.89048 4.74651,-0.89048 l -0.768675,-0.67444 2.893713,-0.27223 0.150317,2.86749 z"
|
||||
id="path5939"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5945"
|
||||
d="m -26.375442,-188.57719 c 0.597261,-0.27838 3.24409,-1.65382 5.954864,1.98377 -3.402397,-2.70693 -4.81525,-0.36835 -4.81525,-0.36835 l 0.567925,0.85041 -2.86557,-0.486 0.596966,-2.80868 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
99
CitizenWatt-Base/static/img/contact.svg
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="contact.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="10.231015"
|
||||
inkscape:cy="21.94947"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="circle5950"
|
||||
cx="-289.85474"
|
||||
cy="-3386.084"
|
||||
r="54.780827" />
|
||||
<g
|
||||
transform="matrix(17.163012,0,0,17.163012,-463.70373,-1511.7869)"
|
||||
id="g5960">
|
||||
<circle
|
||||
r="1.3624809"
|
||||
cy="-109.19511"
|
||||
cx="10.107313"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:0.16933334;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="circle5952"
|
||||
d="m 11.469794,-109.19511 a 1.3624809,1.3624809 0 0 1 -1.362481,1.36248 1.3624809,1.3624809 0 0 1 -1.3624807,-1.36248 1.3624809,1.3624809 0 0 1 1.3624807,-1.36248 1.3624809,1.3624809 0 0 1 1.362481,1.36248 z" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5956"
|
||||
d="m 11.35146,-108.7036 c -0.711806,0.26399 -1.4912772,0.0711 -1.4912772,0.0711 0,0 0.4965642,0.52859 1.0854922,0.52141"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.16933334;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5958"
|
||||
d="m 10.241133,-110.03034 c 0.162511,0.17267 0.02031,0.55864 0.02031,0.55864"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.16933334;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
97
CitizenWatt-Base/static/img/data.svg
Normal file
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="data.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="49.640088"
|
||||
inkscape:cy="23.737821"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<circle
|
||||
r="32.000004"
|
||||
cy="42.150745"
|
||||
cx="52.162083"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12199" />
|
||||
<g
|
||||
id="g12205"
|
||||
transform="matrix(0.5461791,0,0,0.5461791,-433.25723,1714.5672)">
|
||||
<rect
|
||||
y="-3064.4236"
|
||||
x="867.5"
|
||||
height="25.357143"
|
||||
width="11.071428"
|
||||
id="rect12207"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="rect12209"
|
||||
width="11.071428"
|
||||
height="45.000244"
|
||||
x="882.32147"
|
||||
y="-3084.0664" />
|
||||
<rect
|
||||
y="-3057.9951"
|
||||
x="897.14288"
|
||||
height="18.928434"
|
||||
width="11.071428"
|
||||
id="rect12211"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
100
CitizenWatt-Base/static/img/day.svg
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="day.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="48.713158"
|
||||
inkscape:cy="17.485184"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<circle
|
||||
r="16"
|
||||
cy="58.150742"
|
||||
cx="36.162098"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12308" />
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -265.75382,-3396.9445 -48.61931,0"
|
||||
id="path12328"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -314.37313,-3411.1013 9.72386,0 3.13881,-5.8805 3.13881,5.8805 16.61636,0 3.13881,-5.8805 3.13879,5.8805 9.72387,0 0,48.6193 -48.61931,0 z"
|
||||
id="path12330"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25.81082535px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
x="-297.20145"
|
||||
y="-3371.0356"
|
||||
id="text12332"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12334"
|
||||
x="-297.20145"
|
||||
y="-3371.0356">1</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
83
CitizenWatt-Base/static/img/help.svg
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="help.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="49.640088"
|
||||
inkscape:cy="23.737821"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<circle
|
||||
r="32.000004"
|
||||
cy="42.150745"
|
||||
cx="52.162094"
|
||||
id="path12063"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.33991241px;line-height:125%;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
x="39.080994"
|
||||
y="58.733574"
|
||||
id="text12119"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12121"
|
||||
x="39.080994"
|
||||
y="58.733574">?</tspan></text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
111
CitizenWatt-Base/static/img/install.svg
Normal file
@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="install.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="63.874369"
|
||||
inkscape:cy="37.577007"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-global="false" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<g
|
||||
id="g12205"
|
||||
transform="matrix(0.5461791,0,0,0.5461791,-433.25723,1714.5672)">
|
||||
<rect
|
||||
y="-3064.4236"
|
||||
x="867.5"
|
||||
height="25.357143"
|
||||
width="11.071428"
|
||||
id="rect12207"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="rect12209"
|
||||
width="11.071428"
|
||||
height="45.000244"
|
||||
x="882.32147"
|
||||
y="-3084.0664" />
|
||||
<rect
|
||||
y="-3057.9951"
|
||||
x="897.14288"
|
||||
height="18.928434"
|
||||
width="11.071428"
|
||||
id="rect12211"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
</g>
|
||||
<circle
|
||||
r="32.000004"
|
||||
cy="42.150745"
|
||||
cx="52.162083"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12199"
|
||||
d="M 84.162086,42.150745 A 32.000004,32.000004 0 0 1 52.162083,74.150749 32.000004,32.000004 0 0 1 20.162079,42.150745 32.000004,32.000004 0 0 1 52.162083,10.150742 32.000004,32.000004 0 0 1 84.162086,42.150745 Z" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 64.580733,32.345095 -3.049297,5.360259 -6.289981,0.04057 -3.17983,-5.42769 3.083411,-5.419325 c -0.834769,-0.470407 -4.088972,2.694094 -4.958965,4.326285 -0.870995,1.634086 0.901287,4.115451 0.284309,6.740007 -0.571345,2.430464 -8.224819,16.606232 -9.395806,18.767912 l -0.643753,0.915452 2.436337,1.28156 2.327373,1.46876 0.468496,-1.009479 c 1.279091,-2.082809 9.738525,-15.81536 11.558685,-17.526319 1.964441,-1.846599 5.144471,-1.468744 6.12413,-3.040095 0.979664,-1.571346 2.423071,-5.95362 1.553801,-6.455493 -0.005,-0.003 -0.31374,-0.01941 -0.3189,-0.0224 z"
|
||||
id="path15434"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccsscccccsssc" />
|
||||
<path
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 52.206097,40.690541 1.933092,1.116071 -8.363397,12.48583 -0.201042,-0.116072 z"
|
||||
id="rect15991"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
130
CitizenWatt-Base/static/img/loading.svg
Normal file
@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="loading.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="48.713158"
|
||||
inkscape:cy="17.485184"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
r="54.780827"
|
||||
cy="-3386.084"
|
||||
cx="-289.85474"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12414" />
|
||||
<g
|
||||
transform="matrix(1.6756837,0,0,1.6756837,1236.1172,880.52979)"
|
||||
id="g12610">
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.8745098;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
id="path12612"
|
||||
transform="matrix(1.4411765,0,0,1.4411765,371.40931,1249.9514)"
|
||||
inkscape:transform-center-y="16.667517"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
inkscape:transform-center-y="8.3338253"
|
||||
transform="matrix(0.72058825,-1.2480955,1.2480955,0.72058825,3017.7815,-1758.913)"
|
||||
id="path12614"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.5372549;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
inkscape:transform-center-x="-14.434413"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
inkscape:transform-center-y="-8.3337523"
|
||||
transform="matrix(-0.72058825,-1.2480955,1.2480955,-0.72058825,1735.2145,-5555.1708)"
|
||||
id="path12616"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.35294118;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
inkscape:transform-center-x="-14.434364"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
inkscape:transform-center-x="-4.4417856e-05"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.15294118;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
id="path12618"
|
||||
transform="matrix(-1.4411765,1.9831428e-8,-1.9831428e-8,-1.4411765,-2193.7246,-6342.5641)"
|
||||
inkscape:transform-center-y="-16.667556"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
inkscape:transform-center-y="8.3336884"
|
||||
transform="matrix(0.72058827,1.2480955,-1.2480955,0.72058827,-3557.5298,462.55811)"
|
||||
id="path12620"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
inkscape:transform-center-x="14.4344"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.9 KiB |
124
CitizenWatt-Base/static/img/loading_simple.svg
Normal file
@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="loading_simple.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="-0.30469914"
|
||||
inkscape:cy="18.556613"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<g
|
||||
transform="matrix(1.6756837,0,0,1.6756837,1236.1172,880.52979)"
|
||||
id="g12610">
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.8745098;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
id="path12612"
|
||||
transform="matrix(1.4411765,0,0,1.4411765,371.40931,1249.9514)"
|
||||
inkscape:transform-center-y="16.667517"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
inkscape:transform-center-y="8.3338253"
|
||||
transform="matrix(0.72058825,-1.2480955,1.2480955,0.72058825,3017.7815,-1758.913)"
|
||||
id="path12614"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.5372549;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
inkscape:transform-center-x="-14.434413"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
inkscape:transform-center-y="-8.3337523"
|
||||
transform="matrix(-0.72058825,-1.2480955,1.2480955,-0.72058825,1735.2145,-5555.1708)"
|
||||
id="path12616"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.35294118;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
inkscape:transform-center-x="-14.434364"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
inkscape:transform-center-x="-4.4417856e-05"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.15294118;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
id="path12618"
|
||||
transform="matrix(-1.4411765,1.9831428e-8,-1.9831428e-8,-1.4411765,-2193.7246,-6342.5641)"
|
||||
inkscape:transform-center-y="-16.667556"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
r="4.555995"
|
||||
cy="-2622.5728"
|
||||
cx="-889.9444"
|
||||
inkscape:transform-center-y="8.3336884"
|
||||
transform="matrix(0.72058827,1.2480955,-1.2480955,0.72058827,-3557.5298,462.55811)"
|
||||
id="path12620"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
inkscape:transform-center-x="14.4344"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.6 KiB |
83
CitizenWatt-Base/static/img/login.svg
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="login.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="9.852577"
|
||||
inkscape:cy="46.119405"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<g
|
||||
id="g8766"
|
||||
transform="matrix(0.54617902,0,0,0.54617902,-474.73446,1591.804)">
|
||||
<circle
|
||||
r="58.588848"
|
||||
cy="-3048.8572"
|
||||
cx="-438.40622"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path8760"
|
||||
transform="translate(1403.1019,211.59467)"
|
||||
d="m -379.81737,-3048.8572 a 58.588848,58.588848 0 0 1 -58.58885,58.5889 58.588848,58.588848 0 0 1 -58.58885,-58.5889 58.588848,58.588848 0 0 1 58.58885,-58.5888 58.588848,58.588848 0 0 1 58.58885,58.5888 z" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#f1f1f1;stroke-width:4.36943197;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 47.528085,53.564276 58.114896,42.97755 47.528085,32.390822"
|
||||
id="path8789"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
BIN
CitizenWatt-Base/static/img/logo.png
Normal file
After Width: | Height: | Size: 11 KiB |
94
CitizenWatt-Base/static/img/logout.svg
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="logout.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="9.852577"
|
||||
inkscape:cy="46.119405"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<g
|
||||
id="g8766"
|
||||
transform="matrix(0.54617902,0,0,0.54617902,-474.73446,1591.804)">
|
||||
<circle
|
||||
r="58.588848"
|
||||
cy="-3048.8572"
|
||||
cx="-438.40622"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path8760"
|
||||
transform="translate(1403.1019,211.59467)"
|
||||
d="m -379.81737,-3048.8572 a 58.588848,58.588848 0 0 1 -58.58885,58.5889 58.588848,58.588848 0 0 1 -58.58885,-58.5889 58.588848,58.588848 0 0 1 58.58885,-58.5888 58.588848,58.588848 0 0 1 58.58885,58.5888 z" />
|
||||
</g>
|
||||
<g
|
||||
id="g8777"
|
||||
transform="matrix(0.38620662,-0.38620662,0.38620662,0.38620662,1174.155,1111.3069)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path8779"
|
||||
d="m -87.208889,-2836.748 38.45007,0"
|
||||
style="fill:none;stroke:#f1f1f1;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
style="fill:none;stroke:#f1f1f1;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -67.983854,-2817.523 0,-38.45"
|
||||
id="path8781"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
100
CitizenWatt-Base/static/img/month.svg
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="month.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="49.640088"
|
||||
inkscape:cy="23.737821"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<circle
|
||||
r="16"
|
||||
cy="58.150742"
|
||||
cx="36.162098"
|
||||
id="path12356"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate" />
|
||||
<g
|
||||
id="g12362"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82084,1047.1346)"
|
||||
style="stroke:#ffffff;stroke-opacity:1">
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12364"
|
||||
d="m -265.75382,-3396.9445 -48.61931,0"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12366"
|
||||
d="m -314.37313,-3411.1013 9.72386,0 3.13881,-5.8805 3.13881,5.8805 16.61636,0 3.13881,-5.8805 3.13879,5.8805 9.72387,0 0,48.6193 -48.61931,0 z"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text12368"
|
||||
y="-3371.0356"
|
||||
x="-288.89352"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25.81082535px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
xml:space="preserve"><tspan
|
||||
y="-3371.0356"
|
||||
x="-288.89352"
|
||||
id="tspan12370"
|
||||
sodipodi:role="line">31</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
195
CitizenWatt-Base/static/img/moon.svg
Normal file
@ -0,0 +1,195 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="night.svg">
|
||||
<defs
|
||||
id="defs14624">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient7359"
|
||||
id="linearGradient7365"
|
||||
x1="45.597984"
|
||||
y1="10.306918"
|
||||
x2="47.26799"
|
||||
y2="12.978639"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.89388158,0,0,0.89388158,4.7808631,1.4067091)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient7359">
|
||||
<stop
|
||||
style="stop-color:#dad5d6;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7361" />
|
||||
<stop
|
||||
style="stop-color:#cac2c4;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop7363" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="35.864005"
|
||||
inkscape:cy="23.896049"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<g
|
||||
id="g7409"
|
||||
transform="matrix(13.919674,0,0,13.919674,-606.94219,-110.36889)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="circle7334"
|
||||
d="m 48.50005,12.948411 a 2.2991438,2.2991438 0 0 0 0.841277,-3.140778 2.2991438,2.2991438 0 0 0 -3.140433,-0.8414774 2.2991438,2.2991438 0 0 0 -0.185523,0.1217829 3.0746763,3.0746763 0 0 0 0.256308,2.4919575 3.0746763,3.0746763 0 0 0 2.032311,1.468872 2.2991438,2.2991438 0 0 0 0.19606,-0.100357 z"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="circle7342"
|
||||
d="M 48.30399,13.048768 A 3.0746763,3.0746763 0 0 1 46.271679,11.579896 3.0746763,3.0746763 0 0 1 46.015371,9.0879385 2.2991438,2.2991438 0 0 0 45.359072,12.106789 2.2991438,2.2991438 0 0 0 48.30399,13.048768 Z"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient7365);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
inkscape:transform-center-y="0.019565777"
|
||||
inkscape:transform-center-x="-0.096758663"
|
||||
d="m 47.667272,9.8729331 -0.08754,-0.1246824 -0.148484,0.034081 0.09153,-0.1217852 -0.0783,-0.1306853 0.144109,0.049415 0.100094,-0.1148489 -0.0025,0.1523254 0.140159,0.059705 -0.145632,0.044727 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.9373154"
|
||||
sodipodi:arg1="1.3089969"
|
||||
sodipodi:r2="0.088957042"
|
||||
sodipodi:r1="0.21505897"
|
||||
sodipodi:cy="9.6652021"
|
||||
sodipodi:cx="47.61161"
|
||||
sodipodi:sides="5"
|
||||
id="path7348"
|
||||
style="opacity:1;fill:#cac2c4;fill-opacity:1;stroke:none;stroke-width:0.28222224;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
sodipodi:type="star" />
|
||||
<path
|
||||
transform="matrix(0.77412415,-0.44694079,0.44694079,0.77412415,6.4372147,24.280541)"
|
||||
sodipodi:type="star"
|
||||
style="opacity:1;fill:#dad5d6;fill-opacity:1;stroke:none;stroke-width:0.28222224;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path7350"
|
||||
sodipodi:sides="5"
|
||||
sodipodi:cx="48.428383"
|
||||
sodipodi:cy="9.2998533"
|
||||
sodipodi:r1="0.15348034"
|
||||
sodipodi:r2="0.063285984"
|
||||
sodipodi:arg1="1.3089969"
|
||||
sodipodi:arg2="1.9373154"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 48.468107,9.4481039 -0.0624,-0.089168 -0.10604,0.024509 0.06552,-0.086904 -0.05608,-0.093276 0.102897,0.035459 0.07138,-0.082157 -0.0019,0.1088182 0.100194,0.042501 -0.104088,0.031795 z"
|
||||
inkscape:transform-center-x="-0.025223116"
|
||||
inkscape:transform-center-y="-0.043594137" />
|
||||
<path
|
||||
inkscape:transform-center-y="0.010338504"
|
||||
inkscape:transform-center-x="-0.012480403"
|
||||
d="m 48.468107,9.4481039 -0.0624,-0.089168 -0.10604,0.024509 0.06552,-0.086904 -0.05608,-0.093276 0.102897,0.035459 0.07138,-0.082157 -0.0019,0.1088182 0.100194,0.042501 -0.104088,0.031795 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.9373154"
|
||||
sodipodi:arg1="1.3089969"
|
||||
sodipodi:r2="0.063285984"
|
||||
sodipodi:r1="0.15348034"
|
||||
sodipodi:cy="9.2998533"
|
||||
sodipodi:cx="48.428383"
|
||||
sodipodi:sides="5"
|
||||
id="path7352"
|
||||
style="opacity:1;fill:#b9b0b2;fill-opacity:1;stroke:none;stroke-width:0.28222224;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
sodipodi:type="star"
|
||||
transform="matrix(0.63206973,0.63206973,-0.63206973,0.63206973,23.101949,-24.338997)" />
|
||||
<ellipse
|
||||
ry="0.23700748"
|
||||
rx="0.25137156"
|
||||
cy="10.696868"
|
||||
cx="45.444836"
|
||||
id="path7374"
|
||||
style="opacity:1;fill:#cac2c4;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 45.696207,10.696868 a 0.25137156,0.23700748 0 0 1 -0.251371,0.237007 0.25137156,0.23700748 0 0 1 -0.251372,-0.237007 0.25137156,0.23700748 0 0 1 0.251372,-0.237008 0.25137156,0.23700748 0 0 1 0.251371,0.237008 z" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#cac2c4;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="ellipse7376"
|
||||
cx="45.642342"
|
||||
cy="10.91592"
|
||||
rx="0.12568578"
|
||||
ry="0.11850374"
|
||||
d="m 45.768027,10.91592 a 0.12568578,0.11850374 0 0 1 -0.125685,0.118504 0.12568578,0.11850374 0 0 1 -0.125686,-0.118504 0.12568578,0.11850374 0 0 1 0.125686,-0.118503 0.12568578,0.11850374 0 0 1 0.125685,0.118503 z" />
|
||||
<ellipse
|
||||
ry="0.1591336"
|
||||
rx="0.16877805"
|
||||
cy="12.5918"
|
||||
cx="46.231266"
|
||||
id="ellipse7378"
|
||||
style="opacity:1;fill:#cac2c4;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 46.400044,12.5918 A 0.16877805,0.1591336 0 0 1 46.231266,12.750933 0.16877805,0.1591336 0 0 1 46.062488,12.5918 0.16877805,0.1591336 0 0 1 46.231266,12.432666 0.16877805,0.1591336 0 0 1 46.400044,12.5918 Z" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#cac2c4;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="ellipse7380"
|
||||
cx="46.450317"
|
||||
cy="12.513721"
|
||||
rx="0.12209476"
|
||||
ry="0.11511792"
|
||||
d="m 46.572412,12.513721 a 0.12209476,0.11511792 0 0 1 -0.122095,0.115118 0.12209476,0.11511792 0 0 1 -0.122094,-0.115118 0.12209476,0.11511792 0 0 1 0.122094,-0.115117 0.12209476,0.11511792 0 0 1 0.122095,0.115117 z" />
|
||||
<path
|
||||
id="ellipse7382"
|
||||
transform="matrix(0.28222224,0,0,0.28222224,0.39905348,6.6290842)"
|
||||
d="m 158.83789,17.746094 a 1.3637915,1.2858606 0 0 0 -0.24023,0.01953 8.146572,8.146572 0 0 0 0.70898,1.644531 8.146572,8.146572 0 0 0 0.39844,0.613282 1.3637915,1.2858606 0 0 0 0.49609,-0.992188 1.3637915,1.2858606 0 0 0 -1.36328,-1.285156 z"
|
||||
style="opacity:1;fill:#cac2c4;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.0 KiB |
89
CitizenWatt-Base/static/img/more.svg
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="more.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="49.640088"
|
||||
inkscape:cy="23.737821"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<circle
|
||||
r="16"
|
||||
cy="58.150742"
|
||||
cx="36.162098"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12372" />
|
||||
<g
|
||||
style="fill:#f1f1f1;fill-opacity:1"
|
||||
id="g12388"
|
||||
transform="matrix(0.3489974,0,0,0.3489974,59.293232,1047.8424)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12390"
|
||||
d="m -87.208889,-2836.748 38.45007,0"
|
||||
style="fill:#f1f1f1;fill-opacity:1;stroke:#f1f1f1;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
style="fill:#f1f1f1;fill-opacity:1;stroke:#f1f1f1;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -67.983854,-2817.523 0,-38.45"
|
||||
id="path12392"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
87
CitizenWatt-Base/static/img/progress.svg
Normal file
@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="progress.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.8"
|
||||
inkscape:cx="34.977298"
|
||||
inkscape:cy="31.936832"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
r="54.780827"
|
||||
cy="-3386.084"
|
||||
cx="-289.85474"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12494" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12551"
|
||||
d="m -333.08045,-3374.0447 17.94489,-17.9447 30.17967,30.1796 27.32503,-27.3248 10.60366,10.6038"
|
||||
style="fill:none;stroke:#ffffff;stroke-width:2.16968155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12553"
|
||||
d="m -331.78858,-3392.7275 18.76034,-18.7604 19.16837,19.1685 11.01137,-11.0117 27.32503,27.325"
|
||||
style="fill:none;stroke:#ffffff;stroke-width:2.16968155;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4.33936308, 2.16968164;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
113
CitizenWatt-Base/static/img/results.svg
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="results.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="49.640088"
|
||||
inkscape:cy="23.737821"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<circle
|
||||
r="32.000004"
|
||||
cy="42.150742"
|
||||
cx="52.162098"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12065" />
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.54617907,0,0,0.54617907,175.48713,1893.1932)"
|
||||
id="g12131">
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12133"
|
||||
d="m -228.00257,-3405.6945 -18.61931,0"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12135"
|
||||
d="m -250.55045,-3411.1013 48.61931,0 0,48.6193 -48.61931,0 z"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -233.18114,-3401.2302 -13.44074,0"
|
||||
id="path12137"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12139"
|
||||
d="m -233.18114,-3397.3016 -13.44074,0"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12141"
|
||||
d="m -206.93114,-3380.5159 -39.69074,0"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path12143"
|
||||
d="m -217.40749,-3386.2036 0,16.6818 10.24715,0 0,-16.6818 -39.09242,0 0,11.3187 39.09242,0"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
83
CitizenWatt-Base/static/img/sensor.svg
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="sensor.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="10.231015"
|
||||
inkscape:cy="21.94947"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
id="circle5922"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
cx="-289.85474"
|
||||
cy="-3386.084"
|
||||
r="54.780827" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#f1f1f1;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
d="m -306.26477,-3415.4098 c -1.57728,0 -2.85575,1.2778 -2.85575,2.8556 l 0,39.6685 -1.45543,1.2649 1.07098,1.0988 c 0,0 1.43482,-1.2414 1.48631,-1.2918 0.20939,-0.2077 0.28491,-0.4509 0.28491,-2.2514 l 0,-20.0279 9.07545,0 0,-9.1729 c 0,-0.3958 0.32439,-0.7214 0.72256,-0.7214 l 14.83623,0 c 0.39646,0 0.71055,0.3254 0.71055,0.7214 l 0,9.1729 10.21955,0 0,-18.4612 c 0,-1.578 -1.27866,-2.8558 -2.85594,-2.8558 z m -0.0113,22.7446 0,17.9544 c 0,1.578 0.82555,2.8558 2.40367,2.8558 l 0.81697,0 c 1.57728,0 2.86519,1.2778 2.86519,2.8557 l 0,12.0349 c 0,1.578 1.27693,2.8558 2.85576,2.8558 l 22.30727,0 c 1.57728,0 2.85594,-1.2778 2.85594,-2.8558 l 0,-35.7008 -10.21955,0 0,2.7335 c 0,0.3959 -0.31751,0.7115 -0.71056,0.7115 l -14.83639,0 c -0.39646,0 -0.72256,-0.3157 -0.72256,-0.7115 l 0,-2.7335 z"
|
||||
id="path18125"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sscccssccssssccssscssssssssccsssscc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
107
CitizenWatt-Base/static/img/small-data.svg
Normal file
@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="small-data.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.2"
|
||||
inkscape:cx="18.721536"
|
||||
inkscape:cy="19.218064"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12444"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
r="54.780827"
|
||||
cy="-3386.084"
|
||||
cx="-289.85474"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12438" />
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="translate(8.3940035e-5,0.00102414)"
|
||||
id="g12444">
|
||||
<g
|
||||
id="g12205"
|
||||
transform="matrix(1.1307112,0,0,1.1307112,-1295.3545,75.272408)">
|
||||
<rect
|
||||
y="-3064.4236"
|
||||
x="867.5"
|
||||
height="25.357143"
|
||||
width="11.071428"
|
||||
id="rect12207"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="rect12209"
|
||||
width="11.071428"
|
||||
height="45.000244"
|
||||
x="882.32147"
|
||||
y="-3084.0664" />
|
||||
<rect
|
||||
y="-3057.9951"
|
||||
x="897.14288"
|
||||
height="18.928434"
|
||||
width="11.071428"
|
||||
id="rect12211"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91600001;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
181
CitizenWatt-Base/static/img/sun.svg
Normal file
@ -0,0 +1,181 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="sun.svg">
|
||||
<defs
|
||||
id="defs14624">
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient7423"
|
||||
id="radialGradient7429"
|
||||
cx="54.271488"
|
||||
cy="10.949031"
|
||||
fx="54.271488"
|
||||
fy="10.949031"
|
||||
r="2.2954693"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient7423">
|
||||
<stop
|
||||
style="stop-color:#ffd974;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop7425" />
|
||||
<stop
|
||||
style="stop-color:#ffd35d;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop7427" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="35.864005"
|
||||
inkscape:cy="23.896049"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<path
|
||||
style="fill:#ddd2b1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 55.037951,10.150743 c -0.435068,0.942491 -1.012927,1.812226 -1.713187,2.578518 -0.841526,0.908259 -1.842983,1.653925 -2.954373,2.199752 -2.021693,-0.656119 -3.784662,-1.933736 -5.037571,-3.650715 -0.107407,-0.156342 -0.210427,-0.315681 -0.308847,-0.477825 -0.124427,1.031771 -0.407477,2.038134 -0.839111,2.983505 -0.429995,0.932757 -0.999844,1.794418 -1.689871,2.55521 -1.903833,-0.196344 -3.712596,-0.929953 -5.215316,-2.115259 -0.146356,-0.121235 -0.28913,-0.246564 -0.428303,-0.375853 0.157409,1.02653 0.155454,2.071205 -0.0058,3.097135 -0.203022,1.237288 -0.634792,2.425918 -1.273235,3.505036 -2.0599,0.585684 -4.252689,0.500993 -6.261274,-0.241827 -0.17591,-0.07084 -0.349864,-0.146624 -0.521543,-0.227261 0.46747,0.927948 0.788826,1.922513 0.952737,2.948543 0.188067,1.223209 0.149533,2.470575 -0.113701,3.679851 -1.771768,1.174703 -3.869219,1.759993 -5.993239,1.672397 -0.18886,-0.01208 -0.377349,-0.0296 -0.565235,-0.05233 0.730478,0.737857 1.342741,1.584009 1.815164,2.50859 0.557253,1.10975 0.904939,2.312806 1.025576,3.548739 -1.324093,1.669416 -3.142556,2.876964 -5.194908,3.44968 -0.183363,0.04599 -0.367941,0.08679 -0.553591,0.122368 0.922471,0.477619 1.765685,1.094809 2.499852,1.829727 0.880313,0.892153 1.586773,1.940469 2.083215,3.091312 -0.73791,2.010186 -2.096033,3.733596 -3.877977,4.921037 -0.160421,0.100693 -0.323646,0.196893 -0.489459,0.288442 1.024672,0.168192 2.017265,0.493476 2.942718,0.964394 1.100527,0.567746 2.087174,1.333163 2.910666,2.258025 -0.09308,2.122746 -0.853716,4.161979 -2.173528,5.827156 -0.12216,0.146311 -0.248481,0.28913 -0.378776,0.428303 1.026686,-0.156458 2.07136,-0.153499 3.097136,0.009 1.01472,0.165285 1.998216,0.484596 2.91648,0.946918 0.31984,1.887072 0.09652,3.826187 -0.643898,5.591158 -0.0786,0.173267 -0.162219,0.344261 -0.250545,0.512769 0.947263,-0.425923 1.954598,-0.703039 2.986412,-0.82163 1.247539,-0.135004 2.509323,-0.03647 3.720641,0.29135 1.113922,1.827728 1.617969,3.962257 1.439302,6.095212 -0.02114,0.189122 -0.04705,0.377613 -0.0786,0.565235 0.768476,-0.698701 1.640185,-1.274603 2.584345,-1.707362 1.128019,-0.507959 2.339799,-0.804268 3.574956,-0.874071 1.606319,1.392317 2.732023,3.256247 3.216603,5.326021 0.03859,0.186479 0.07131,0.373966 0.09905,0.562322 0.516364,-0.901847 1.168952,-1.71853 1.934623,-2.421185 0.950935,-0.861664 2.057458,-1.534084 3.260285,-1.981231 2.024459,0.80175 3.735368,2.236701 4.87733,4.090664 0.09594,0.163488 0.187273,0.329619 0.273907,0.498233 0.203766,-1.018339 0.563438,-1.999135 1.066366,-2.907753 0.502673,-0.896561 1.139356,-1.711073 1.888007,-2.415359 1.88195,0.345211 3.627004,1.218256 5.03175,2.517331 0.135899,0.132199 0.268038,0.268251 0.396269,0.4079 -0.07563,-1.035264 0.009,-2.075994 0.250542,-3.085484 0.299124,-1.218549 0.822776,-2.370649 1.544187,-3.397234 2.098583,-0.422014 4.277019,-0.165919 6.220503,0.731314 0.170464,0.08363 0.338553,0.172049 0.504049,0.265132 -0.394465,-0.961042 -0.638186,-1.977226 -0.722576,-3.012644 -0.09174,-1.235117 0.04445,-2.476584 0.402089,-3.662365 1.857844,-1.031814 3.993972,-1.450877 6.103954,-1.197485 0.188333,0.02749 0.375876,0.06073 0.562316,0.09905 -0.67102,-0.793495 -1.215482,-1.68585 -1.614121,-2.645533 -0.469157,-1.150055 -0.722024,-2.376821 -0.745877,-3.618664 1.45046,-1.560752 3.357652,-2.62245 5.448387,-3.033036 0.186648,-0.03171 0.374191,-0.05772 0.562331,-0.07865 -0.881999,-0.548216 -1.674105,-1.229273 -2.348347,-2.01911 -0.809157,-0.958282 -1.432635,-2.05899 -1.838466,-3.245728 0.893978,-1.945954 2.383731,-3.557132 4.253822,-4.600542 0.167569,-0.08827 0.337614,-0.171839 0.509868,-0.250544 -1.009215,-0.246633 -1.974291,-0.647609 -2.861137,-1.188746 -1.052476,-0.652377 -1.975917,-1.492857 -2.724195,-2.479456 0.258417,-2.109401 1.176097,-4.083407 2.622219,-5.640691 0.133213,-0.135944 0.270156,-0.26804 0.410814,-0.396273 -1.036381,0.07501 -2.078116,-0.01059 -3.088391,-0.253503 -0.997954,-0.243092 -1.952944,-0.637064 -2.832003,-1.168344 -0.170835,-1.906629 0.204027,-3.822528 1.080939,-5.524147 0.09055,-0.166781 0.185738,-0.330978 0.285536,-0.492393 -0.977754,0.350566 -2.003752,0.548098 -3.041773,0.585629 -1.012648,0.03182 -2.024308,-0.08897 -3.001003,-0.358368 -0.868066,-1.887349 -1.1326,-3.996583 -0.757512,-6.03985 0.03968,-0.185752 0.08413,-0.37035 0.134108,-0.553581 -0.834233,0.618964 -1.759046,1.105432 -2.741707,1.442222 -0.842274,0.281894 -1.718096,0.451376 -2.604735,0.504049 -0.96767,-1.469984 -1.534145,-3.16744 -1.643247,-4.923949 -0.0058,-0.190288 -0.0075,-0.380716 -0.003,-0.571063 -0.660326,0.802614 -1.44066,1.498421 -2.313393,2.062815 -0.870687,0.557712 -1.821903,0.97829 -2.820332,1.247045 -1.427069,-1.204155 -2.49214,-2.780577 -3.076743,-4.553925 -0.0546,-0.181158 -0.104288,-0.363813 -0.148578,-0.547753 z"
|
||||
id="path7691"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" />
|
||||
<g
|
||||
id="g7482"
|
||||
transform="matrix(11.813135,0,0,11.813135,-589.18632,-87.372382)">
|
||||
<circle
|
||||
r="2.2954693"
|
||||
cy="10.949031"
|
||||
cx="54.271488"
|
||||
id="path7421"
|
||||
style="opacity:1;fill:url(#radialGradient7429);fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 56.566957,10.949031 A 2.2954693,2.2954693 0 0 1 54.271488,13.2445 2.2954693,2.2954693 0 0 1 51.976019,10.949031 2.2954693,2.2954693 0 0 1 54.271488,8.6535616 2.2954693,2.2954693 0 0 1 56.566957,10.949031 Z" />
|
||||
<ellipse
|
||||
ry="0.67380553"
|
||||
rx="0.5383181"
|
||||
cy="10.603694"
|
||||
cx="55.72393"
|
||||
id="path7451"
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 56.262248,10.603694 A 0.5383181,0.67380553 0 0 1 55.72393,11.277499 0.5383181,0.67380553 0 0 1 55.185612,10.603694 0.5383181,0.67380553 0 0 1 55.72393,9.9298884 0.5383181,0.67380553 0 0 1 56.262248,10.603694 Z" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="ellipse7453"
|
||||
cx="55.343044"
|
||||
cy="10.155198"
|
||||
rx="0.41135627"
|
||||
ry="0.45194274"
|
||||
d="m 55.754401,10.155198 a 0.41135627,0.45194274 0 0 1 -0.411357,0.451943 0.41135627,0.45194274 0 0 1 -0.411356,-0.451943 0.41135627,0.45194274 0 0 1 0.411356,-0.4519426 0.41135627,0.45194274 0 0 1 0.411357,0.4519426 z" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="ellipse7455"
|
||||
cx="53.032341"
|
||||
cy="11.395936"
|
||||
rx="0.63988751"
|
||||
ry="0.67380553"
|
||||
d="m 53.672229,11.395936 a 0.63988751,0.67380553 0 0 1 -0.639888,0.673806 0.63988751,0.67380553 0 0 1 -0.639888,-0.673806 0.63988751,0.67380553 0 0 1 0.639888,-0.673806 0.63988751,0.67380553 0 0 1 0.639888,0.673806 z" />
|
||||
<ellipse
|
||||
ry="0.52407098"
|
||||
rx="0.49769029"
|
||||
cy="10.662016"
|
||||
cx="53.326893"
|
||||
id="ellipse7457"
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 53.824583,10.662016 a 0.49769029,0.52407098 0 0 1 -0.49769,0.524071 0.49769029,0.52407098 0 0 1 -0.49769,-0.524071 0.49769029,0.52407098 0 0 1 0.49769,-0.524071 0.49769029,0.52407098 0 0 1 0.49769,0.524071 z" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="ellipse7459"
|
||||
cx="52.899345"
|
||||
cy="12.053517"
|
||||
rx="0.32406834"
|
||||
ry="0.34124598"
|
||||
d="m 53.223414,12.053517 a 0.32406834,0.34124598 0 0 1 -0.324069,0.341246 0.32406834,0.34124598 0 0 1 -0.324068,-0.341246 0.32406834,0.34124598 0 0 1 0.324068,-0.341246 0.32406834,0.34124598 0 0 1 0.324069,0.341246 z" />
|
||||
<path
|
||||
id="ellipse7461"
|
||||
transform="matrix(0.28222224,0,0,0.28222224,0.39905348,6.6290842)"
|
||||
d="m 193.38867,18.570312 a 3.1850419,3.1072832 0 0 0 -3.18555,3.107422 3.1850419,3.1072832 0 0 0 0.5625,1.757813 8.1335521,8.1335521 0 0 0 0.1211,0.0059 8.1335521,8.1335521 0 0 0 5.64258,-2.279297 3.1850419,3.1072832 0 0 0 -3.14063,-2.591797 z"
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<ellipse
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="ellipse7466"
|
||||
cx="54.911373"
|
||||
cy="11.491799"
|
||||
rx="0.17266805"
|
||||
ry="0.18182054"
|
||||
d="m 55.084041,11.491799 a 0.17266805,0.18182054 0 0 1 -0.172668,0.181821 0.17266805,0.18182054 0 0 1 -0.172668,-0.181821 0.17266805,0.18182054 0 0 1 0.172668,-0.18182 0.17266805,0.18182054 0 0 1 0.172668,0.18182 z" />
|
||||
<ellipse
|
||||
ry="0.13154623"
|
||||
rx="0.12492445"
|
||||
cy="11.326612"
|
||||
cx="54.755898"
|
||||
id="ellipse7468"
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 54.880822,11.326612 a 0.12492445,0.13154623 0 0 1 -0.124924,0.131547 0.12492445,0.13154623 0 0 1 -0.124925,-0.131547 0.12492445,0.13154623 0 0 1 0.124925,-0.131546 0.12492445,0.13154623 0 0 1 0.124924,0.131546 z" />
|
||||
<path
|
||||
id="ellipse7470"
|
||||
transform="matrix(0.28222224,0,0,0.28222224,0.39905348,6.6290842)"
|
||||
d="m 190.88672,7.1738281 a 8.1335521,8.1335521 0 0 0 -8.13281,8.1328129 8.1335521,8.1335521 0 0 0 0.004,0.111328 7.089867,7.4656736 0 0 0 3.73828,1.132812 7.089867,7.4656736 0 0 0 7.08985,-7.4667966 7.089867,7.4656736 0 0 0 -0.15235,-1.4941406 8.1335521,8.1335521 0 0 0 -2.54687,-0.4160157 z"
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="ellipse7477"
|
||||
transform="matrix(0.28222224,0,0,0.28222224,0.39905348,6.6290842)"
|
||||
d="m 195.66211,13.783203 a 4.5230362,4.7627852 0 0 0 -4.52344,4.763672 4.5230362,4.7627852 0 0 0 2.67969,4.345703 8.1335521,8.1335521 0 0 0 5.19922,-7.53125 4.5230362,4.7627852 0 0 0 -3.35547,-1.578125 z"
|
||||
style="opacity:1;fill:#ffeebc;fill-opacity:0.35922331;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 13 KiB |
94
CitizenWatt-Base/static/img/target-no.svg
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="target-no.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.2"
|
||||
inkscape:cx="6.7572499"
|
||||
inkscape:cy="12.610921"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e35c4f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="circle16017"
|
||||
cx="-289.85474"
|
||||
cy="-3386.084"
|
||||
r="54.780827" />
|
||||
<g
|
||||
style="fill:#f1f1f1;fill-opacity:1"
|
||||
id="g16019"
|
||||
transform="matrix(0.84492033,0.84492033,-0.84492033,0.84492033,-2629.2399,-931.81687)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16021"
|
||||
d="m -87.208889,-2836.748 38.45007,0"
|
||||
style="fill:#f1f1f1;fill-opacity:1;stroke:#f1f1f1;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
style="fill:#f1f1f1;fill-opacity:1;stroke:#f1f1f1;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -67.983854,-2817.523 0,-38.45"
|
||||
id="path16023"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
89
CitizenWatt-Base/static/img/target-ok.svg
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="target-ok.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.2"
|
||||
inkscape:cx="6.7572499"
|
||||
inkscape:cy="12.610921"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
r="54.780827"
|
||||
cy="-3386.084"
|
||||
cx="-289.85474"
|
||||
id="circle16013"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#63bd51;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:89.11461639px;line-height:125%;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L;letter-spacing:-4.14955664px;word-spacing:0px;fill:#f1f1f1;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
x="-323.99905"
|
||||
y="-3356.2107"
|
||||
id="text16025"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0001094,0.99989062)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan16027"
|
||||
x="-323.99905"
|
||||
y="-3356.2107">✓</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
132
CitizenWatt-Base/static/img/target-wip.svg
Normal file
@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="target-wip.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.2"
|
||||
inkscape:cx="15.462607"
|
||||
inkscape:cy="13.593064"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
id="circle16015"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#f0c551;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
cx="-289.85474"
|
||||
cy="-3386.084"
|
||||
r="54.780827"
|
||||
d="m -235.07391,-3386.084 a 54.780827,54.780827 0 0 1 -54.78083,54.7808 54.780827,54.780827 0 0 1 -54.78082,-54.7808 54.780827,54.780827 0 0 1 54.78082,-54.7808 54.780827,54.780827 0 0 1 54.78083,54.7808 z" />
|
||||
<g
|
||||
id="g16029"
|
||||
transform="matrix(1.6756837,0,0,1.6756837,1236.1172,880.52991)"
|
||||
style="stroke-width:0.70684433">
|
||||
<circle
|
||||
inkscape:transform-center-y="16.667517"
|
||||
transform="matrix(1.4411765,0,0,1.4411765,371.40931,1249.9514)"
|
||||
id="circle16031"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.8745098;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
cx="-889.9444"
|
||||
cy="-2622.5728"
|
||||
r="4.555995"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
inkscape:transform-center-x="-14.434413"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.5372549;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
id="circle16033"
|
||||
transform="matrix(0.72058825,-1.2480955,1.2480955,0.72058825,3017.7815,-1758.913)"
|
||||
inkscape:transform-center-y="8.3338253"
|
||||
cx="-889.9444"
|
||||
cy="-2622.5728"
|
||||
r="4.555995"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
inkscape:transform-center-x="-14.434364"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.35294118;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
id="circle16035"
|
||||
transform="matrix(-0.72058825,-1.2480955,1.2480955,-0.72058825,1735.2145,-5555.1708)"
|
||||
inkscape:transform-center-y="-8.3337523"
|
||||
cx="-889.9444"
|
||||
cy="-2622.5728"
|
||||
r="4.555995"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
inkscape:transform-center-y="-16.667556"
|
||||
transform="matrix(-1.4411765,1.9831428e-8,-1.9831428e-8,-1.4411765,-2193.7246,-6342.5641)"
|
||||
id="circle16037"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:0.15294118;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
inkscape:transform-center-x="-4.4417856e-05"
|
||||
cx="-889.9444"
|
||||
cy="-2622.5728"
|
||||
r="4.555995"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
<circle
|
||||
inkscape:transform-center-x="14.4344"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#fdfdfd;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
|
||||
id="circle16039"
|
||||
transform="matrix(0.72058827,1.2480955,-1.2480955,0.72058827,-3557.5298,462.55811)"
|
||||
inkscape:transform-center-y="8.3336884"
|
||||
cx="-889.9444"
|
||||
cy="-2622.5728"
|
||||
r="4.555995"
|
||||
d="m -885.3884,-2622.5728 a 4.555995,4.555995 0 0 1 -4.556,4.556 4.555995,4.555995 0 0 1 -4.55599,-4.556 4.555995,4.555995 0 0 1 4.55599,-4.5559 4.555995,4.555995 0 0 1 4.556,4.5559 z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.2 KiB |
95
CitizenWatt-Base/static/img/target.svg
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 63.999999 64.000002"
|
||||
sodipodi:docname="target.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="49.640088"
|
||||
inkscape:cy="23.737821"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-10.150743)">
|
||||
<circle
|
||||
r="32.000004"
|
||||
cy="42.150745"
|
||||
cx="52.162083"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#e3834f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12199" />
|
||||
<g
|
||||
id="g12215"
|
||||
transform="matrix(0.54617908,0,0,0.54617908,-2217.081,1620.9655)">
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.9000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="rect12145"
|
||||
width="33.498375"
|
||||
height="33.491047"
|
||||
x="4137.5"
|
||||
y="-2907.2537" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:74.48440552px;line-height:125%;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L;letter-spacing:-3.46831131px;word-spacing:0px;fill:#f1f1f1;fill-opacity:1;stroke:none"
|
||||
x="4130.2358"
|
||||
y="-2870.698"
|
||||
id="text12147"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0001094,0.99989062)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12149"
|
||||
x="4130.2358"
|
||||
y="-2870.698">✓</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
89
CitizenWatt-Base/static/img/tick.svg
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="tick.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="48.713158"
|
||||
inkscape:cy="17.485184"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
r="54.780827"
|
||||
cy="-3386.084"
|
||||
cx="-289.85474"
|
||||
id="path12398"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:89.11461639px;line-height:125%;font-family:Nimbus Sans L;-inkscape-font-specification:Nimbus Sans L;letter-spacing:-4.14955664px;word-spacing:0px;fill:#f1f1f1;fill-opacity:1;stroke:none"
|
||||
x="-323.99899"
|
||||
y="-3356.2104"
|
||||
id="text12603"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="scale(1.0001094,0.99989062)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12605"
|
||||
x="-323.99899"
|
||||
y="-3356.2104">✓</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
90
CitizenWatt-Base/static/img/user.svg
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="uesr.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="10.231015"
|
||||
inkscape:cy="21.94947"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
r="54.780827"
|
||||
cy="-3386.084"
|
||||
cx="-289.85474"
|
||||
id="path12454"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
d="m -235.07391,-3386.084 a 54.780827,54.780827 0 0 1 -54.78083,54.7808 54.780827,54.780827 0 0 1 -54.78082,-54.7808 54.780827,54.780827 0 0 1 54.78082,-54.7808 54.780827,54.780827 0 0 1 54.78083,54.7808 z" />
|
||||
<circle
|
||||
r="13.361743"
|
||||
cy="-3400.4666"
|
||||
cx="-291.33752"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:2.19928575;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
id="path12555"
|
||||
d="m -277.97578,-3400.4666 a 13.361743,13.361743 0 0 1 -13.36174,13.3618 13.361743,13.361743 0 0 1 -13.36175,-13.3618 13.361743,13.361743 0 0 1 13.36175,-13.3617 13.361743,13.361743 0 0 1 13.36174,13.3617 z" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:2.19928575;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -291.37757,-3383.8464 c -12.16028,0 -22.00587,9.7921 -22.09314,21.9315 6.33176,4.1861 13.93466,6.6118 22.09314,6.6118 8.19802,0 15.82338,-2.4687 22.17339,-6.6923 -0.13213,-12.1021 -10.04057,-21.851 -22.17339,-21.851 z"
|
||||
id="path12559"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
100
CitizenWatt-Base/static/img/week.svg
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="week.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="48.713158"
|
||||
inkscape:cy="17.485184"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12326"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<circle
|
||||
r="16"
|
||||
cy="58.150742"
|
||||
cx="36.162098"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12308" />
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -265.75382,-3396.9445 -48.61931,0"
|
||||
id="path12328"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:1.91458273;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -314.37313,-3411.1013 9.72386,0 3.13881,-5.8805 3.13881,5.8805 16.61636,0 3.13881,-5.8805 3.13879,5.8805 9.72387,0 0,48.6193 -48.61931,0 z"
|
||||
id="path12330"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccc" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25.81082535px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
x="-297.20145"
|
||||
y="-3371.0356"
|
||||
id="text12332"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan12334"
|
||||
x="-297.20145"
|
||||
y="-3371.0356">7</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
112
CitizenWatt-Base/static/img/wiki.svg
Normal file
@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
id="svg14622"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre0 r13343"
|
||||
viewBox="0 0 32 32.000001"
|
||||
sodipodi:docname="wiki.svg">
|
||||
<defs
|
||||
id="defs14624" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.2"
|
||||
inkscape:cx="18.721536"
|
||||
inkscape:cy="19.218064"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g12444"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:window-width="1543"
|
||||
inkscape:window-height="876"
|
||||
inkscape:window-x="57"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata14627">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-20.162097,-42.150744)">
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="matrix(0.29207305,0,0,0.29207305,120.82086,1047.1346)"
|
||||
id="g12326">
|
||||
<circle
|
||||
r="54.780827"
|
||||
cy="-3386.084"
|
||||
cx="-289.85474"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#5f5557;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;marker:none;enable-background:accumulate"
|
||||
id="path12438" />
|
||||
<g
|
||||
style="stroke:#ffffff;stroke-opacity:1"
|
||||
transform="translate(8.3940035e-5,0.00102414)"
|
||||
id="g12444">
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path15224"
|
||||
d="m -259.28157,-3396.2925 -60.69219,0"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:2.84980893;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:2.84980893;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -259.28157,-3386.1813 -60.69219,0"
|
||||
id="path15226"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path15228"
|
||||
d="m -259.28157,-3376.0701 -60.69219,0"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:2.84980893;stroke-opacity:1;marker:none;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:2.94841957;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -286.32676,-3408.3838 -27.68655,0"
|
||||
id="path15285"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:2.84980893;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m -275.61853,-3365.9589 -44.35523,0"
|
||||
id="path15287"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
206
CitizenWatt-Base/static/js/conso/App.js
Normal file
@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Core application
|
||||
*/
|
||||
var App = function() {
|
||||
var api = {};
|
||||
var graph = Graph()
|
||||
, provider = DataProvider()
|
||||
, menu = Menu()
|
||||
, hash = HashManager()
|
||||
, rate = RateDisplay()
|
||||
;
|
||||
|
||||
function reload(_, callback) {
|
||||
var mode = menu.getMode()
|
||||
, date = menu.getDate()
|
||||
, unit = menu.getUnit()
|
||||
;
|
||||
|
||||
graph.clean();
|
||||
graph = unit == 'energy' ? Graph(mode == 'now' ? 'W' : 'kWh') : PriceGraph(mode == 'now' ? 'cents/min' : '€');
|
||||
graph.autoremove = mode == 'now' && date === null;
|
||||
|
||||
if (unit == 'price') graph.round = function(v) { return Math.round(v * 100) / 100; };
|
||||
|
||||
if (mode == 'now' && unit == 'energy') {
|
||||
provider.getProviderInfo(function(provider) {
|
||||
graph.addAbsoluteVerticalGraduation(provider['threshold']);
|
||||
});
|
||||
}
|
||||
graph.init();
|
||||
hash.setMode(mode);
|
||||
hash.setDate(date);
|
||||
hash.setUnit(unit == 'price' ? 'euros' : 'watt');
|
||||
api.initValues(callback);
|
||||
};
|
||||
|
||||
menu.onmodechange = function(ev, callback) {
|
||||
menu.setDate(null, function(){
|
||||
reload(null, callback);
|
||||
}, false);
|
||||
}
|
||||
menu.onunitchange = reload;
|
||||
menu.ondatechange = reload;
|
||||
|
||||
provider.onratechange = rate.setRate;
|
||||
|
||||
/**
|
||||
* Callbacks
|
||||
*/
|
||||
api.oninit = function(){}; // called when init is done
|
||||
|
||||
/**
|
||||
* Init application.
|
||||
* Add graduation lines
|
||||
*/
|
||||
api.init = function() {
|
||||
menu.init();
|
||||
|
||||
provider.get('/time', function(basetime) {
|
||||
dateutils.offset = parseFloat(basetime) * 1000.0 - (new Date()).getTime();
|
||||
|
||||
var unit = hash.getUnit() == 'euros' ? 'price' : 'energy';
|
||||
|
||||
menu.setUnit(unit, function(){
|
||||
menu.setMode(hash.getMode(), function(){
|
||||
menu.setDate(hash.getDate(), function(){
|
||||
reload(null, api.oninit);
|
||||
}, false);
|
||||
}, false);
|
||||
}, false);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Init graph values.
|
||||
* @param callback: (optional)
|
||||
*/
|
||||
api.initValues = function(callback) {
|
||||
provider.getSensorId(function(sensor_id) {
|
||||
var target = '/' + sensor_id + '/get/' + menu.getUnitString();
|
||||
var mode = menu.getMode();
|
||||
var date = menu.getDate() || (new Date());
|
||||
var modifier = 0;
|
||||
|
||||
switch (mode) {
|
||||
case 'now':
|
||||
menu.timeWidth = Config.timestep * (graph.getWidth()+1) * 1000;
|
||||
var start_date = new Date(date.getTime() - menu.timeWidth);
|
||||
target
|
||||
+= '/by_time/'
|
||||
+ start_date.getTime() / 1000.0 + '/'
|
||||
+ date.getTime() / 1000.0 + '/'
|
||||
+ Config.timestep;
|
||||
if (!menu.getDate()) graph.setOverviewLabel('Consommation actuelle');
|
||||
else graph.setOverviewLabel('Consommation entre ' + dateutils.humanTime(start_date) + ' et ' + dateutils.humanTime(date));
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
target
|
||||
+= '/by_time/'
|
||||
+ dateutils.getDayStart(date) / 1000.0 + '/'
|
||||
+ dateutils.getDayEnd(date) / 1000.0 + '/'
|
||||
+ dateutils.getHourLength(date) / 1000.0;
|
||||
graph.setOverviewLabel('Consommation ' + dateutils.humanDay(date));
|
||||
modifier = 1. / (dateutils.getMonthLength(date) / dateutils.getDayLength());
|
||||
break;
|
||||
|
||||
case 'week':
|
||||
target
|
||||
+= '/by_time/'
|
||||
+ (dateutils.getWeekStart(date) / 1000.0) + '/'
|
||||
+ ((dateutils.getWeekStart(date) + dateutils.getDayLength(date) * 7) / 1000.0) + '/' // Avoid pbs with Daylight Saving Time
|
||||
+ (dateutils.getDayLength(date) / 1000.0);
|
||||
graph.setOverviewLabel('Consommation ' + dateutils.humanWeek(date));
|
||||
modifier = 7. / (dateutils.getMonthLength(date) / dateutils.getDayLength());
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
target
|
||||
+= '/by_time/'
|
||||
+ dateutils.getMonthStart(date) / 1000.0 + '/'
|
||||
+ dateutils.getMonthEnd(date) / 1000.0 + '/'
|
||||
+ dateutils.getDayLength(date) / 1000.0;
|
||||
graph.setOverviewLabel('Consommation ' + dateutils.humanMonth(date));
|
||||
modifier = 1.0;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
graph.setOverview(null);
|
||||
graph.startLoading();
|
||||
provider.get(target, function(data) {
|
||||
graph.rect_width = graph.getPixelWidth() / data.length - graph.rect_margin;
|
||||
var s = 0, i = 0;
|
||||
var last_good_value = null;
|
||||
var before_last_value = null;
|
||||
var last_value = null;
|
||||
data.map(function(m) {
|
||||
if (m !== null) {
|
||||
if (last_value === null && before_last_value !== null) {
|
||||
v = (before_last_value + m.value) / 2.0;
|
||||
s += v;
|
||||
graph
|
||||
.removeRect()
|
||||
.addRect(v, false, graph.getLegend(mode, date, i));
|
||||
}
|
||||
graph.addRect(m.value, false, graph.getLegend(mode, date, i));
|
||||
s += m.value;
|
||||
last_good_value = m.value;
|
||||
before_last_value = last_value;
|
||||
last_value = m.value;
|
||||
} else if (mode != 'now' || i < data.length - 1) { // Avoid leading undefined rect in instant view
|
||||
graph.addRect(undefined, false, graph.getLegend(mode, date, i));
|
||||
before_last_value = last_value;
|
||||
last_value = null;
|
||||
}
|
||||
i += 1;
|
||||
});
|
||||
if (mode != 'now') {
|
||||
provider.getConvertInfo(rate.getRate(), function(base_price){
|
||||
// Assume that base_price is not dependent of rate type
|
||||
graph.setOverview(s + base_price * modifier);
|
||||
});
|
||||
} else {
|
||||
graph.setOverview(last_good_value);
|
||||
}
|
||||
graph.stopLoading();
|
||||
if (callback) callback();
|
||||
});
|
||||
|
||||
graph.last_call = Date.now() / 1000.0;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Go and get new values. This function should be called regularely by the main loop.
|
||||
*/
|
||||
api.update = function() {
|
||||
if (menu.getMode() == 'now' && menu.getDate() === null && menu.isUpdated()) {
|
||||
provider.getSensorId(function(sensor_id) {
|
||||
var target
|
||||
= '/' + sensor_id + '/get/'
|
||||
+ menu.getUnitString()
|
||||
+ '/by_time/'
|
||||
+ graph.last_call + '/'
|
||||
+ (graph.last_call = Date.now() / 1000.0) + '/'
|
||||
+ Config.timestep;
|
||||
|
||||
provider.get(target, function(data) {
|
||||
data.map(function(m) {
|
||||
if (m !== null) {
|
||||
graph.addRect(m.value);
|
||||
graph.setOverview(m.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return api;
|
||||
};
|
7
CitizenWatt-Base/static/js/conso/Config.js
Normal file
@ -0,0 +1,7 @@
|
||||
// General params
|
||||
var Config = {
|
||||
update_timeout: 2000, // Milliseconds
|
||||
rect_width: 12, // Pixels
|
||||
rect_margin: 2, // Pixels
|
||||
timestep: 8 // Second
|
||||
};
|
89
CitizenWatt-Base/static/js/conso/DataProvider.js
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Provide a way to get new data.
|
||||
*/
|
||||
var DataProvider = function() {
|
||||
var api = {};
|
||||
var energy_provider = null; // Cache for energy provider info
|
||||
var sensor_id = null; // Cache for sensor ID
|
||||
|
||||
api.onratechange = function(rate){};
|
||||
|
||||
/**
|
||||
* Get new data from server.
|
||||
* @param target: Type of data to get (@see API specification)
|
||||
* @param callback: callback that take data as first argument
|
||||
*/
|
||||
api.get = function(target, callback) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', API_URL + target, true);
|
||||
req.send();
|
||||
req.onreadystatechange = function() {
|
||||
if (req.readyState == 4) {
|
||||
var res;
|
||||
try {
|
||||
res = JSON.parse(req.responseText);
|
||||
}
|
||||
catch (e) {
|
||||
console.log('ERROR while handling `' + target + '`:', req.responseText);
|
||||
}
|
||||
if (res && res.rate) {
|
||||
api.onratechange(res.rate);
|
||||
}
|
||||
if (res && res.data === null) res.data = [];
|
||||
callback(res.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get current provider info
|
||||
* @param callback: callback that takes provider
|
||||
*/
|
||||
api.getProviderInfo = function(callback) {
|
||||
if (energy_provider === null) {
|
||||
api.get('/energy_providers/current', function(provider) {
|
||||
energy_provider = provider;
|
||||
callback(energy_provider);
|
||||
});
|
||||
} else {
|
||||
callback(energy_provider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get watt_euros convertion info
|
||||
* @param rate: day or night tariff
|
||||
* @param callback: callback that takes origin value and slope
|
||||
*/
|
||||
api.getConvertInfo = function(rate, callback) {
|
||||
api.getProviderInfo(function(provider){
|
||||
callback(
|
||||
provider[rate+'_slope_watt_euros'],
|
||||
provider[rate+'_constant_watt_euros']
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get active sensor ID (active iff name is 'CitizenWatt')
|
||||
* @param callback: callback that takes sensor ID
|
||||
*/
|
||||
api.getSensorId = function(callback) {
|
||||
if (sensor_id == null) {
|
||||
api.get('/sensors', function(sensors) {
|
||||
for (var i = sensors.length - 1; i >= 0; i--) {
|
||||
if (sensors[i].name == 'CitizenWatt')
|
||||
sensor_id = sensors[i].id;
|
||||
};
|
||||
callback(sensor_id);
|
||||
});
|
||||
} else {
|
||||
callback(sensor_id);
|
||||
}
|
||||
}
|
||||
|
||||
return api;
|
||||
};
|
345
CitizenWatt-Base/static/js/conso/Graph.js
Normal file
@ -0,0 +1,345 @@
|
||||
/**
|
||||
* Gather graph-related functions
|
||||
* @param unit: (optional) Graphe unit
|
||||
* @param max_value: (optional) Defautl graph max_value
|
||||
*/
|
||||
var Graph = function(unit, max_value) {
|
||||
var api = {};
|
||||
|
||||
var graph = document.getElementById('graph')
|
||||
, graph_vertical_axis = document.getElementById('graph_vertical_axis')
|
||||
, graph_values = document.getElementById('graph_values')
|
||||
, graph_loading = document.getElementById('graph_loading')
|
||||
, now = document.getElementById('now')
|
||||
, now_label = document.getElementById('now_label')
|
||||
, sum, n_values, mean
|
||||
;
|
||||
|
||||
/**
|
||||
* Function used to convert values received from server
|
||||
*/
|
||||
api.convertValue = function(v){ return v; };
|
||||
|
||||
api.unit = unit || 'W';
|
||||
api.type = 'energy';
|
||||
api.rect_width = Config.rect_width;
|
||||
api.rect_margin = Config.rect_margin;
|
||||
api.autoremove = true;
|
||||
api.max_value = max_value || 1e-6;
|
||||
|
||||
/**
|
||||
* Set color class name from height (between 0.0 and 1.0)
|
||||
*/
|
||||
api.colorize = function(t) {
|
||||
return (t > 33.3 ? (t >= 66.7 ? 'red' : 'orange') : 'yellow');
|
||||
}
|
||||
|
||||
/**
|
||||
* Round value according to max value
|
||||
*/
|
||||
api.round = function(v) {
|
||||
return Math.round(v * 10) / 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init graph
|
||||
*/
|
||||
api.init = function() {
|
||||
n_values = 0;
|
||||
|
||||
var graduations = [0.00, 0.33, 0.66, 1.00]; // Graduation positions (relative)
|
||||
graduations.map(function (t) {
|
||||
api.addVerticalGraduation(t)
|
||||
});
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value disaplyed in overview
|
||||
* @param power: value to display
|
||||
*/
|
||||
api.setOverview = function(power) {
|
||||
if (power === null) {
|
||||
now.innerHTML = ' ';
|
||||
return;
|
||||
}
|
||||
if (api.unit == 'cents/min') power = power / 8 * 60 * 100;
|
||||
now.innerHTML = api.round(power) + (api.unit == 'cents/min' ? ' centimes par minute' : api.unit);
|
||||
var height = power / api.max_value * 100;
|
||||
now.className = 'blurry ' + api.colorize(height);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set label under overview field.
|
||||
* @pram label: new label
|
||||
*/
|
||||
api.setOverviewLabel = function(label) {
|
||||
now_label.innerHTML = label;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a new rect to the graph.
|
||||
* @param power: Power represented by the rect.
|
||||
* @param animated: (optional) Whether the addition of the value must be animated. Default to True
|
||||
* @param legend: (optional) Legend to add to the rect
|
||||
*/
|
||||
api.addRect = function(power, animated, legend) {
|
||||
if (animated === undefined) animated = true;
|
||||
var defined = true;
|
||||
if (power === undefined) {
|
||||
defined = false;
|
||||
power = 0;
|
||||
}
|
||||
|
||||
if (unit == 'cents/min') {
|
||||
power = power / 8 * 60 * 100;
|
||||
}
|
||||
|
||||
if (power > api.max_value) {
|
||||
api.scaleVertically(power / api.max_value);
|
||||
}
|
||||
|
||||
var div = document.createElement('a');
|
||||
div.setAttribute('href', location.hash); // TODO
|
||||
graph_values.appendChild(div);
|
||||
|
||||
div.className = 'rect';
|
||||
if (!defined) div.className += ' undefined';
|
||||
if (animated) div.className += ' animated';
|
||||
div.className += ' ' + api.type;
|
||||
|
||||
var info = document.createElement('div');
|
||||
div.appendChild(info);
|
||||
|
||||
info.className = 'rect-info';
|
||||
if (defined) info.innerHTML = api.round(power) + api.unit;
|
||||
else info.innerHTML = '<em>Pas de donnée</em>';
|
||||
if (legend) info.innerHTML += '<br/>' + legend;
|
||||
|
||||
var color = document.createElement('div');
|
||||
div.appendChild(color);
|
||||
|
||||
var height = api.round(power) / api.max_value * 100;
|
||||
var color_class = api.colorize(height);
|
||||
|
||||
color.className = 'rect-color ' + color_class + '-day';
|
||||
color.style.height = height + '%';
|
||||
|
||||
|
||||
++n_values;
|
||||
|
||||
var max_values = api.getWidth();
|
||||
// Le +10 c'est pour prendre de la marge. On ne peut pas mettre
|
||||
// simplement 1 sinon ça se voit lorsque plusieurs nouvelles mesures
|
||||
// arrivent en même temps.
|
||||
if (api.autoremove && n_values > max_values + 10) {
|
||||
/*
|
||||
graph_values.firstChild.style.width = '0';
|
||||
graph_values.firstChild.addEventListener('transitionend', function(){
|
||||
graph_values.removeChild(this);
|
||||
}, false);
|
||||
*/
|
||||
graph_values.removeChild(graph_values.firstChild)
|
||||
}
|
||||
|
||||
div.style.width = api.rect_width + 'px';
|
||||
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove last rect from graph
|
||||
* @return api;
|
||||
*/
|
||||
api.removeRect = function() {
|
||||
if (graph_values.lastChild)
|
||||
graph_values.removeChild(graph_values.lastChild);
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an horizontal graduation line (so a graduation for the vertical axis)
|
||||
* @param pos: Relative position at which the graduation is placed
|
||||
*/
|
||||
api.addVerticalGraduation = function(pos) {
|
||||
var height = pos * 100;
|
||||
var span = document.createElement('span');
|
||||
graph_vertical_axis.appendChild(span);
|
||||
|
||||
span.style.bottom = height + '%';
|
||||
span.setAttribute('cw-graduation-position', pos);
|
||||
api.updateVerticalGraduation(span);
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an horizontal absolute graduation line
|
||||
* @param pos: Absolute position at which the graduation is placed
|
||||
*/
|
||||
api.addAbsoluteVerticalGraduation = function(pos) {
|
||||
if (pos * 1.1 > api.max_value) {
|
||||
api.scaleVertically(pos * 1.1 / api.max_value);
|
||||
}
|
||||
|
||||
var height = pos / api.max_value * 100;
|
||||
var span = document.createElement('span');
|
||||
var hr_id = rand64(5);
|
||||
graph_vertical_axis.appendChild(span);
|
||||
|
||||
span.style.bottom = height + '%';
|
||||
span.setAttribute('cw-absolute-graduation-position', pos);
|
||||
span.setAttribute('cw-absolute-graduation-hr', hr_id);
|
||||
|
||||
var hr = document.createElement('hr');
|
||||
hr.style.bottom = height + '%';
|
||||
hr.id = hr_id;
|
||||
hr.className = 'absolute-graduation-hr';
|
||||
graph.appendChild(hr);
|
||||
|
||||
api.updateVerticalGraduation(span);
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update displayed value of vertical graduation
|
||||
* @param graduation: graduation to resize
|
||||
*/
|
||||
api.updateVerticalGraduation = function(graduation) {
|
||||
if (graduation.getAttribute('cw-graduation-position') !== null) {
|
||||
var power = api.round(graduation.getAttribute('cw-graduation-position') * api.max_value);
|
||||
graduation.innerHTML = power + api.unit;
|
||||
}
|
||||
if (graduation.getAttribute('cw-absolute-graduation-position') !== null) {
|
||||
var pos = graduation.getAttribute('cw-absolute-graduation-position');
|
||||
var hr_id = graduation.getAttribute('cw-absolute-graduation-hr');
|
||||
var hr = document.getElementById(hr_id);
|
||||
hr.style.bottom = graduation.style.bottom = (pos / api.max_value * 100) + '%';
|
||||
var power = api.round(pos);
|
||||
graduation.innerHTML = power + api.unit;
|
||||
}
|
||||
return api;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change single rect vertical scale without modifying the value it represents.
|
||||
* @param rect: rect to resize
|
||||
* @param ratio: Value by which multiply the rect vertical scale
|
||||
*/
|
||||
api.scaleRect = function(rect, ratio) {
|
||||
var color = rect.getElementsByClassName('rect-color')[0];
|
||||
height = parseInt(color.style.height.slice(0, -1));
|
||||
new_height = height / ratio;
|
||||
color.style.height = new_height + '%';
|
||||
|
||||
var color_class = api.colorize(new_height);
|
||||
color.className = color.className.replace(/[^ ]*-day/, color_class + '-day');
|
||||
return api;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change graph vertical scale
|
||||
* @param ratio: Value by which multiply the graph vertical scale
|
||||
*/
|
||||
api.scaleVertically = function(ratio) {
|
||||
api.max_value = api.max_value * ratio;
|
||||
|
||||
var rects = graph_values.children;
|
||||
for (var i = 0 ; i < rects.length ; i++) {
|
||||
api.scaleRect(rects[i], ratio);
|
||||
}
|
||||
var graduations = graph_vertical_axis.children;
|
||||
for (var i = 0 ; i < graduations.length ; i++) {
|
||||
api.updateVerticalGraduation(graduations[i]);
|
||||
}
|
||||
return api;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return the width of the graph in pixels
|
||||
*/
|
||||
api.getPixelWidth = function() {
|
||||
return graph.clientWidth;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return the width of the graph in number of values that can be displayed
|
||||
*/
|
||||
api.getWidth = function() {
|
||||
return Math.floor(api.getPixelWidth() / (api.rect_width + api.rect_margin));
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean graph (remove all values)
|
||||
*/
|
||||
api.clean = function() {
|
||||
while (graph_values.firstChild)
|
||||
graph_values.removeChild(graph_values.firstChild);
|
||||
while (graph_vertical_axis.firstChild)
|
||||
graph_vertical_axis.removeChild(graph_vertical_axis.firstChild);
|
||||
|
||||
var hr;
|
||||
while (hr = document.getElementsByClassName('absolute-graduation-hr')[0])
|
||||
hr.parentNode.removeChild(hr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide loading icon
|
||||
*/
|
||||
api.stopLoading = function() {
|
||||
graph_loading.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide loading icon
|
||||
*/
|
||||
api.startLoading = function() {
|
||||
graph_loading.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a human readable legend
|
||||
* @param mode: 'now', 'day', 'week', 'month'
|
||||
* @param date: view date
|
||||
* @param i: index
|
||||
* [Static]
|
||||
*/
|
||||
api.getLegend = function(mode, date, i) {
|
||||
switch(mode) {
|
||||
case 'now':
|
||||
return '';
|
||||
|
||||
case 'day':
|
||||
return i + 'h - ' + (i+1) + 'h';
|
||||
|
||||
case 'week':
|
||||
return dateutils.getStringDay(i);
|
||||
|
||||
case 'month':
|
||||
return i + ' ' + dateutils.getStringMonth(date);
|
||||
}
|
||||
};
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
|
||||
var PriceGraph = function(unit) {
|
||||
var api = Graph(unit);
|
||||
|
||||
api.type = 'price';
|
||||
|
||||
api.colorize = function(t) {
|
||||
return (t > 33.3 ? (t >= 66.7 ? 'dark-blue' : 'blue') : 'light-blue');
|
||||
}
|
||||
|
||||
return api;
|
||||
};
|
||||
|
45
CitizenWatt-Base/static/js/conso/HashManager.js
Normal file
@ -0,0 +1,45 @@
|
||||
var HashManager = function() {
|
||||
var api = {};
|
||||
var unit, mode;
|
||||
|
||||
components = location.hash.slice(1).split('-');
|
||||
unit = components[0];
|
||||
mode = components[1] || 'now';
|
||||
date = components.length > 2 ? new Date(parseInt(components[2])*1000) : null;
|
||||
|
||||
api.updateHash= function(){
|
||||
var hash = '#' + unit + '-' + mode;
|
||||
if (date !== null) hash += '-' + Math.floor(date.getTime() / 1000);
|
||||
location.hash = hash;
|
||||
};
|
||||
|
||||
api.setUnit = function(new_unit) {
|
||||
unit = new_unit;
|
||||
api.updateHash();
|
||||
};
|
||||
|
||||
api.getUnit = function() {
|
||||
return unit;
|
||||
};
|
||||
|
||||
api.setMode = function(new_mode) {
|
||||
mode = new_mode;
|
||||
api.updateHash();
|
||||
};
|
||||
|
||||
api.getMode = function() {
|
||||
return mode;
|
||||
};
|
||||
|
||||
api.setDate = function(new_date) {
|
||||
date = new_date;
|
||||
api.updateHash();
|
||||
};
|
||||
|
||||
api.getDate = function() {
|
||||
return date;
|
||||
};
|
||||
|
||||
return api;
|
||||
};
|
||||
|
242
CitizenWatt-Base/static/js/conso/Menu.js
Normal file
@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Handle graph menu buttons
|
||||
*/
|
||||
var Menu = function() {
|
||||
var api = {};
|
||||
var now_btn = document.getElementById('scale-now')
|
||||
, day_btn = document.getElementById('scale-day')
|
||||
, week_btn = document.getElementById('scale-week')
|
||||
, month_btn = document.getElementById('scale-month')
|
||||
, unit_energy = document.getElementById('unit-energy')
|
||||
, unit_price = document.getElementById('unit-price')
|
||||
, update_toggle = document.getElementById('update-toggle')
|
||||
, prev = document.getElementById('prev')
|
||||
, next = document.getElementById('next')
|
||||
, mode = ''
|
||||
, unit = ''
|
||||
, date = null // means 'now'
|
||||
, is_updated = true
|
||||
;
|
||||
|
||||
api.onunitchange = function(unit, callback){};
|
||||
api.onmodechange = function(mode, callback){};
|
||||
api.ondatechange = function(date, callback){};
|
||||
|
||||
// Defined by user view width. Default to 15min
|
||||
api.timeWidth = 15*60*1000;
|
||||
|
||||
/**
|
||||
* Add menu listeners
|
||||
*/
|
||||
api.init = function() {
|
||||
now_btn.addEventListener('click', function() {
|
||||
api.setMode('now');
|
||||
});
|
||||
|
||||
day_btn.addEventListener('click', function() {
|
||||
api.setMode('day');
|
||||
});
|
||||
|
||||
week_btn.addEventListener('click', function() {
|
||||
api.setMode('week');
|
||||
});
|
||||
|
||||
month_btn.addEventListener('click', function() {
|
||||
api.setMode('month');
|
||||
});
|
||||
|
||||
unit_energy.addEventListener('click', function() {
|
||||
api.setUnit('energy');
|
||||
});
|
||||
|
||||
unit_price.addEventListener('click', function() {
|
||||
api.setUnit('price');
|
||||
});
|
||||
|
||||
prev.addEventListener('click', function() {
|
||||
now_btn.className = '';
|
||||
day_btn.className = '';
|
||||
week_btn.className = '';
|
||||
month_btn.className = '';
|
||||
api.setDate(new Date((date || new Date()).getTime() - api.getTimeWidth()));
|
||||
});
|
||||
|
||||
next.addEventListener('click', function() {
|
||||
now_btn.className = '';
|
||||
day_btn.className = '';
|
||||
week_btn.className = '';
|
||||
month_btn.className = '';
|
||||
api.setDate(new Date((date || new Date()).getTime() + api.getTimeWidth()));
|
||||
});
|
||||
|
||||
update_toggle.addEventListener('click', function() {
|
||||
is_updated = !is_updated;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display mode
|
||||
*/
|
||||
api.getMode = function() {
|
||||
return mode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set display mode.
|
||||
* @param mode: New mode
|
||||
* @param callback: (optional)
|
||||
* @param fire_event: (optional) false to avoid running ondatechange
|
||||
* @return boolean Whether the mode is accepted.
|
||||
*/
|
||||
api.setMode = function(new_mode, callback, fire_event) {
|
||||
if (fire_event === undefined) fire_event = true;
|
||||
now_btn.className = '';
|
||||
day_btn.className = '';
|
||||
week_btn.className = '';
|
||||
month_btn.className = '';
|
||||
switch(new_mode) {
|
||||
case 'now':
|
||||
now_btn.className = 'active';
|
||||
break;
|
||||
case 'day':
|
||||
day_btn.className = 'active';
|
||||
break;
|
||||
case 'week':
|
||||
week_btn.className = 'active';
|
||||
break;
|
||||
case 'month':
|
||||
month_btn.className = 'active';
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (new_mode != mode || date != null) {
|
||||
mode = new_mode;
|
||||
|
||||
if (fire_event) api.onmodechange(mode, callback);
|
||||
else if (callback) callback();
|
||||
}
|
||||
else if (callback) callback();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set unit.
|
||||
* @param unit: New unit
|
||||
* @param callback: (optional)
|
||||
* @param fire_event: (optional) false to avoid running ondatechange
|
||||
* @return boolean Whether the unit is accepted.
|
||||
*/
|
||||
api.setUnit = function(new_unit, callback, fire_event) {
|
||||
if (fire_event === undefined) fire_event = true;
|
||||
unit_energy.className = '';
|
||||
unit_price.className = '';
|
||||
switch(new_unit) {
|
||||
case 'energy':
|
||||
unit_energy.className = 'active';
|
||||
break;
|
||||
case 'price':
|
||||
unit_price.className = 'active';
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (new_unit != unit) {
|
||||
unit = new_unit;
|
||||
|
||||
if (fire_event) api.onunitchange(unit, callback);
|
||||
else if (callback) callback();
|
||||
}
|
||||
else if (callback) callback();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get unit.
|
||||
*/
|
||||
api.getUnit = function() {
|
||||
return unit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get unit string, which designate unit in ascii chars.
|
||||
* This is used for example in the API.
|
||||
* @return unit string.
|
||||
*/
|
||||
api.getUnitString = function() {
|
||||
return {
|
||||
'energy': mode == 'now' ? 'watts' : 'kwatthours',
|
||||
'price': 'euros'
|
||||
}[unit];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get unit shortcut.
|
||||
* This is used for displaying.
|
||||
* @return unit shortcut.
|
||||
*/
|
||||
api.getUnitShortcut = function() {
|
||||
return {
|
||||
'energy': mode == 'now' ? 'W' : 'kWh',
|
||||
'price': '€'
|
||||
}[unit];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get date.
|
||||
*/
|
||||
api.getDate = function() {
|
||||
return date;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set date.
|
||||
* @param date: New date
|
||||
* @param callback: (optional)
|
||||
* @param fire_event: (optional) false to avoid running ondatechange
|
||||
*/
|
||||
api.setDate = function(new_date, callback, fire_event) {
|
||||
if (fire_event === undefined) fire_event = true;
|
||||
if (date != new_date) {
|
||||
date = new_date;
|
||||
|
||||
// If 'now' view and new date near now, restore auto update
|
||||
if (mode == 'now' && date !== null) {
|
||||
var now = new Date();
|
||||
if (Math.abs(date.getTime() - now.getTime()) < api.timeWidth / 2) {
|
||||
date = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (fire_event) api.ondatechange(unit, callback);
|
||||
else if (callback) callback();
|
||||
}
|
||||
else if (callback) callback();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return view width in milliseconds
|
||||
*/
|
||||
api.getTimeWidth = function() {
|
||||
switch (mode) {
|
||||
case 'now':
|
||||
return api.timeWidth; // Written
|
||||
case 'day':
|
||||
return dateutils.getDayLength();
|
||||
case 'week':
|
||||
return dateutils.getWeekLength();
|
||||
case 'month':
|
||||
return dateutils.getMonthLength(date);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return whether auto-update is activated
|
||||
*/
|
||||
api.isUpdated = function() {
|
||||
return is_updated;
|
||||
}
|
||||
|
||||
|
||||
return api;
|
||||
}
|
28
CitizenWatt-Base/static/js/conso/RateDisplay.js
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Display rate type (day or night) in the header
|
||||
*/
|
||||
var RateDisplay = function() {
|
||||
var api = {};
|
||||
|
||||
var logo_day = document.getElementById('rate-logo-day')
|
||||
, logo_night = document.getElementById('rate-logo-night')
|
||||
;
|
||||
var rate;
|
||||
|
||||
api.setRate = function(new_rate) {
|
||||
if (new_rate != rate) {
|
||||
logo_day.style.display = logo_night.style.display = 'none';
|
||||
(new_rate == 'day' ? logo_day : logo_night).style.display = 'block';
|
||||
rate = new_rate;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return current rate ('day' or 'night')
|
||||
*/
|
||||
api.getRate = function() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
return api;
|
||||
}
|
16
CitizenWatt-Base/static/js/conso/tail.js
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Main function
|
||||
* Create app, Init it and then update it regularly
|
||||
*/
|
||||
function init() {
|
||||
var app = App();
|
||||
|
||||
app.oninit = function() {
|
||||
app.update();
|
||||
setTimeout(arguments.callee, Config.update_timeout);
|
||||
}
|
||||
|
||||
app.init();
|
||||
}
|
||||
|
||||
window.onload = init();
|
212
CitizenWatt-Base/static/js/dateutils.js
Normal file
@ -0,0 +1,212 @@
|
||||
var dateutils = (function() {
|
||||
var api = {};
|
||||
|
||||
/**
|
||||
* Difference between base (raspi) time and local user
|
||||
*/
|
||||
api.offset = 0;
|
||||
|
||||
/**
|
||||
* Get hour length (in milliseconds)
|
||||
*/
|
||||
api.getHourLength = function() {
|
||||
return 3600 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day length (in milliseconds)
|
||||
*/
|
||||
api.getDayLength = function() {
|
||||
return api.getHourLength() * 24;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current day start (in millisecond timestamp)
|
||||
* @param date: (optional) Replace current date
|
||||
*/
|
||||
api.getDayStart = function(date) {
|
||||
var date = date || new Date();
|
||||
return (new Date(date.getFullYear(), date.getMonth(), date.getDate())).getTime() + api.offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current day end (in millisecond timestamp)
|
||||
* @param date: (optional) Replace current date
|
||||
*/
|
||||
api.getDayEnd = function(date) {
|
||||
var date = date || new Date();
|
||||
var day = (date.getHours() + 6) % 7;
|
||||
return (new Date(date.getFullYear(), date.getMonth(), date.getDate()+1)).getTime() + api.offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get week length (in milliseconds)
|
||||
*/
|
||||
api.getWeekLength = function() {
|
||||
return api.getDayLength() * 7;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current week start (in millisecond timestamp)
|
||||
* @param date: (optional) Replace current date
|
||||
*/
|
||||
api.getWeekStart = function(date) {
|
||||
var date = date || new Date();
|
||||
var day = (date.getDay() + 6) % 7;
|
||||
return (new Date(date.getFullYear(), date.getMonth(), date.getDate() - day)).getTime() + api.offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current week end (in millisecond timestamp)
|
||||
* @param date: (optional) Replace current date
|
||||
*/
|
||||
api.getWeekEnd = function(date) {
|
||||
var date = date || new Date();
|
||||
var day = (date.getDay() + 6) % 7;
|
||||
return (new Date(date.getFullYear(), date.getMonth(), date.getDate() - day + 7)).getTime() + api.offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current month length (in milliseconds)
|
||||
* @param date: (optional) Replace current date
|
||||
*/
|
||||
api.getMonthLength = function(date) {
|
||||
var date = date || new Date();
|
||||
return (new Date(date.getFullYear(),date.getMonth()+1,0)).getDate() * api.getDayLength();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current month start (in millisecond timestamp)
|
||||
* @param date: (optional) Replace current date
|
||||
*/
|
||||
api.getMonthStart = function(date) {
|
||||
var date = date || new Date();
|
||||
return (new Date(date.getFullYear(), date.getMonth(), 1)).getTime() + api.offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current week end (in millisecond timestamp)
|
||||
* @param date: (optional) Replace current date
|
||||
*/
|
||||
api.getMonthEnd = function(date) {
|
||||
var date = date || new Date();
|
||||
return (new Date(date.getFullYear(), date.getMonth()+1, 1)).getTime() + api.offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return human readable day of week
|
||||
* @param i: index of day or date
|
||||
*/
|
||||
api.getStringDay = function(i) {
|
||||
if (i.getDay !== undefined) i = (i.getDay() + 6) % 7;
|
||||
return ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'][i];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return human readable month
|
||||
* @param i: index of month or date
|
||||
*/
|
||||
api.getStringMonth = function(i) {
|
||||
if (i.getMonth !== undefined) i = i.getMonth();
|
||||
return ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'][i];
|
||||
};
|
||||
|
||||
/**
|
||||
* Human readable time. Relative for first values (now, 1 min ago, etc) and then absolute.
|
||||
* @param date
|
||||
* @return string time
|
||||
*/
|
||||
api.humanTime = function(date) {
|
||||
var now = new Date();
|
||||
var prefix = now > date ? 'il y a ' : 'dans ';
|
||||
var diff = Math.abs(date.getTime() - now.getTime());
|
||||
if (diff < 60*1000)
|
||||
return prefix + Math.round(diff / 1000) + 's';
|
||||
|
||||
if (Math.abs(date.getTime() - now.getTime()) < 3600*1000)
|
||||
return prefix + Math.round(diff / 60000) + 'min';// + Math.abs(date.getSeconds() - comp.getSeconds()) + 's';
|
||||
|
||||
if (api.getDayStart(date) == api.getDayStart())
|
||||
return date.getHours() + 'h' + date.getMinutes();
|
||||
|
||||
return api.humanDay() + ' à ' + date.getHours() + 'h' + date.getMinutes();
|
||||
};
|
||||
|
||||
/**
|
||||
* Human readable date. Relative for first values (today, yesterday) and then absolute.
|
||||
* @param date
|
||||
* @return string date
|
||||
*/
|
||||
api.humanDay = function(date) {
|
||||
var comp = new Date();
|
||||
if (api.getDayStart(comp) == api.getDayStart(date))
|
||||
return 'aujourd\'hui';
|
||||
|
||||
comp.setDate(comp.getDate() + 1);
|
||||
if (api.getDayStart(comp) == api.getDayStart(date))
|
||||
return 'demain';
|
||||
|
||||
comp.setDate(comp.getDate() - 2);
|
||||
if (api.getDayStart(comp) == api.getDayStart(date))
|
||||
return 'hier';
|
||||
|
||||
if (api.getWeekStart() == api.getWeekStart(date) && date < (new Date()))
|
||||
return api.getStringDay(date).toLowerCase() + ' dernier';
|
||||
|
||||
//if (api.getMonthStart() == api.getMonthStart(date))
|
||||
// return 'le ' + date.getDate();
|
||||
|
||||
return 'le ' + date.getDate() + ' ' + api.getStringMonth(date).toLowerCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Human readable week. Relative for first values (this week, past week) and then absolute.
|
||||
* @param date
|
||||
* @return string week
|
||||
*/
|
||||
api.humanWeek = function(date) {
|
||||
var comp = new Date();
|
||||
if (api.getWeekStart(comp) == api.getWeekStart(date))
|
||||
return 'cette semaine';
|
||||
|
||||
comp.setDate(comp.getDate() + 7);
|
||||
if (api.getWeekStart(comp) == api.getWeekStart(date))
|
||||
return 'la semaine prochaine';
|
||||
|
||||
comp.setDate(comp.getDate() - 14);
|
||||
if (api.getWeekStart(comp) == api.getWeekStart(date))
|
||||
return 'la semaine dernière';
|
||||
|
||||
var f = new Date(api.getWeekStart(date));
|
||||
var l = new Date(api.getWeekEnd(date) - 1);
|
||||
var v
|
||||
= 'entre le ' + f.getDate() + ' ' + api.getStringMonth(f)
|
||||
+ ' et le ' + l.getDate() + ' ' + api.getStringMonth(l);
|
||||
return v.toLowerCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Human readable month.
|
||||
* @param date
|
||||
* @return string month
|
||||
*/
|
||||
api.humanMonth = function(date) {
|
||||
var comp = new Date();
|
||||
if (api.getMonthStart(comp) == api.getMonthStart(date))
|
||||
return 'ce mois';
|
||||
|
||||
return 'en ' + api.getStringMonth(date).toLowerCase();
|
||||
};
|
||||
|
||||
return api;
|
||||
})();
|
||||
|
||||
|
||||
/*
|
||||
// Exports all for unit testing
|
||||
for (var property in dateutils) {
|
||||
if (dateutils.hasOwnProperty(property)) {
|
||||
exports[property] = dateutils[property];
|
||||
}
|
||||
}
|
||||
//*/
|
29
CitizenWatt-Base/static/js/target.js
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
function init() {
|
||||
var target_ok = document.getElementById('target-ok')
|
||||
, target_wip = document.getElementById('target-wip')
|
||||
, target_no = document.getElementById('target-no')
|
||||
, target_more = document.getElementById('target-more')
|
||||
;
|
||||
|
||||
target_ok.style.display = 'block';
|
||||
target_wip.style.display = 'block';
|
||||
target_no.style.display = 'block';
|
||||
target_more.style.display = 'none';
|
||||
switch (location.hash) {
|
||||
case '#ok':
|
||||
target_wip.style.display = 'none';
|
||||
target_no.style.display = 'none';
|
||||
target_more.style.display = 'block';
|
||||
break;
|
||||
|
||||
case '#wip':
|
||||
target_ok.style.display = 'none';
|
||||
target_no.style.display = 'none';
|
||||
target_more.style.display = 'block';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = init();
|
||||
window.addEventListener('hashchange', init);
|
15
CitizenWatt-Base/static/js/utils.js
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
/**
|
||||
* @param length: Length of random string
|
||||
* @return random string
|
||||
*/
|
||||
function rand64(length) {
|
||||
var text = "";
|
||||
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
||||
|
||||
for (var i = length - 1; i >= 0; i--) {
|
||||
text += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
9
CitizenWatt-Base/system/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
CitizenWatt System scripts
|
||||
==========================
|
||||
|
||||
Launch these two scripts as root, with bash :
|
||||
- sudo bash install_python34.sh
|
||||
- sudo bash cleanup_raspbian.sh (removes Gnome UI)
|
||||
|
||||
|
||||
* citizenwatt.sh is a Debian service to add to startup in /etc/init.d
|
31
CitizenWatt-Base/system/citizenwatt.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#### BEGIN INIT INFO
|
||||
# Provides: citizenwatt
|
||||
# Required-Start:
|
||||
# Required-Stop:
|
||||
# Should-Start:
|
||||
# Should-Stop:
|
||||
# Default-Start: 1 2 3 4 5
|
||||
# Default-Stop: 0 6
|
||||
# Short-Description: startup script for citizenwatt
|
||||
#### END INIT INFO
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
echo "Starting the webserver…"
|
||||
screen -dmS visu && screen -S visu -p 0 -X stuff "while true; do python3 visu.py; done$(printf \\r)"
|
||||
|
||||
echo "Starting receive script…"
|
||||
screen -dmS receive && screen -S receive -p 0 -X stuff "while true; do ./receive; done$(printf \\r)"
|
||||
echo "Done !\n"
|
||||
sleep 0.2
|
||||
echo "Starting processing script…"
|
||||
screen -dmS process && screen -S process -p 0 -X stuff "while true; do python3 process.py; done$(printf \\r)"
|
||||
echo "Done !\n"
|
||||
|
||||
while ! curl -s --head http://localhost:8080 2>&1 > /dev/null; do
|
||||
echo "Webserver is starting…"
|
||||
sleep 1
|
||||
done
|
||||
echo "Webserver started !\n"
|
||||
|
||||
echo "Ready to start !"
|
23
CitizenWatt-Base/system/cleanup_raspbian.sh
Normal file
@ -0,0 +1,23 @@
|
||||
# Citizenwatt Raspbian cleanup script
|
||||
# Launch as root
|
||||
|
||||
# Remove useless packages
|
||||
apt-get --yes purge x11-common lxde dillo gnome-icon-theme \
|
||||
gnome-themes-standard-data libgnome-keyring-common libgnome-keyring0 \
|
||||
libsoup-gnome2.4-1 lxde-common lxde-icon-theme omxplayer dbus-x11 libx11-6 \
|
||||
libx11-data libx11-xcb1 desktop-file-utils debian-reference-en \
|
||||
debian-reference-common java-common
|
||||
|
||||
apt-get --yes install mysql-client mysql-server avahi-daemon
|
||||
|
||||
# Remove unused packets
|
||||
apt-get --yes autoremove --purge
|
||||
|
||||
# Clear APT cache
|
||||
#apt-get clean
|
||||
|
||||
rm -rf /opt/vc /home/pi/Desktop /home/pi/python_games /home/pi/ocr_pi.png
|
||||
|
||||
# Do updates
|
||||
apt-get update && apt-get upgrade --yes
|
||||
|
35
CitizenWatt-Base/system/install_citizenwatt.sh
Normal file
@ -0,0 +1,35 @@
|
||||
# Citizenwatt install script
|
||||
# Launch as root
|
||||
|
||||
# Change Hostname
|
||||
echo "citizenwatt" > /etc/hostname
|
||||
|
||||
# Add citizenWatt repository
|
||||
echo "deb http://ks.citoyenscapteurs.net/repos/apt/debian/ wheezy main" > /etc/apt/sources.list.d/citizenwatt.list
|
||||
|
||||
# Add our GPG key
|
||||
wget -O - http://ks.citoyenscapteurs.net/repos/apt/citizenwatt.public.key | apt-key add -
|
||||
|
||||
# Install Python
|
||||
/bin/bash install_python34.sh
|
||||
|
||||
# Install packages
|
||||
# TODO : add citizenwatt-visu
|
||||
apt-get --yes install librf24-dev postgresql supervisor avahi-daemon redis-server iptables-persistent
|
||||
|
||||
# Install Python module deps
|
||||
apt-get -t jessie --yes install postgresql-server-dev-all
|
||||
|
||||
# Python modules
|
||||
pip3 install requests sqlalchemy pycrypto numpy cherrypy psycopg2 redis
|
||||
|
||||
# Database setup
|
||||
su - postgresql
|
||||
psql -c "CREATE DATABASE citizenwatt;"
|
||||
psql -c "CREATE USER citizenwatt PASSWORD 'citizenwatt';"
|
||||
psql -c "GRANT ALL ON DATABASE citizenwatt TO citizenwatt;"
|
||||
exit
|
||||
|
||||
# Firewall setup
|
||||
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination :8080
|
||||
/etc/init.d/iptables-persistent save
|
17
CitizenWatt-Base/system/install_python34.sh
Normal file
@ -0,0 +1,17 @@
|
||||
# Python 3.4 install script
|
||||
# Launch as root
|
||||
|
||||
# Add testing sources
|
||||
echo "deb http://mirrordirector.raspbian.org/raspbian/ jessie main" > /etc/apt/sources.list.d/jessie.list
|
||||
|
||||
# Pinning
|
||||
echo -e "Package: *\nPin: release a=testing\nPin-Priority: 300" > /etc/apt/preferences.d/jessie.pref
|
||||
|
||||
# Update
|
||||
apt-get update
|
||||
|
||||
# Install
|
||||
apt-get -t jessie --yes install python3 gcc python3-pip python3-dev
|
||||
|
||||
# Remove unused packets (ie. Python3.2)
|
||||
apt-get --yes autoremove --purge
|
14
CitizenWatt-Base/system/install_rf24.sh
Normal file
@ -0,0 +1,14 @@
|
||||
# CD to home
|
||||
cd
|
||||
|
||||
# Get RF24
|
||||
git clone https://github.com/stanleyseow/RF24.git RF24
|
||||
|
||||
# Checkout to our version
|
||||
cd RF24
|
||||
git checkout 2a1a4e6e27056844a3bc419d65b8a2d4e0f1770e
|
||||
|
||||
# Build
|
||||
cd librf24-rpi/librf24
|
||||
make
|
||||
sudo make install
|
34
CitizenWatt-Base/system/supervisor_citizenwatt.conf
Normal file
@ -0,0 +1,34 @@
|
||||
[program:receive]
|
||||
command=/opt/citizenwatt/receive
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startsecs=10
|
||||
startretries=10
|
||||
user=pi
|
||||
environment = HOME="/home/pi",USER="pi"
|
||||
stdout_logfile = NONE
|
||||
#stderr_logfile = NONE
|
||||
|
||||
[program:process]
|
||||
command=/usr/bin/python3 /opt/citizenwatt/process.py
|
||||
directory=/opt/citizenwatt/
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startsecs=10
|
||||
startretries=10
|
||||
user=pi
|
||||
environment = HOME="/home/pi",USER="pi"
|
||||
stdout_logfile = NONE
|
||||
#stderr_logfile = NONE
|
||||
|
||||
[program:visu]
|
||||
command=/usr/bin/python3 /opt/citizenwatt/visu.py
|
||||
directory=/opt/citizenwatt/
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startsecs=10
|
||||
startretries=10
|
||||
user=pi
|
||||
environment = HOME="/home/pi",USER="pi"
|
||||
stdout_logfile = NONE
|
||||
#stderr_logfile = NONE
|
1
CitizenWatt-Base/tests/libcitizenwatt
Symbolic link
@ -0,0 +1 @@
|
||||
../libcitizenwatt
|
73
CitizenWatt-Base/tests/test_process.py
Executable file
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate test data instead of piping from the sensor."""
|
||||
|
||||
import datetime
|
||||
import random
|
||||
import time
|
||||
import math
|
||||
|
||||
from libcitizenwatt import database
|
||||
from libcitizenwatt import tools
|
||||
from libcitizenwatt.config import Config
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
|
||||
def get_rate_type(db):
|
||||
"""Returns "day" or "night" according to current time
|
||||
"""
|
||||
user = db.query(database.User).filter_by(is_admin=1).first()
|
||||
now = datetime.datetime.now()
|
||||
now = 3600 * now.hour + 60 * now.minute
|
||||
if user is None:
|
||||
return -1
|
||||
elif user.end_night_rate > user.start_night_rate:
|
||||
if now > user.start_night_rate and now < user.end_night_rate:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
else:
|
||||
if now > user.start_night_rate or now < user.end_night_rate:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
# Configuration
|
||||
config = Config()
|
||||
|
||||
# DB initialization
|
||||
database_url = (config.get("database_type") + "://" + config.get("username") + ":" +
|
||||
config.get("password") + "@" + config.get("host") + "/" +
|
||||
config.get("database"))
|
||||
engine = create_engine(database_url, echo=config.get("debug"))
|
||||
create_session = sessionmaker(bind=engine)
|
||||
database.Base.metadata.create_all(engine)
|
||||
|
||||
try:
|
||||
while True:
|
||||
power = random.randint(0, 4000)
|
||||
power = math.sin(time.clock()*2)**2 * 2000
|
||||
print("New encrypted packet:" + str(power))
|
||||
|
||||
db = create_session()
|
||||
sensor = (db.query(database.Sensor)
|
||||
.filter_by(name="CitizenWatt")
|
||||
.first())
|
||||
if not sensor:
|
||||
tools.warning("Got packet "+str(power)+" but install is not " +
|
||||
"complete ! Visit http://citizenwatt first.")
|
||||
db.close()
|
||||
else:
|
||||
now = datetime.datetime.now().timestamp()
|
||||
measure_db = database.Measures(sensor_id=sensor.id,
|
||||
value=power,
|
||||
timestamp=now,
|
||||
night_rate=get_rate_type(db))
|
||||
db.add(measure_db)
|
||||
db.commit()
|
||||
print(now)
|
||||
print("Saved successfully.")
|
||||
time.sleep(8)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
48
CitizenWatt-Base/tests/tests.js
Normal file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/node
|
||||
|
||||
/* You have to uncomment the latest lines of ../static/js/datetutils.js to use these tests. */
|
||||
|
||||
var assert = require('assert');
|
||||
var dateutils = require('../static/js/dateutils');
|
||||
|
||||
assert(dateutils.getHourLength() == 3600 * 1000, 'Hour length');
|
||||
assert(dateutils.getDayLength() == 86400 * 1000, 'Day length' );
|
||||
assert(dateutils.getWeekLength() == 604800 * 1000, 'Week length');
|
||||
|
||||
assert(dateutils.getMonthLength(new Date(2000,1,0)) == 31*86400*1000, 'January length' );
|
||||
assert(dateutils.getMonthLength(new Date(2000,2,0)) == 29*86400*1000, 'February length');
|
||||
assert(dateutils.getMonthLength(new Date(2000,3,0)) == 31*86400*1000, 'March length' );
|
||||
assert(dateutils.getMonthLength(new Date(2000,4,0)) == 30*86400*1000, 'April length' );
|
||||
assert(dateutils.getMonthLength(new Date(2000,5,0)) == 31*86400*1000, 'May length' );
|
||||
|
||||
var d = new Date(2000,5,27,12,5,45,0);
|
||||
assert(dateutils.getDayStart(d) == (new Date(2000,5,27,0,0,0,0)).getTime(), '[1] Day start' );
|
||||
assert(dateutils.getDayEnd(d) == (new Date(2000,5,28,0,0,0,0)).getTime(), '[1] Day end' );
|
||||
assert(dateutils.getWeekStart(d) == (new Date(2000,5,26,0,0,0,0)).getTime(), '[1] Week start' );
|
||||
assert(dateutils.getWeekEnd(d) == (new Date(2000,6, 3,0,0,0,0)).getTime(), '[1] Week end' );
|
||||
assert(dateutils.getMonthStart(d) == (new Date(2000,5, 1,0,0,0,0)).getTime(), '[1] Month start');
|
||||
assert(dateutils.getMonthEnd(d) == (new Date(2000,6, 1,0,0,0,0)).getTime(), '[1] Month end' );
|
||||
|
||||
var d = new Date(1999,11,30,18,12,9,0);
|
||||
assert(dateutils.getDayStart(d) == (new Date(1999,11,30,0,0,0,0)).getTime(), '[2] Day start' );
|
||||
assert(dateutils.getDayEnd(d) == (new Date(1999,11,31,0,0,0,0)).getTime(), '[2] Day end' );
|
||||
assert(dateutils.getWeekStart(d) == (new Date(1999,11,27,0,0,0,0)).getTime(), '[2] Week start' );
|
||||
assert(dateutils.getWeekEnd(d) == (new Date(2000, 0, 3,0,0,0,0)).getTime(), '[2] Week end' );
|
||||
assert(dateutils.getMonthStart(d) == (new Date(1999,11, 1,0,0,0,0)).getTime(), '[2] Month start');
|
||||
assert(dateutils.getMonthEnd(d) == (new Date(2000, 0, 1,0,0,0,0)).getTime(), '[2] Month end' );
|
||||
|
||||
console.log(dateutils.getWeekEnd(d) - dateutils.getWeekStart(d))
|
||||
assert(dateutils.getDayEnd(d) - dateutils.getDayStart(d) == dateutils.getDayLength() , 'Day length consistency ');
|
||||
assert(dateutils.getWeekEnd(d) - dateutils.getWeekStart(d) == dateutils.getWeekLength() , 'Week length consistency ');
|
||||
assert(dateutils.getMonthEnd(d) - dateutils.getMonthStart(d) == dateutils.getMonthLength(d), 'Month length consistency');
|
||||
|
||||
assert(dateutils.getStringDay(3) == 'Jeudi' , 'String day by index' );
|
||||
assert(dateutils.getStringDay(d) == 'Jeudi' , 'String day by date' );
|
||||
assert(dateutils.getStringMonth(11) == 'Décembre', 'String month by index');
|
||||
assert(dateutils.getStringMonth( d) == 'Décembre', 'String month by date' );
|
||||
|
||||
assert(dateutils.humanDay(d) == 'le 30 décembre' , 'Human date' );
|
||||
assert(dateutils.humanWeek(d) == 'entre le 27 décembre et le 2 janvier', 'Human week' );
|
||||
assert(dateutils.humanMonth(d) == 'en décembre' , 'Human month');
|
||||
|
||||
console.log('Everything is ok.')
|
1
CitizenWatt-Base/updater.sh
Executable file
@ -0,0 +1 @@
|
||||
#!/bin/bash
|
34
CitizenWatt-Base/views/_begin.tpl
Normal file
@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CitizenWatt — {{ title }}</title>
|
||||
<link rel="stylesheet" href="{{ get_url('static', filename='css/normalize.css') }}">
|
||||
<link rel="stylesheet" href="{{ get_url('static', filename='css/style.css') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page">
|
||||
<header>
|
||||
<a href="{{ get_url('index') }}" class="header-logo">
|
||||
<img src="{{ get_url('static', filename='img/logo.png') }}" alt="Logo CitizenWatt"/>
|
||||
</a>
|
||||
<div id="rate-logo-day" class="rate-logo">
|
||||
<img src="{{ get_url('static', filename='img/sun.svg') }}" alt="Tarif de jour"/>
|
||||
<span>Tarif de jour</span>
|
||||
</div>
|
||||
<div id="rate-logo-night" class="rate-logo">
|
||||
<img src="{{ get_url('static', filename='img/moon.svg') }}" alt="Tarif de nuit"/>
|
||||
<span>Tarif de nuit</span>
|
||||
</div>
|
||||
|
||||
% if valid_session():
|
||||
<nav id="menu">
|
||||
<a {{ !'class="active"' if page=='home' else '' }} href="{{ get_url('index') }}">Accueil</a>
|
||||
<a {{ !'class="active"' if page=='conso' else '' }} href="{{ get_url('conso') }}">Conso</a>
|
||||
<a {{ !'class="active"' if page=='settings' else '' }} href="{{ get_url('settings') }}">Config</a>
|
||||
<a {{ !'class="active"' if page=='help' else '' }} href="{{ get_url('help') }}">Guide</a>
|
||||
<a {{ !'class="active"' if page=='community' else '' }} href="{{ get_url('community') }}">Communauté</a>
|
||||
</nav>
|
||||
% end
|
||||
</header>
|
27
CitizenWatt-Base/views/_end.tpl
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
<div class="clearfix pre-footer"><div/>
|
||||
<footer>
|
||||
<p>
|
||||
% if valid_session():
|
||||
<a href="{{ get_url('logout') }}">
|
||||
Déconnexion
|
||||
</a>
|
||||
% end
|
||||
</p>
|
||||
<p>
|
||||
Licence GNU GPL | <a href="http://citoyenscapteurs.net/">Citoyens Capteurs</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
// Constants set on the server side
|
||||
var API_URL = '{{ API_URL }}api';
|
||||
</script>
|
||||
% if defined('scripts'):
|
||||
% for script in scripts:
|
||||
<script src="{{ get_url('static', filename='js/' + script + '.js') }}"></script>
|
||||
% end
|
||||
% end
|
||||
</body>
|
||||
</html>
|
13
CitizenWatt-Base/views/community.tpl
Normal file
@ -0,0 +1,13 @@
|
||||
% include('_begin.tpl', title='Community', page='community')
|
||||
|
||||
<main>
|
||||
<div class="menu">
|
||||
<h1><img alt="" src="{{ get_url('static', filename='img/community.svg') }}" />Community</h1>
|
||||
</div>
|
||||
<div class="coming-soon">
|
||||
<img alt="" src="{{ get_url('static', filename='img/loading_simple.svg') }}" />
|
||||
<span>À venir…</span>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
% include('_end.tpl')
|
59
CitizenWatt-Base/views/conso.tpl
Normal file
@ -0,0 +1,59 @@
|
||||
% include('_begin.tpl', title='Consommation', page='conso')
|
||||
|
||||
<main>
|
||||
<div class="menu">
|
||||
<h1><img alt="" src="{{ get_url('static', filename='img/data.svg') }}" />Consommation</h1>
|
||||
</div>
|
||||
<div id="overview">
|
||||
<div>
|
||||
<p id="now" class="blurry red"> </p>
|
||||
<p id="now_label">Consommation actuelle</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="graph">
|
||||
<button id="prev"><</button>
|
||||
<div id="graph_loading">
|
||||
<img alt="Chargement" src="{{ get_url('static', filename='img/loading_simple.svg') }}" />
|
||||
</div>
|
||||
<div id="graph_values_wrapper">
|
||||
<div id="graph_values"></div>
|
||||
</div>
|
||||
<div id="graph_vertical_axis"></div>
|
||||
<hr style="bottom:33.3%"/>
|
||||
<hr style="bottom:66.7%"/>
|
||||
<button id="next">></button>
|
||||
</div>
|
||||
|
||||
|
||||
<nav id="scale">
|
||||
<button id="scale-now" class="active">Maintenant</button>
|
||||
<button id="scale-day">Aujourd'hui</button>
|
||||
<button id="scale-week">Cette semaine</button>
|
||||
<button id="scale-month">Ce mois</button>
|
||||
<button id="unit-energy" class="active">Watts</button>
|
||||
<button id="unit-price">Euros</button>
|
||||
</nav>
|
||||
|
||||
<button style="display: none" id="update-toggle">Start/Stop update</button>
|
||||
|
||||
<p style="text-align: center;">{{ provider }}</p>
|
||||
</main>
|
||||
|
||||
<%
|
||||
scripts = [
|
||||
'utils',
|
||||
'dateutils',
|
||||
'conso/Menu',
|
||||
'conso/Graph',
|
||||
'conso/DataProvider',
|
||||
'conso/RateDisplay',
|
||||
'conso/HashManager',
|
||||
'conso/App',
|
||||
'conso/Config',
|
||||
'conso/tail'
|
||||
]
|
||||
include('_end.tpl', scripts=scripts)
|
||||
%>
|
||||
|
32
CitizenWatt-Base/views/help.tpl
Normal file
@ -0,0 +1,32 @@
|
||||
% include('_begin.tpl', title='Guide', page='help')
|
||||
|
||||
<main>
|
||||
<div class="menu">
|
||||
<h1><img alt="" src="{{ get_url('static', filename='img/help.svg') }}" />Guide</h1>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<a href="http://wiki.citizenwatt.paris">
|
||||
<img alt="" src="{{ get_url('static', filename='img/wiki.svg') }}" />Wiki
|
||||
</a>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>
|
||||
Le <a href="http://wiki.citizenwatt.paris">wiki de CitizenWatt</a> regroupe toutes les informations dont vous pourriez avoir besoin pour utiliser cette interface, mais aussi les réponses à des questions sur le projet en général.
|
||||
</p>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<a href="{{ get_url('help') }}#contact" id="contact">
|
||||
<img alt="" src="{{ get_url('static', filename='img/contact.svg') }}" />Contact
|
||||
</a>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>
|
||||
Vous pouvez contacter l'équipe de CitizenWatt à l'adresse <a href="mailto:contact@citizenwatt.paris">contact@citizenwatt.paris</a>.
|
||||
</p>
|
||||
<p>
|
||||
Vous pouvez également utiliser <a href="http://www.citizenwatt.paris/#eight">ce formulaire</a>.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
% include('_end.tpl')
|
46
CitizenWatt-Base/views/index.tpl
Normal file
@ -0,0 +1,46 @@
|
||||
% include('_begin.tpl', title='Accueil', page='home')
|
||||
|
||||
<main>
|
||||
<div class="left-column">
|
||||
<div class="menu">
|
||||
<h1><a href="{{ get_url('conso') }}"><img alt="" src="{{ get_url('static', filename='img/data.svg') }}" />Consommation</a></h1>
|
||||
<a href="{{ get_url('conso') }}">
|
||||
<img alt="" src="{{ get_url('static', filename='img/progress.svg') }}" />En cours
|
||||
</a>
|
||||
<a href="{{ get_url('conso') }}#watt-day">
|
||||
<img alt="" src="{{ get_url('static', filename='img/small-data.svg') }}" />Aperçu global
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="menu">
|
||||
<h1><a href="{{get_url('settings') }}"><img alt="" src="{{ get_url('static', filename='img/target.svg') }}" />Configuration</a></h1>
|
||||
<a href="{{ get_url('settings') }}#user">
|
||||
<img alt="" src="{{ get_url('static', filename='img/user.svg') }}" />Utilisateur
|
||||
</a>
|
||||
<a href="{{ get_url('settings') }}#sensors">
|
||||
<img alt="" src="{{ get_url('static', filename='img/sensor.svg') }}" />Capteurs
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-column">
|
||||
<div class="menu">
|
||||
<h1><a href="{{ get_url('help') }}"><img alt="" src="{{ get_url('static', filename='img/help.svg') }}" />Guide</a></h1>
|
||||
<a href="http://wiki.citizenwatt.paris">
|
||||
<img alt="" src="{{ get_url('static', filename='img/wiki.svg') }}" />Wiki
|
||||
</a>
|
||||
<a href="{{ get_url('help') }}#contact">
|
||||
<img alt="" src="{{ get_url('static', filename='img/contact.svg') }}" />Contact
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="menu">
|
||||
<h1><a href="{{ get_url('community') }}"><img alt="" src="{{ get_url('static', filename='img/community.svg') }}" />Communauté</a></h1>
|
||||
<a href="{{ get_url('community') }}">
|
||||
<img alt="" src="{{ get_url('static', filename='img/loading.svg') }}" />À venir…
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
% include('_end.tpl')
|
80
CitizenWatt-Base/views/install.tpl
Normal file
@ -0,0 +1,80 @@
|
||||
% include('_begin.tpl', title='Accueil', page='home')
|
||||
|
||||
<main>
|
||||
<div class="menu">
|
||||
<h1><img title="" src="{{ get_url('static', filename='img/install.svg') }}" />Installation</h1>
|
||||
</div>
|
||||
<form action="" method="post">
|
||||
% if defined('err'):
|
||||
<div class="dialog-err">
|
||||
<h4>{{ err['title'] }}</h4>
|
||||
<p>
|
||||
{{ err['content'] }}
|
||||
</p>
|
||||
</div>
|
||||
% end
|
||||
|
||||
<h2>Utilisateur</h2>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="login">Identifiant : </label>
|
||||
<input type="text" name="login" id="login" value="{{login}}"/>
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="password">Mot de passe : </label>
|
||||
<input type="password" name="password" id="password"/>
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="password_confirm">Confirmer le mot de passe : </label>
|
||||
<input type="password" name="password_confirm" id="password_confirm"/>
|
||||
</p>
|
||||
|
||||
<h2>Abonnement</h2>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="provider">Fournisseur d'énergie : </label>
|
||||
<select name="provider" id="provider">
|
||||
% for provider in providers:
|
||||
<option value="{{ provider["name"]}}">{{ provider["name"] }}</option>
|
||||
% end
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="start_night_rate">Début des heures creuses : </label>
|
||||
<input type="time" name="start_night_rate" id="start_night_rate" value="{{ start_night_rate }}" placeholder="hh:mm"/>
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="end_night_rate">Fin des heures creuses : </label>
|
||||
<input type="time" name="end_night_rate" id="end_night_rate" value="{{ end_night_rate }}" placeholder="hh:mm"/>
|
||||
</p>
|
||||
|
||||
<h2>Sécurité</h2>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="base_address">Adresse de la base : </label>
|
||||
<input type="text" name="base_address" id="base_address" value="{{base_address}}"/>
|
||||
</p>
|
||||
<p class="form-help">
|
||||
Par exemple <code>0xE056D446D0LL</code>.
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="aes_key">Clé AES : </label>
|
||||
<input type="int" name="aes_key" id="aes_key" value="{{aes_key}}"/>
|
||||
</p>
|
||||
<p class="form-help">
|
||||
Par exemple <code>1-254-0-145-23-3-4-5-6-6-7-8-0-1-15-64</code>.
|
||||
</p>
|
||||
|
||||
|
||||
<p class="form-item">
|
||||
<input type="submit" value="Installer"/>
|
||||
</p>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
% include('_end.tpl')
|
32
CitizenWatt-Base/views/login.tpl
Normal file
@ -0,0 +1,32 @@
|
||||
% include('_begin.tpl', title='Accueil', page='home')
|
||||
|
||||
<main>
|
||||
<div class="menu">
|
||||
<h1><img alt="" src="{{ get_url('static', filename='img/login.svg') }}" />Connexion</h1>
|
||||
</div>
|
||||
<form action="" method="post">
|
||||
% if defined('err'):
|
||||
<div class="dialog-err">
|
||||
<h4>{{ err['title'] }}</h4>
|
||||
<p>
|
||||
{{ err['content'] }}
|
||||
</p>
|
||||
</div>
|
||||
% end
|
||||
<p class="form-item">
|
||||
<label for="login">Identifiant : </label>
|
||||
<input type="text" name="login" id="login" value="{{login}}" autofocus/>
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="password">Mot de passe : </label>
|
||||
<input type="password" name="password" id="password"/>
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<input type="submit" value="Connexion"/>
|
||||
</p>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
% include('_end.tpl')
|
111
CitizenWatt-Base/views/settings.tpl
Normal file
@ -0,0 +1,111 @@
|
||||
% include('_begin.tpl', title='Settings', page='settings')
|
||||
|
||||
<main>
|
||||
<div class="menu">
|
||||
<h1><img alt="" src="{{ get_url('static', filename='img/target.svg') }}" />Configuration</h1>
|
||||
</div>
|
||||
|
||||
<article id="user">
|
||||
<form method="post" action="/settings">
|
||||
<h2>Utilisateur</h2>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="password">Mot de passe : </label>
|
||||
<input type="password" name="password" id="password"/>
|
||||
</p>
|
||||
<p class="form-item">
|
||||
<label for="password_confirm">Mot de passe (confirmation) : </label>
|
||||
<input type="password" name="password_confirm" id="password_confirm"/>
|
||||
</p>
|
||||
<p class="form-help">
|
||||
Laisser vide pour ne pas modifier le mot de passe.
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Sauvegarder"/>
|
||||
</p>
|
||||
|
||||
|
||||
<h2>Abonnement</h2>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="provider">Fournisseur d'énergie : </label>
|
||||
<select name="provider" id="provider">
|
||||
% for provider in providers:
|
||||
<option value="{{ provider["name"] }}">{{ provider["name"] }}</option>
|
||||
% end
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="start_night_rate">Début des heures creuses : </label>
|
||||
<input type="time" name="start_night_rate" id="start_night_rate" value="{{ start_night_rate }}" placeholder="hh:mm"/>
|
||||
</p>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="end_night_rate">Fin des heures creuses : </label>
|
||||
<input type="time" name="end_night_rate" id="end_night_rate" value="{{ end_night_rate }}" placeholder="hh:mm"/>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Sauvegarder"/>
|
||||
</p>
|
||||
|
||||
|
||||
<h2>Sécurité</h2>
|
||||
|
||||
<p class="form-item">
|
||||
<label for="base_address">Adresse de la base : </label>
|
||||
<input type="text" name="base_address" id="base_address" value="{{base_address}}"/>
|
||||
</p>
|
||||
<p class="form-help">
|
||||
Par exemple <code>0xE056D446D0LL</code>.
|
||||
</p>
|
||||
<p class="form-item">
|
||||
<label for="aes_key">Clé AES : </label>
|
||||
<input type="int" name="aes_key" id="aes_key" value="{{aes_key}}"/>
|
||||
</p>
|
||||
<p class="form-help">
|
||||
Par exemple <code>1-254-0-145-23-3-4-5-6-6-7-8-0-1-15-64</code>.
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Sauvegarder"/>
|
||||
</p>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article id="sensors">
|
||||
<h2>Capteurs</h2>
|
||||
|
||||
% if len(sensors) > 0:
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Type</th>
|
||||
<th>Appairer</th>
|
||||
</tr>
|
||||
% for sensor in sensors:
|
||||
<tr>
|
||||
<td>{{ sensor["name"] }}</td>
|
||||
<td>{{ sensor["type"] }}</td>
|
||||
<td><a href="/reset_timer/{{ sensor["id"] }}">Appairer</a></td>
|
||||
</tr>
|
||||
% end
|
||||
</table>
|
||||
% else:
|
||||
<p>Aucun capteur disponible.</p>
|
||||
% end
|
||||
</article>
|
||||
|
||||
<article id="update">
|
||||
<h2>Mise à jour</h2>
|
||||
|
||||
<p>
|
||||
<a href="{{ get_url('update') }}">Mettre à jour le système</a>
|
||||
</p>
|
||||
<p class="form-help">
|
||||
La mise à jour est automatique. N'utilisez ce bouton que pour forcer la mise à jour.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
</main>
|
||||
|
||||
% include('_end.tpl')
|
987
CitizenWatt-Base/visu.py
Executable file
@ -0,0 +1,987 @@
|
||||
#!/usr/bin/env python3
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
from libcitizenwatt import cache
|
||||
from libcitizenwatt import database
|
||||
from libcitizenwatt import tools
|
||||
from bottle import abort, Bottle, SimpleTemplate, static_file
|
||||
from bottle import redirect, request, run
|
||||
from bottle.ext import sqlalchemy
|
||||
from bottlesession import PickleSession, authenticator
|
||||
from libcitizenwatt.config import Config
|
||||
from sqlalchemy import create_engine, desc
|
||||
from sqlalchemy.exc import OperationalError, ProgrammingError
|
||||
|
||||
|
||||
# =========
|
||||
# Functions
|
||||
# =========
|
||||
def get_rate_type(db):
|
||||
"""Returns "day" or "night" according to current time"""
|
||||
session = session_manager.get_session()
|
||||
user = db.query(database.User).filter_by(login=session.get("login")).first()
|
||||
now = datetime.datetime.now()
|
||||
now = 3600 * now.hour + 60 * now.minute
|
||||
if user is None:
|
||||
return None
|
||||
elif user.end_night_rate > user.start_night_rate:
|
||||
if now > user.start_night_rate and now < user.end_night_rate:
|
||||
return "night"
|
||||
else:
|
||||
return "day"
|
||||
else:
|
||||
if now > user.start_night_rate or now < user.end_night_rate:
|
||||
return "night"
|
||||
else:
|
||||
return "day"
|
||||
|
||||
|
||||
def update_providers(fetch, db):
|
||||
"""Updates the available providers. Simply returns them without updating if
|
||||
fetch is False.
|
||||
"""
|
||||
try:
|
||||
assert(fetch)
|
||||
providers = requests.get(config.get("url_energy_providers")).json()
|
||||
except (requests.ConnectionError, AssertionError):
|
||||
providers = db.query(database.Provider).all()
|
||||
if not providers:
|
||||
providers = []
|
||||
return tools.to_dict(providers)
|
||||
|
||||
old_current = db.query(database.Provider).filter_by(current=1).first()
|
||||
db.query(database.Provider).delete()
|
||||
|
||||
for provider in providers:
|
||||
type_id = (db.query(database.MeasureType)
|
||||
.filter_by(name=provider["type_name"])
|
||||
.first())
|
||||
if not type_id:
|
||||
type_db = database.MeasureType(name=provider["type_name"])
|
||||
db.add(type_db)
|
||||
db.flush()
|
||||
type_id = database.MeasureType(name=provider["type_name"]).first()
|
||||
|
||||
provider_db = database.Provider(name=provider["name"],
|
||||
day_constant_watt_euros=provider["day_constant_watt_euros"],
|
||||
day_slope_watt_euros=provider["day_slope_watt_euros"],
|
||||
night_constant_watt_euros=provider["night_constant_watt_euros"],
|
||||
night_slope_watt_euros=provider["night_slope_watt_euros"],
|
||||
type_id=type_id.id,
|
||||
current=(1 if old_current and old_current.name == provider["name"] else 0),
|
||||
threshold=int(provider["threshold"]))
|
||||
db.add(provider_db)
|
||||
return providers
|
||||
|
||||
|
||||
def api_auth(post, db):
|
||||
"""
|
||||
Handles login authentication for API.
|
||||
|
||||
Returns True if login is ok, False otherwise.
|
||||
"""
|
||||
login = post.get("login")
|
||||
user = db.query(database.User).filter_by(login=login).first()
|
||||
|
||||
password = (config.get("salt") +
|
||||
hashlib.sha256(post.get("password", "").encode('utf-8'))
|
||||
.hexdigest())
|
||||
if user and user.password == password:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# ===============
|
||||
# Initializations
|
||||
# ===============
|
||||
config = Config()
|
||||
database_url = (config.get("database_type") + "://" + config.get("username") +
|
||||
":" + config.get("password") + "@" + config.get("host") + "/" +
|
||||
config.get("database"))
|
||||
engine = create_engine(database_url, echo=config.get("debug"))
|
||||
|
||||
app = Bottle()
|
||||
plugin = sqlalchemy.Plugin(
|
||||
engine,
|
||||
database.Base.metadata,
|
||||
keyword='db',
|
||||
create=True,
|
||||
commit=True,
|
||||
use_kwargs=False
|
||||
)
|
||||
app.install(plugin)
|
||||
|
||||
session_manager = PickleSession()
|
||||
valid_user = authenticator(session_manager, login_url='/login')
|
||||
|
||||
|
||||
# ===
|
||||
# API
|
||||
# ===
|
||||
@app.route("/api/sensors",
|
||||
apply=valid_user())
|
||||
def api_sensors(db):
|
||||
"""Returns a list of all the available sensors.
|
||||
|
||||
If no sensors are found, returns null"""
|
||||
sensors = db.query(database.Sensor).all()
|
||||
if sensors:
|
||||
sensors = [{"id": sensor.id,
|
||||
"name": sensor.name,
|
||||
"type": sensor.type.name,
|
||||
"type_id": sensor.type_id} for sensor in sensors]
|
||||
else:
|
||||
sensors = None
|
||||
|
||||
return {"data": sensors}
|
||||
|
||||
|
||||
@app.route("/api/sensors",
|
||||
method="post")
|
||||
def api_sensors_post(db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_sensors(db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/sensors/<id:int>",
|
||||
apply=valid_user())
|
||||
def api_sensor(id, db):
|
||||
"""Returns the sensor with id <id>.
|
||||
|
||||
If no matching sensor is found, returns null"""
|
||||
sensor = db.query(database.Sensor).filter_by(id=id).first()
|
||||
if sensor:
|
||||
sensor = {"id": sensor.id,
|
||||
"name": sensor.name,
|
||||
"type": sensor.type.name,
|
||||
"type_id": sensor.type_id}
|
||||
else:
|
||||
sensor = None
|
||||
|
||||
return {"data": sensor}
|
||||
|
||||
|
||||
@app.route("/api/sensors/<id:int>",
|
||||
method="post")
|
||||
def api_sensor_post(id, db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_sensor(id, db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/types",
|
||||
apply=valid_user())
|
||||
def api_types(db):
|
||||
"""Returns a list of all the available measure types.
|
||||
|
||||
If no types are found, returns null"""
|
||||
types = db.query(database.MeasureType).all()
|
||||
if types:
|
||||
types = [{"id": mtype.id,
|
||||
"name": mtype.name} for mtype in types]
|
||||
else:
|
||||
types = None
|
||||
|
||||
return {"data": types}
|
||||
|
||||
|
||||
@app.route("/api/types",
|
||||
method="post")
|
||||
def api_types_post(db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_types(db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/time",
|
||||
apply=valid_user())
|
||||
def api_time(db):
|
||||
"""
|
||||
Returns current timestamp on the server side."""
|
||||
now = datetime.datetime.now()
|
||||
|
||||
return {"data": now.timestamp()}
|
||||
|
||||
|
||||
@app.route("/api/time",
|
||||
method="post")
|
||||
def api_time_post(db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_time(db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/watts/by_id/<id1:int>",
|
||||
apply=valid_user())
|
||||
def api_get_id(sensor, id1, db):
|
||||
"""
|
||||
Returns measure with id <id1> associated to sensor <sensor>, in watts.
|
||||
|
||||
If <id1> < 0, counts from the last measure, as in Python lists.
|
||||
|
||||
If no matching data is found, returns null.
|
||||
"""
|
||||
if id1 >= 0:
|
||||
data = (db.query(database.Measures)
|
||||
.filter_by(sensor_id=sensor, id=id1)
|
||||
.first())
|
||||
else:
|
||||
data = (db.query(database.Measures)
|
||||
.filter_by(sensor_id=sensor)
|
||||
.order_by(desc(database.Measures.timestamp))
|
||||
.slice(-id1, -id1)
|
||||
.first())
|
||||
|
||||
if not data:
|
||||
data = None
|
||||
else:
|
||||
data = tools.to_dict(data)
|
||||
|
||||
return {"data": data, "rate": get_rate_type(db)}
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/watts/by_id/<id1:int>",
|
||||
method="post")
|
||||
def api_get_id_post(sensor, id1, db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_get_id(sensor, id1, db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_id/<id1:int>/<id2:int>",
|
||||
apply=valid_user())
|
||||
def api_get_ids(sensor, watt_euros, id1, id2, db):
|
||||
"""
|
||||
Returns measures between ids <id1> and <id2> from sensor <sensor> in
|
||||
watts or euros.
|
||||
|
||||
If id1 and id2 are negative, counts from the end of the measures.
|
||||
|
||||
* If `watts_euros` is watts, returns the list of measures.
|
||||
* If `watt_euros` is kwatthours, returns the total energy for all the
|
||||
measures (dict).
|
||||
* If `watt_euros` is euros, returns the cost of all the measures (dict).
|
||||
|
||||
Returns measure in ASC order of timestamp.
|
||||
|
||||
Returns null if no measures were found.
|
||||
"""
|
||||
if (id2 - id1) > config.get("max_returned_values"):
|
||||
abort(403,
|
||||
"Too many values to return. " +
|
||||
"(Maximum is set to %d)" % config.get("max_returned_values"))
|
||||
elif id2 < id1 or id2 * id1 < 0:
|
||||
abort(400, "Invalid parameters")
|
||||
else:
|
||||
data = cache.do_cache_ids(sensor, watt_euros, id1, id2, db)
|
||||
|
||||
return {"data": data, "rate": get_rate_type(db)}
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_id/<id1:int>/<id2:int>",
|
||||
method="post")
|
||||
def api_get_ids_post(sensor, watt_euros, id1, id2, db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_get_ids(sensor, watt_euros, id1, id2, db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_id/<id1:int>/<id2:int>/<step:int>",
|
||||
apply=valid_user())
|
||||
def api_get_ids_step(sensor, watt_euros, id1, id2, step, db,
|
||||
timestep=config.get("default_timestep")):
|
||||
"""
|
||||
Returns all the measures of sensor `sensor` between ids `id1`
|
||||
and `id2`, grouped by step, as a list of the number of steps element.
|
||||
Each item is null if no matching measures are found.
|
||||
|
||||
* If `watts_euros` is watts, returns the mean power for each group.
|
||||
* If `watt_euros` is kwatthours, returns the total energy for each group.
|
||||
* If `watt_euros` is euros, returns the cost of each group.
|
||||
|
||||
Returns measure in ASC order of timestamp.
|
||||
|
||||
Returns null if no measures were found.
|
||||
"""
|
||||
if id1 * id2 < 0 or id2 <= id1 or step <= 0:
|
||||
abort(400, "Invalid parameters")
|
||||
elif (id2 - id1) > config.get("max_returned_values"):
|
||||
abort(403,
|
||||
"Too many values to return. " +
|
||||
"(Maximum is set to %d)" % config.get("max_returned_values"))
|
||||
|
||||
try:
|
||||
data = cache.do_cache_group_id(sensor,
|
||||
watt_euros,
|
||||
id1,
|
||||
id2,
|
||||
step,
|
||||
db,
|
||||
timestep)
|
||||
except ValueError:
|
||||
abort(400, "Wrong parameters id1 and id2.")
|
||||
|
||||
return {"data": data, "rate": get_rate_type(db)}
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_id/<id1:int>/<id2:int>/<step:int>",
|
||||
method="post")
|
||||
def api_get_ids_step_post(sensor, watt_euros, id1, id2, step, db,
|
||||
timestep=config.get("default_timestep")):
|
||||
if api_auth(request.POST, db):
|
||||
return api_get_ids_step(sensor, watt_euros, id1, id2, step, db, timestep)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/watts/by_time/<time1:float>",
|
||||
apply=valid_user())
|
||||
def api_get_time(sensor, time1, db):
|
||||
"""
|
||||
Returns measure at timestamp <time1> for sensor <sensor>, in watts.
|
||||
|
||||
Returns null if no measure is found.
|
||||
"""
|
||||
if time1 < 0:
|
||||
abort(400, "Invalid timestamp.")
|
||||
|
||||
data = (db.query(database.Measures)
|
||||
.filter_by(sensor_id=sensor,
|
||||
timestamp=time1)
|
||||
.first())
|
||||
if not data:
|
||||
data = None
|
||||
else:
|
||||
data = tools.to_dict(data)
|
||||
|
||||
return {"data": data, "rate": get_rate_type(db)}
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/watts/by_time/<time1:float>",
|
||||
method="post")
|
||||
def api_get_time_post(sensor, time1, db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_get_time(sensor, time1, db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_time/<time1:float>/<time2:float>",
|
||||
apply=valid_user())
|
||||
def api_get_times(sensor, watt_euros, time1, time2, db):
|
||||
"""
|
||||
Returns measures between timestamps <time1> and <time2>
|
||||
from sensor <sensor> in watts or euros.
|
||||
|
||||
* If `watts_euros` is watts, returns the list of measures.
|
||||
* If `watt_euros` is kwatthours, returns the total energy for all the
|
||||
measures (dict).
|
||||
* If `watt_euros` is euros, returns the cost of all the measures (dict).
|
||||
|
||||
Returns measure in ASC order of timestamp.
|
||||
|
||||
Returns null if no matching measures are found.
|
||||
"""
|
||||
if time1 < 0 or time2 < time1:
|
||||
abort(400, "Invalid timestamps.")
|
||||
|
||||
data = cache.do_cache_times(sensor, watt_euros, time1, time2, db)
|
||||
|
||||
return {"data": data, "rate": get_rate_type(db)}
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_time/<time1:float>/<time2:float>",
|
||||
method="post")
|
||||
def api_get_times_post(sensor, watt_euros, time1, time2, db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_get_times(sensor, watt_euros, time1, time2, db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_time/<time1:float>/<time2:float>/<step:float>",
|
||||
apply=valid_user())
|
||||
def api_get_times_step(sensor, watt_euros, time1, time2, step, db):
|
||||
"""
|
||||
Returns all the measures of sensor `sensor` between timestamps `time1`
|
||||
and `time2`, grouped by step, as a list of the number of steps element.
|
||||
Each item is null if no matching measures are found.
|
||||
|
||||
* If `watts_euros` is watts, returns the mean power for each group.
|
||||
* If `watt_euros` is kwatthours, returns the total energy for each group.
|
||||
* If `watt_euros` is euros, returns the cost of each group.
|
||||
|
||||
Returns measure in ASC order of timestamp.
|
||||
"""
|
||||
if time1 < 0 or time2 < 0 or step <= 0:
|
||||
abort(400, "Invalid parameters")
|
||||
|
||||
data = cache.do_cache_group_timestamp(sensor,
|
||||
watt_euros,
|
||||
time1,
|
||||
time2,
|
||||
step,
|
||||
db)
|
||||
|
||||
return {"data": data, "rate": get_rate_type(db)}
|
||||
|
||||
|
||||
@app.route("/api/<sensor:int>/get/<watt_euros:re:watts|kwatthours|euros>/by_time/<time1:float>/<time2:float>/<step:float>",
|
||||
method="post")
|
||||
def api_get_times_step_post(sensor, watt_euros, time1, time2, step, db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_get_times_step(sensor, watt_euros, time1, time2, step, db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/energy_providers",
|
||||
apply=valid_user())
|
||||
def api_energy_providers(db):
|
||||
"""Returns all the available energy providers or null if none found."""
|
||||
providers = db.query(database.Provider).all()
|
||||
if not providers:
|
||||
providers = None
|
||||
else:
|
||||
providers = tools.to_dict(providers)
|
||||
for provider in providers:
|
||||
if provider["day_slope_watt_euros"] != provider["night_slope_watt_euros"]:
|
||||
session = session_manager.get_session()
|
||||
user = db.query(database.User).filter_by(login=session["login"]).first()
|
||||
start_night_rate = ("%02d" % (user.start_night_rate // 3600) + ":" +
|
||||
"%02d" % ((user.start_night_rate % 3600) // 60))
|
||||
end_night_rate = ("%02d" % (user.end_night_rate // 3600) + ":" +
|
||||
"%02d" % ((user.end_night_rate % 3600) // 60))
|
||||
provider["start_night_rate"] = start_night_rate
|
||||
provider["end_night_rate"] = end_night_rate
|
||||
|
||||
return {"data": providers}
|
||||
|
||||
|
||||
@app.route("/api/energy_providers",
|
||||
method="post")
|
||||
def api_energy_providers_post(db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_energy_providers(db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/energy_providers/<id:re:current|\d*>",
|
||||
apply=valid_user())
|
||||
def api_specific_energy_providers(id, db):
|
||||
"""
|
||||
Returns the current energy provider,
|
||||
or the specified energy provider.
|
||||
"""
|
||||
if id == "current":
|
||||
provider = (db.query(database.Provider)
|
||||
.filter_by(current=1)
|
||||
.first())
|
||||
else:
|
||||
try:
|
||||
id = int(id)
|
||||
except ValueError:
|
||||
abort(400, "Invalid parameter.")
|
||||
|
||||
provider = (db.query(database.Provider)
|
||||
.filter_by(id=id)
|
||||
.first())
|
||||
|
||||
if not provider:
|
||||
provider = None
|
||||
else:
|
||||
provider = tools.to_dict(provider)
|
||||
if provider["day_slope_watt_euros"] != provider["night_slope_watt_euros"]:
|
||||
session = session_manager.get_session()
|
||||
user = db.query(database.User).filter_by(login=session["login"]).first()
|
||||
start_night_rate = ("%02d" % (user.start_night_rate // 3600) + ":" +
|
||||
"%02d" % ((user.start_night_rate % 3600) // 60))
|
||||
end_night_rate = ("%02d" % (user.end_night_rate // 3600) + ":" +
|
||||
"%02d" % ((user.end_night_rate % 3600) // 60))
|
||||
provider["start_night_rate"] = start_night_rate
|
||||
provider["end_night_rate"] = end_night_rate
|
||||
|
||||
return {"data": provider}
|
||||
|
||||
|
||||
@app.route("/api/energy_providers/<id:re:current|\d*>",
|
||||
method="post")
|
||||
def api_specific_energy_providers_post(id, db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_specific_energy_providers(id, db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
@app.route("/api/<energy_provider:re:current|\d>/watt_to_euros/<tariff:re:night|day>/<consumption:float>",
|
||||
apply=valid_user())
|
||||
def api_watt_euros(energy_provider, tariff, consumption, db):
|
||||
"""
|
||||
Returns the cost in € associated with a certain consumption, in kWh.
|
||||
|
||||
One should specify the tariff (night or day) and the id of the
|
||||
energy_provider.
|
||||
|
||||
Returns null if no valid result to return.
|
||||
"""
|
||||
# Consumption should be in kWh !!!
|
||||
|
||||
if energy_provider == "current":
|
||||
energy_provider = 0
|
||||
|
||||
try:
|
||||
int(energy_provider)
|
||||
except ValueError:
|
||||
abort(400, "Wrong parameter energy_provider.")
|
||||
|
||||
data = tools.watt_euros(energy_provider, tariff, consumption, db)
|
||||
return {"data": data}
|
||||
|
||||
|
||||
@app.route("/api/<energy_provider:re:current|\d>/watt_to_euros/<tariff:re:night|day>/<consumption:float>",
|
||||
method="post")
|
||||
def api_watt_euros_post(energy_provider, tariff, consumption, db):
|
||||
if api_auth(request.POST, db):
|
||||
return api_watt_euros(energy_provider, tariff, consumption, db)
|
||||
else:
|
||||
abort(403, "Access forbidden")
|
||||
|
||||
|
||||
# ======
|
||||
# Routes
|
||||
# ======
|
||||
@app.route("/static/<filename:path>",
|
||||
name="static")
|
||||
def static(filename):
|
||||
"""Routes static files"""
|
||||
return static_file(filename, root="static")
|
||||
|
||||
|
||||
@app.route('/',
|
||||
name="index",
|
||||
template="index",
|
||||
apply=valid_user())
|
||||
def index():
|
||||
"""Index view"""
|
||||
return {}
|
||||
|
||||
|
||||
@app.route("/conso",
|
||||
name="conso",
|
||||
template="conso",
|
||||
apply=valid_user())
|
||||
def conso(db):
|
||||
"""Conso view"""
|
||||
provider = db.query(database.Provider).filter_by(current=1).first()
|
||||
return {"provider": provider.name}
|
||||
|
||||
|
||||
@app.route("/reset_timer/<sensor:int>", apply=valid_user())
|
||||
def reset_timer(sensor, db):
|
||||
db.query(database.Sensor).filter_by(id=sensor).update({"last_timer": 0})
|
||||
redirect("/settings")
|
||||
|
||||
|
||||
@app.route("/settings",
|
||||
name="settings",
|
||||
template="settings",
|
||||
apply=valid_user())
|
||||
def settings(db):
|
||||
"""Settings view"""
|
||||
sensors = db.query(database.Sensor).all()
|
||||
if sensors:
|
||||
sensors = [{"id": sensor.id,
|
||||
"name": sensor.name,
|
||||
"type": sensor.type.name,
|
||||
"type_id": sensor.type_id,
|
||||
"aes_key": sensor.aes_key,
|
||||
"base_address": sensor.base_address}
|
||||
for sensor in sensors]
|
||||
else:
|
||||
sensors = []
|
||||
|
||||
sensor_cw = [sensor for sensor in sensors if sensor["name"] ==
|
||||
"CitizenWatt"][0]
|
||||
|
||||
providers = update_providers(True, db)
|
||||
|
||||
session = session_manager.get_session()
|
||||
user = db.query(database.User).filter_by(login=session["login"]).first()
|
||||
start_night_rate = ("%02d" % (user.start_night_rate // 3600) + ":" +
|
||||
"%02d" % ((user.start_night_rate % 3600) // 60))
|
||||
end_night_rate = ("%02d" % (user.end_night_rate // 3600) + ":" +
|
||||
"%02d" % ((user.end_night_rate % 3600) // 60))
|
||||
|
||||
return {"sensors": sensors,
|
||||
"providers": providers,
|
||||
"start_night_rate": start_night_rate,
|
||||
"end_night_rate": end_night_rate,
|
||||
"base_address": sensor_cw["base_address"],
|
||||
"aes_key": '-'.join([str(i) for i in
|
||||
json.loads(sensor_cw["aes_key"])])}
|
||||
|
||||
|
||||
@app.route("/settings",
|
||||
name="settings",
|
||||
apply=valid_user(),
|
||||
method="post")
|
||||
def settings_post(db):
|
||||
"""Settings view with POST data"""
|
||||
error = None
|
||||
|
||||
password = request.forms.get("password").strip()
|
||||
password_confirm = request.forms.get("password_confirm")
|
||||
|
||||
if password:
|
||||
if password == password_confirm:
|
||||
password = (config.get("salt") +
|
||||
hashlib.sha256(password.encode('utf-8')).hexdigest())
|
||||
session = session_manager.get_session()
|
||||
(db.query(database.User)
|
||||
.filter_by(login=session["login"])
|
||||
.update({"password": password},
|
||||
synchronize_session=False))
|
||||
else:
|
||||
error = {"title": "Les mots de passe ne sont pas identiques.",
|
||||
"content": ("Les deux mots de passe doient " +
|
||||
"être identiques.")}
|
||||
settings_json = settings(db)
|
||||
settings_json.update({"err": error})
|
||||
return settings_json
|
||||
|
||||
provider = request.forms.get("provider")
|
||||
provider = (db.query(database.Provider)
|
||||
.filter_by(name=provider)
|
||||
.update({"current": 1}))
|
||||
|
||||
raw_start_night_rate = request.forms.get("start_night_rate")
|
||||
raw_end_night_rate = request.forms.get("end_night_rate")
|
||||
|
||||
raw_base_address = request.forms.get("base_address")
|
||||
raw_aes_key = request.forms.get("aes_key")
|
||||
|
||||
try:
|
||||
base_address_int = int(raw_base_address.strip("L"), 16)
|
||||
base_address = str(hex(base_address_int)).upper() + "LL"
|
||||
except ValueError:
|
||||
error = {"title": "Format invalide",
|
||||
"content": ("L'adresse de la base entrée est invalide.")}
|
||||
settings_json = settings(db)
|
||||
settings_json.update({"err": error})
|
||||
return settings_json
|
||||
|
||||
sensor = db.query(database.Sensor).filter_by(name="CitizenWatt").first()
|
||||
if base_address != sensor.base_address:
|
||||
tools.update_base_address(base_address_int)
|
||||
|
||||
try:
|
||||
aes_key = [int(i.strip()) for i in raw_aes_key.split("-")]
|
||||
if len(aes_key) != 16:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
error = {"title": "Format invalide",
|
||||
"content": ("La clé AES doit être constituée de 16 " +
|
||||
"chiffres entre 0 et 255, séparés " +
|
||||
"par des tirets.")}
|
||||
settings_json = settings(db)
|
||||
settings_json.update({"err": error})
|
||||
return settings_json
|
||||
(db.query(database.Sensor)
|
||||
.filter_by(name="CitizenWatt")
|
||||
.update({"base_address": base_address, "aes_key": json.dumps(aes_key)}))
|
||||
db.commit()
|
||||
|
||||
try:
|
||||
start_night_rate = raw_start_night_rate.split(":")
|
||||
assert(len(start_night_rate) == 2)
|
||||
start_night_rate = [int(i) for i in start_night_rate]
|
||||
assert(start_night_rate[0] >= 0 and start_night_rate[0] <= 23)
|
||||
assert(start_night_rate[1] >= 0 and start_night_rate[1] <= 59)
|
||||
start_night_rate = 3600 * start_night_rate[0] + 60*start_night_rate[1]
|
||||
except (AssertionError, ValueError):
|
||||
error = {"title": "Format invalide",
|
||||
"content": ("La date de début d'heures " +
|
||||
"creuses doit être au format hh:mm.")}
|
||||
settings_json = settings(db)
|
||||
settings_json.update({"err": error})
|
||||
return settings_json
|
||||
try:
|
||||
end_night_rate = raw_end_night_rate.split(":")
|
||||
assert(len(end_night_rate) == 2)
|
||||
end_night_rate = [int(i) for i in end_night_rate]
|
||||
assert(end_night_rate[0] >= 0 and end_night_rate[0] <= 23)
|
||||
assert(end_night_rate[1] >= 0 and end_night_rate[1] <= 59)
|
||||
end_night_rate = 3600 * end_night_rate[0] + 60*end_night_rate[1]
|
||||
except (AssertionError, ValueError):
|
||||
error = {"title": "Format invalide",
|
||||
"content": ("La date de fin d'heures " +
|
||||
"creuses doit être au format hh:mm.")}
|
||||
settings_json = settings(db)
|
||||
settings_json.update({"err": error})
|
||||
return settings_json
|
||||
|
||||
session = session_manager.get_session()
|
||||
(db.query(database.User)
|
||||
.filter_by(login=session["login"])
|
||||
.update({"start_night_rate": start_night_rate,
|
||||
"end_night_rate": end_night_rate}))
|
||||
|
||||
redirect("/settings")
|
||||
|
||||
|
||||
@app.route("/update", name="update")
|
||||
def update():
|
||||
"""Handles updating"""
|
||||
subprocess.Popen([os.path.dirname(os.path.realpath(__file__)) +
|
||||
"/updater.sh"])
|
||||
redirect("/settings")
|
||||
|
||||
|
||||
@app.route("/community",
|
||||
name="community",
|
||||
template="community")
|
||||
def store():
|
||||
"""Community view"""
|
||||
return {}
|
||||
|
||||
|
||||
@app.route("/help",
|
||||
name="help",
|
||||
template="help")
|
||||
def help():
|
||||
"""Help view"""
|
||||
return {}
|
||||
|
||||
|
||||
@app.route("/login",
|
||||
name="login",
|
||||
template="login")
|
||||
def login(db):
|
||||
"""Login view"""
|
||||
try:
|
||||
if not db.query(database.User).all():
|
||||
redirect("/install")
|
||||
except ProgrammingError:
|
||||
redirect("/install")
|
||||
session = session_manager.get_session()
|
||||
if session['valid'] is True:
|
||||
redirect('/')
|
||||
else:
|
||||
return {"login": ''}
|
||||
|
||||
|
||||
@app.route("/login",
|
||||
name="login",
|
||||
template="login",
|
||||
method="post")
|
||||
def login_post(db):
|
||||
"""Login view with POST data"""
|
||||
login = request.forms.get("login")
|
||||
user = db.query(database.User).filter_by(login=login).first()
|
||||
session = session_manager.get_session()
|
||||
session['valid'] = False
|
||||
session_manager.save(session)
|
||||
|
||||
password = (config.get("salt") +
|
||||
hashlib.sha256(request.forms.get("password")
|
||||
.encode('utf-8')).hexdigest())
|
||||
if user and user.password == password:
|
||||
session['valid'] = True
|
||||
session['login'] = login
|
||||
session['is_admin'] = user.is_admin
|
||||
session_manager.save(session)
|
||||
redirect('/')
|
||||
else:
|
||||
return {
|
||||
"login": login,
|
||||
"err": {
|
||||
"title": "Identifiants incorrects.",
|
||||
"content": ("Aucun utilisateur n'est enregistré à ce nom."
|
||||
if user else "Mot de passe erroné.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@app.route("/logout",
|
||||
name="logout")
|
||||
def logout():
|
||||
"""Logout"""
|
||||
session = session_manager.get_session()
|
||||
session['valid'] = False
|
||||
del(session['login'])
|
||||
del(session['is_admin'])
|
||||
session_manager.save(session)
|
||||
redirect('/')
|
||||
|
||||
|
||||
@app.route("/install",
|
||||
name="install",
|
||||
template="install")
|
||||
def install(db):
|
||||
"""Install view (first run)"""
|
||||
if db.query(database.User).all():
|
||||
redirect('/')
|
||||
|
||||
db.query(database.MeasureType).delete()
|
||||
db.query(database.Provider).delete()
|
||||
db.query(database.Sensor).delete()
|
||||
|
||||
electricity_type = database.MeasureType(name="Électricité")
|
||||
db.add(electricity_type)
|
||||
db.flush()
|
||||
|
||||
providers = update_providers(True, db)
|
||||
|
||||
sensor = database.Sensor(name="CitizenWatt",
|
||||
type_id=electricity_type.id,
|
||||
last_timer=0)
|
||||
db.add(sensor)
|
||||
|
||||
return {"login": '',
|
||||
"providers": providers,
|
||||
"start_night_rate": '',
|
||||
"end_night_rate": '',
|
||||
"base_address": '',
|
||||
"aes_key": ''}
|
||||
|
||||
|
||||
@app.route("/install",
|
||||
name="install",
|
||||
template="install",
|
||||
method="post")
|
||||
def install_post(db):
|
||||
"""Install view with POST data"""
|
||||
error = None
|
||||
try:
|
||||
if db.query(database.User).all():
|
||||
redirect('/')
|
||||
except OperationalError:
|
||||
error = {"title": "Connexion à la base de données impossible",
|
||||
"content": ("Impossible d'établir une connexion avec la " +
|
||||
"base de données.")}
|
||||
install_json = install(db)
|
||||
install_json.update({"err": error})
|
||||
return install_json
|
||||
|
||||
login = request.forms.get("login").strip()
|
||||
password = request.forms.get("password")
|
||||
password_confirm = request.forms.get("password_confirm")
|
||||
provider = request.forms.get("provider")
|
||||
raw_start_night_rate = request.forms.get("start_night_rate")
|
||||
raw_end_night_rate = request.forms.get("end_night_rate")
|
||||
raw_base_address = request.forms.get("base_address")
|
||||
raw_aes_key = request.forms.get("aes_key")
|
||||
|
||||
ret = {"login": login,
|
||||
"providers": update_providers(False, db),
|
||||
"start_night_rate": raw_start_night_rate,
|
||||
"end_night_rate": raw_end_night_rate,
|
||||
"base_address": raw_base_address,
|
||||
"aes_key": raw_aes_key}
|
||||
|
||||
try:
|
||||
base_address_int = int(raw_base_address.strip("L"), 16)
|
||||
base_address = str(hex(base_address_int)).upper() + "LL"
|
||||
except ValueError:
|
||||
error = {"title": "Format invalide",
|
||||
"content": ("L'adresse de la base entrée est invalide.")}
|
||||
ret.update({"err": error})
|
||||
return ret
|
||||
tools.update_base_address(base_address_int)
|
||||
try:
|
||||
aes_key = [int(i.strip()) for i in raw_aes_key.split("-")]
|
||||
if len(aes_key) != 16:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
error = {"title": "Format invalide",
|
||||
"content": ("La clé AES doit être constituée de 16 " +
|
||||
"chiffres entre 0 et 255, séparés " +
|
||||
"par des tirets.")}
|
||||
ret.update({"err": error})
|
||||
return ret
|
||||
(db.query(database.Sensor)
|
||||
.filter_by(name="CitizenWatt")
|
||||
.update({"base_address": base_address, "aes_key": json.dumps(aes_key)}))
|
||||
db.commit()
|
||||
|
||||
try:
|
||||
start_night_rate = raw_start_night_rate.split(":")
|
||||
assert(len(start_night_rate) == 2)
|
||||
start_night_rate = [int(i) for i in start_night_rate]
|
||||
assert(start_night_rate[0] >= 0 and start_night_rate[0] <= 23)
|
||||
assert(start_night_rate[1] >= 0 and start_night_rate[1] <= 59)
|
||||
start_night_rate = 3600 * start_night_rate[0] + 60*start_night_rate[1]
|
||||
except (AssertionError, ValueError):
|
||||
error = {"title": "Format invalide",
|
||||
"content": ("La date de début d'heures creuses " +
|
||||
"doit être au format hh:mm.")}
|
||||
ret.update({"err": error})
|
||||
return ret
|
||||
|
||||
try:
|
||||
end_night_rate = raw_end_night_rate.split(":")
|
||||
assert(len(end_night_rate) == 2)
|
||||
end_night_rate = [int(i) for i in end_night_rate]
|
||||
assert(end_night_rate[0] >= 0 and end_night_rate[0] <= 23)
|
||||
assert(end_night_rate[1] >= 0 and end_night_rate[1] <= 59)
|
||||
end_night_rate = 3600 * end_night_rate[0] + 60*end_night_rate[1]
|
||||
except (AssertionError, ValueError):
|
||||
error = {"title": "Format invalide",
|
||||
"content": ("La date de fin d'heures creuses " +
|
||||
"doit être au format hh:mm.")}
|
||||
ret.update({"err": error})
|
||||
return ret
|
||||
|
||||
if login and password and password == password_confirm:
|
||||
password = (config.get("salt") +
|
||||
hashlib.sha256(password.encode('utf-8')).hexdigest())
|
||||
admin = database.User(login=login,
|
||||
password=password,
|
||||
is_admin=1,
|
||||
start_night_rate=start_night_rate,
|
||||
end_night_rate=end_night_rate)
|
||||
db.add(admin)
|
||||
|
||||
provider = (db.query(database.Provider)
|
||||
.filter_by(name=provider)
|
||||
.update({"current": 1}))
|
||||
|
||||
session = session_manager.get_session()
|
||||
session['valid'] = True
|
||||
session['login'] = login
|
||||
session['is_admin'] = 1
|
||||
session_manager.save(session)
|
||||
|
||||
redirect('/')
|
||||
else:
|
||||
ret.update({"err": {"title": "Champs obligatoires manquants",
|
||||
"content": ("Vous devez renseigner tous " +
|
||||
"les champs obligatoires.")}})
|
||||
return ret
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# ===
|
||||
# App
|
||||
# ===
|
||||
SimpleTemplate.defaults["get_url"] = app.get_url
|
||||
SimpleTemplate.defaults["API_URL"] = app.get_url("index")
|
||||
SimpleTemplate.defaults["valid_session"] = lambda: session_manager.get_session()['valid']
|
||||
run(app, host="0.0.0.0", port=config.get("port"), debug=config.get("debug"),
|
||||
reloader=config.get("autoreload"), server="cherrypy")
|
BIN
CitizenWatt-PiBoard/2017-02-15-201954.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
CitizenWatt-PiBoard/2017-02-15-202006.png
Normal file
After Width: | Height: | Size: 108 KiB |
339
CitizenWatt-PiBoard/LICENSE
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
1858
CitizenWatt-PiBoard/PiBoard.brd
Normal file
4477
CitizenWatt-PiBoard/PiBoard.sch
Normal file
4
CitizenWatt-PiBoard/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
CitizenWatt-PiBoard
|
||||
===================
|
||||
|
||||
The small piece of PCB to connect the NRF24L01+ to the Pi
|
40
CitizenWatt-sensor/Hardware/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Created by http://www.gitignore.io
|
||||
|
||||
### Eagle ###
|
||||
# Ignore list for Eagle, a PCB layout tool
|
||||
|
||||
# Backup files
|
||||
*.s#?
|
||||
*.b#?
|
||||
*.l#?
|
||||
|
||||
# Eagle project file
|
||||
# It contains a serial number and references to the file structure
|
||||
# on your computer.
|
||||
# comment the following line if you want to have your project file included.
|
||||
eagle.epf
|
||||
|
||||
# CAM files
|
||||
*.$$$
|
||||
*.cmp
|
||||
*.ly2
|
||||
*.l15
|
||||
*.sol
|
||||
*.plc
|
||||
*.stc
|
||||
*.sts
|
||||
*.crc
|
||||
*.crs
|
||||
|
||||
*.dri
|
||||
*.drl
|
||||
*.gpi
|
||||
*.pls
|
||||
|
||||
*.drd
|
||||
*.drd.*
|
||||
|
||||
*.info
|
||||
|
||||
*.eps
|
||||
|
7225
CitizenWatt-sensor/Hardware/CitizenBoard.brd
Normal file
16977
CitizenWatt-sensor/Hardware/CitizenBoard.sch
Normal file
159
CitizenWatt-sensor/Hardware/silkscreen3bold_standalone.svg
Normal file
After Width: | Height: | Size: 64 KiB |
339
CitizenWatt-sensor/LICENSE
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
8
CitizenWatt-sensor/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
CitizenWatt-sensor
|
||||
==================
|
||||
Hardware and code running on the sensor for the CitizenWatt project.
|
||||
|
||||
For the sensor, install some libs first :
|
||||
- RF24: https://github.com/stanleyseow/RF24/tree/2a1a4e6e27056844a3bc419d65b8a2d4e0f1770e
|
||||
- EmonLib: https://github.com/openenergymonitor/EmonLib
|
||||
- AESLib: https://github.com/DavyLandman/AESLib
|
30
CitizenWatt-sensor/Software/sensor/printf.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright (C) 2011 J. Coliz <maniacbug@ymail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __PRINTF_H__
|
||||
#define __PRINTF_H__
|
||||
|
||||
#ifdef ARDUINO
|
||||
|
||||
int serial_putc( char c, FILE * )
|
||||
{
|
||||
Serial.write( c );
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void printf_begin(void)
|
||||
{
|
||||
fdevopen( &serial_putc, 0 );
|
||||
}
|
||||
|
||||
#else
|
||||
#error This example is only for use on Arduino.
|
||||
#endif // ARDUINO
|
||||
|
||||
#endif // __PRINTF_H__
|
316
CitizenWatt-sensor/Software/sensor/sensor.ino
Normal file
@ -0,0 +1,316 @@
|
||||
///////////////////
|
||||
// Includes //
|
||||
///////////////////
|
||||
|
||||
#include <EEPROM.h>
|
||||
#include <SPI.h>
|
||||
#include <avr/sleep.h>
|
||||
#include <avr/power.h>
|
||||
#include <nRF24L01.h>
|
||||
#include <RF24.h> // https://github.com/stanleyseow/RF24/tree/2a1a4e6e27056844a3bc419d65b8a2d4e0f1770e
|
||||
#include "printf.h"
|
||||
#include <EmonLib.h> // https://github.com/openenergymonitor/EmonLib
|
||||
#include <AESLib.h> // https://github.com/DavyLandman/AESLib
|
||||
|
||||
///////////////////////
|
||||
// CONFIGURATION //
|
||||
///////////////////////
|
||||
|
||||
#define DEBUG 1
|
||||
#define SERIAL_BAUDRATE 57600
|
||||
|
||||
// PreAmplifier level for the nRF
|
||||
// Lower this to reduce power consumption. This will reduce range.
|
||||
rf24_pa_dbm_e NRF_PA_LEVEL = RF24_PA_LOW;
|
||||
|
||||
// Radio pipe addresses for the 2 nodes to communicate.
|
||||
uint64_t pipes[2] = { 0xE056D446D0LL, 0xF0F0F0F0D2LL };
|
||||
|
||||
// AES
|
||||
uint8_t key[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
|
||||
|
||||
// Calibration factor for the intensity
|
||||
double ICAL = 85.0;
|
||||
|
||||
// =============================================================
|
||||
|
||||
// Speed for the nrf module
|
||||
// RF24_250KBPS / RF24_1MBPS / RF24_2MBPS
|
||||
// Reduce it to improve reliability
|
||||
rf24_datarate_e NRF_SPEED = RF24_250KBPS;
|
||||
|
||||
// Channel for the nrf module
|
||||
// 76 is default safe channel in RF24
|
||||
int NRF_CHANNEL = 0x4c;
|
||||
|
||||
// Set this to 0 to enable voltage measurement.
|
||||
// Else, set this to your mean voltage.
|
||||
double VOLTAGE = 240.0;
|
||||
|
||||
// Number of samples over which the mean must be done for the current measurement
|
||||
int NUMBER_SAMPLES_I = 1480;
|
||||
|
||||
// Pin for current measurement
|
||||
const int CURRENT_PIN = A0;
|
||||
|
||||
// Pin for voltage measurement
|
||||
const int VOLTAGE_PIN = 0;
|
||||
|
||||
// Pin for the green LED
|
||||
const int GREEN_LED_PIN = 2;
|
||||
|
||||
// Pin for the red LED
|
||||
const int RED_LED_PIN = 3;
|
||||
|
||||
// Energy Monitor object
|
||||
EnergyMonitor emon1;
|
||||
|
||||
///////////////////////
|
||||
// Declarations //
|
||||
///////////////////////
|
||||
|
||||
#define TIMEOUT 250 // timeout in ms
|
||||
|
||||
// Struct to send RF data
|
||||
typedef struct {
|
||||
int power;
|
||||
int voltage;
|
||||
int battery;
|
||||
unsigned long timer;
|
||||
long padding4;
|
||||
int padding2;
|
||||
} PayloadTX;
|
||||
|
||||
// Next measurement to be sent
|
||||
PayloadTX nrf = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
////////////////////////////////
|
||||
// Hardware configuration //
|
||||
////////////////////////////////
|
||||
|
||||
// Set up nRF24L01 radio on SPI bus plus pins 9 & 10
|
||||
RF24 radio(9,10);
|
||||
|
||||
//////////////////////////////
|
||||
// Sleep configuration //
|
||||
//////////////////////////////
|
||||
|
||||
typedef enum { wdt_16ms = 0, wdt_32ms, wdt_64ms, wdt_128ms, wdt_250ms, wdt_500ms, wdt_1s, wdt_2s, wdt_4s, wdt_8s } wdt_prescalar_e;
|
||||
|
||||
void setup_watchdog(uint8_t prescalar);
|
||||
void do_sleep(void);
|
||||
|
||||
//////////////////////////
|
||||
// Setup operation //
|
||||
//////////////////////////
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
pinMode(RED_LED_PIN, OUTPUT);
|
||||
pinMode(GREEN_LED_PIN, OUTPUT);
|
||||
digitalWrite(RED_LED_PIN, HIGH);
|
||||
digitalWrite(GREEN_LED_PIN, HIGH);
|
||||
|
||||
|
||||
Serial.begin(SERIAL_BAUDRATE);
|
||||
Serial.println(F("/!\\ STARTING CitizenWatt Sensor"));
|
||||
Serial.println(F("//////////////////////////////"));
|
||||
Serial.println(F("// CitizenWatt sensor //"));
|
||||
Serial.println(F("// citizenwatt.paris //"));
|
||||
Serial.println(F("////////////////////////////// \n"));
|
||||
|
||||
if( DEBUG )
|
||||
printf_begin();
|
||||
|
||||
//
|
||||
// Prepare sleep parameters
|
||||
//
|
||||
|
||||
setup_watchdog(wdt_8s); // /!\ 8s sleeping
|
||||
|
||||
//
|
||||
// Setup and configure rf radio
|
||||
//
|
||||
|
||||
// Initialize nRF
|
||||
radio.begin();
|
||||
radio.setChannel(NRF_CHANNEL);
|
||||
|
||||
// Max number of retries and max delay between them
|
||||
radio.setRetries(15, 15);
|
||||
|
||||
// Reduce payload size to improve reliability
|
||||
radio.setPayloadSize(16);
|
||||
|
||||
// Set the datarate
|
||||
radio.setDataRate(NRF_SPEED);
|
||||
|
||||
// Use the largest CRC
|
||||
radio.setCRCLength(RF24_CRC_16);
|
||||
|
||||
// Ensure auto ACK is enabled
|
||||
//radio.setAutoAck(1);
|
||||
|
||||
// Use the adapted PA level
|
||||
radio.setPALevel(NRF_PA_LEVEL);
|
||||
|
||||
// Open writing pipe
|
||||
radio.openWritingPipe(pipes[0]);
|
||||
radio.openReadingPipe(1, pipes[1]);
|
||||
|
||||
if(DEBUG) {
|
||||
radio.printDetails();
|
||||
}
|
||||
|
||||
// Init EnergyMonitor, with calibration factor for R1 = 22 Ohms
|
||||
emon1.current(0, ICAL);
|
||||
|
||||
digitalWrite(RED_LED_PIN, LOW);
|
||||
digitalWrite(GREEN_LED_PIN, LOW);
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// Loop part of the code //
|
||||
///////////////////////////////
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
//
|
||||
// Read Current
|
||||
//
|
||||
digitalWrite(GREEN_LED_PIN, HIGH);
|
||||
nrf.power = (int) (emon1.calcIrms(NUMBER_SAMPLES_I) * VOLTAGE);
|
||||
digitalWrite(GREEN_LED_PIN, LOW);
|
||||
|
||||
// Set voltage
|
||||
nrf.voltage = VOLTAGE;
|
||||
|
||||
// Read Vcc
|
||||
nrf.battery = (int) emon1.readVcc();
|
||||
|
||||
// Adding random for AES
|
||||
nrf.timer = millis();
|
||||
|
||||
if( DEBUG )
|
||||
{
|
||||
Serial.print("|");
|
||||
Serial.print(nrf.power);
|
||||
Serial.print("\t");
|
||||
Serial.print("|");
|
||||
Serial.print("\t");
|
||||
Serial.print(nrf.voltage);
|
||||
Serial.print("\t");
|
||||
Serial.print("|");
|
||||
Serial.print("\t");
|
||||
Serial.print(nrf.battery);
|
||||
Serial.print("\t");
|
||||
Serial.println("|");
|
||||
|
||||
Serial.print(F("Clear :"));
|
||||
PrintHex8((uint8_t*)&nrf, sizeof(PayloadTX));
|
||||
Serial.println();
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
// AES ciphering
|
||||
aes128_enc_single(key, &nrf);
|
||||
|
||||
if( DEBUG )
|
||||
{
|
||||
Serial.print(F("Cipher:"));
|
||||
PrintHex8((uint8_t*)&nrf, sizeof(PayloadTX));
|
||||
Serial.println();
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
//
|
||||
// Data sender
|
||||
//
|
||||
|
||||
// First, stop listening so we can talk.
|
||||
radio.stopListening();
|
||||
|
||||
radio.write(&nrf, sizeof(PayloadTX));
|
||||
|
||||
// Now, continue listening now the goal is to get some data back as ACK
|
||||
radio.startListening();
|
||||
|
||||
// Wait here until we get a response, or timeout (250ms)
|
||||
unsigned long started_waiting_at = millis();
|
||||
bool timeout = false;
|
||||
while ( ! radio.available() && ! timeout )
|
||||
if (millis() - started_waiting_at > TIMEOUT )
|
||||
timeout = true;
|
||||
|
||||
// Describe the results
|
||||
if ( timeout && DEBUG )
|
||||
Serial.println(F("[!] Failed to send packet : response timed out ..."));
|
||||
|
||||
// Stop listening
|
||||
radio.stopListening();
|
||||
|
||||
//
|
||||
// Shut down the system
|
||||
//
|
||||
|
||||
// 100ms for the MCU to settle
|
||||
delay(100);
|
||||
|
||||
// Power down the radio. Note that the radio will get powered back up
|
||||
// on the next write() call.
|
||||
// TODO : Fix this !
|
||||
//radio.powerDown();
|
||||
|
||||
// Sleep the MCU. The watchdog timer will awaken in a short while, and
|
||||
// continue execution here.
|
||||
do_sleep();
|
||||
|
||||
// 100ms for the MCU to settle
|
||||
delay(100);
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Sleep functions //
|
||||
//////////////////////////
|
||||
|
||||
// 0=16ms, 1=32ms,2=64ms,3=125ms,4=250ms,5=500ms
|
||||
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
|
||||
|
||||
void setup_watchdog(uint8_t prescalar)
|
||||
{
|
||||
prescalar = min(9,prescalar);
|
||||
uint8_t wdtcsr = prescalar & 7;
|
||||
if ( prescalar & 8 )
|
||||
wdtcsr |= _BV(WDP3);
|
||||
|
||||
MCUSR &= ~_BV(WDRF);
|
||||
WDTCSR = _BV(WDCE) | _BV(WDE);
|
||||
WDTCSR = _BV(WDCE) | wdtcsr | _BV(WDIE);
|
||||
}
|
||||
|
||||
ISR(WDT_vect)
|
||||
{
|
||||
}
|
||||
|
||||
void do_sleep(void)
|
||||
{
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
|
||||
sleep_enable();
|
||||
|
||||
sleep_mode(); // System sleeps here
|
||||
|
||||
sleep_disable(); // System continues execution here when watchdog timed out
|
||||
}
|
||||
|
||||
// Function for debug
|
||||
void PrintHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with leading zeroes
|
||||
{
|
||||
Serial.print("0x");
|
||||
for (int i=0; i<length; i++) {
|
||||
if (data[i]<0x10) {Serial.print("0");}
|
||||
Serial.print(data[i],HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
}
|
||||
|