commit
e970a0e102
|
@ -0,0 +1 @@
|
||||||
|
*.pyc
|
|
@ -0,0 +1,3 @@
|
||||||
|
Energy Efficiency Architecture for XLcloud project.
|
||||||
|
|
||||||
|
License: Apache.
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from flask import Flask, redirect, url_for, jsonify, abort
|
||||||
|
import collector
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return redirect(url_for('v1'))
|
||||||
|
|
||||||
|
@app.route('/v1/')
|
||||||
|
def v1():
|
||||||
|
return 'Welcome to Kwapi!'
|
||||||
|
|
||||||
|
@app.route('/v1/probes/')
|
||||||
|
def v1_probe_list():
|
||||||
|
return jsonify(probes=database.keys())
|
||||||
|
|
||||||
|
@app.route('/v1/probes/<probe>/')
|
||||||
|
def v1_probe_info(probe):
|
||||||
|
if not probe in database:
|
||||||
|
abort(404)
|
||||||
|
result = {probe: database[probe]}
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
@app.route('/v1/probes/<probe>/<meter>/')
|
||||||
|
def v1_probe_value(probe, meter):
|
||||||
|
if not probe in database or not meter in database[probe]._asdict():
|
||||||
|
abort(404)
|
||||||
|
result = {meter: database[probe]._asdict()[meter]}
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
collector = collector.Collector()
|
||||||
|
collector.clean(3600*24, True)
|
||||||
|
collector.start_listen()
|
||||||
|
database = collector.database
|
||||||
|
app.run(debug=True)
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import os, os.path
|
||||||
|
import threading
|
||||||
|
from recordtype import recordtype
|
||||||
|
|
||||||
|
class Collector:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.Record = recordtype('Record', 'timestamp, kwh, w')
|
||||||
|
self.database = {}
|
||||||
|
|
||||||
|
def add(self, probe, watts):
|
||||||
|
if probe in self.database:
|
||||||
|
currentTime = time.time()
|
||||||
|
record = self.database[probe]
|
||||||
|
record.kwh += (currentTime - record.timestamp) / 3600.0 * (watts / 1000.0)
|
||||||
|
record.w = watts
|
||||||
|
record.timestamp = currentTime
|
||||||
|
else:
|
||||||
|
record = self.Record(timestamp=time.time(), kwh=0.0, w=watts)
|
||||||
|
self.database[probe] = record
|
||||||
|
|
||||||
|
def remove(self, probe):
|
||||||
|
if probe in self.database:
|
||||||
|
del self.database[probe]
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clean(self, timeout, periodic):
|
||||||
|
# Cleaning
|
||||||
|
for probe in self.database.keys():
|
||||||
|
if time.time() - self.database[probe].timestamp > timeout:
|
||||||
|
self.remove(probe)
|
||||||
|
|
||||||
|
# Cancel next execution of this function
|
||||||
|
try:
|
||||||
|
self.timer.cancel()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Schedule periodic execution of this function
|
||||||
|
if periodic:
|
||||||
|
self.timer = threading.Timer(timeout, self.clean, [timeout])
|
||||||
|
self.timer.start()
|
||||||
|
|
||||||
|
def listen(self, socket_name='/tmp/kwapi-collector'):
|
||||||
|
if os.path.exists(socket_name):
|
||||||
|
os.remove(socket_name)
|
||||||
|
|
||||||
|
server = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||||
|
server.bind(socket_name)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
datagram = server.recv(1024)
|
||||||
|
if not datagram:
|
||||||
|
print 'Error: not datagram'
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
data = datagram.split(':')
|
||||||
|
if len(data) == 2:
|
||||||
|
try:
|
||||||
|
self.add(data[0], float(data[1]))
|
||||||
|
except:
|
||||||
|
print 'Format error!'
|
||||||
|
else:
|
||||||
|
print 'Malformed datagram!'
|
||||||
|
server.close()
|
||||||
|
os.remove(socket_name)
|
||||||
|
|
||||||
|
def start_listen(self):
|
||||||
|
thread = threading.Thread(target=self.listen)
|
||||||
|
thread.start()
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="config.xsd">
|
||||||
|
|
||||||
|
<collector socket="/tmp/kwapi-collector"/>
|
||||||
|
|
||||||
|
<driver class="Wattsup">
|
||||||
|
<probe id="A">
|
||||||
|
<arg name="device" value="/dev/ttyUSB0"/>
|
||||||
|
</probe>
|
||||||
|
<!-- <probe id="B">-->
|
||||||
|
<!-- <arg name="device" value="/dev/ttyUSB1"/>-->
|
||||||
|
<!-- </probe>-->
|
||||||
|
</driver>
|
||||||
|
|
||||||
|
<!-- <driver class="Omega">-->
|
||||||
|
<!-- <probe id="C">-->
|
||||||
|
<!-- <arg name="IP" value="192.168.0.10"/>-->
|
||||||
|
<!-- <arg name="port" value="2020"/>-->
|
||||||
|
<!-- <arg name="output" value="1"/>-->
|
||||||
|
<!-- </probe>-->
|
||||||
|
<!-- <probe id="D">-->
|
||||||
|
<!-- <arg name="IP" value="192.168.0.10"/>-->
|
||||||
|
<!-- <arg name="port" value="2020"/>-->
|
||||||
|
<!-- <arg name="output" value="1"/>-->
|
||||||
|
<!-- </probe>-->
|
||||||
|
<!-- </driver>-->
|
||||||
|
|
||||||
|
<driver class="Dummy">
|
||||||
|
<probe id="X"/>
|
||||||
|
<probe id="Y">
|
||||||
|
<arg name="min_value" value="10"/>
|
||||||
|
<arg name="max_value" value="20"/>
|
||||||
|
</probe>
|
||||||
|
</driver>
|
||||||
|
|
||||||
|
</config>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
|
||||||
|
|
||||||
|
<xs:element name="config" type="config">
|
||||||
|
<xs:unique name="driverClass">
|
||||||
|
<xs:selector xpath="./driver"/>
|
||||||
|
<xs:field xpath="@class"/>
|
||||||
|
</xs:unique>
|
||||||
|
<xs:unique name="probeID">
|
||||||
|
<xs:selector xpath="./driver/probe"/>
|
||||||
|
<xs:field xpath="@id"/>
|
||||||
|
</xs:unique>
|
||||||
|
</xs:element>
|
||||||
|
|
||||||
|
<xs:complexType name="config">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="collector" type="collector"/>
|
||||||
|
<xs:element name="driver" type="driver" maxOccurs="unbounded"/>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="collector">
|
||||||
|
<xs:attribute name="socket" type="xs:string"/>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="driver">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="probe" type="probe" maxOccurs="unbounded"/>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="class" type="xs:NCName"/>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="probe">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="arg" type="arg" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="id" type="xs:NCName"/>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="arg">
|
||||||
|
<xs:attribute name="name" type="xs:string"/>
|
||||||
|
<xs:attribute name="value" type="xs:string"/>
|
||||||
|
</xs:complexType>
|
||||||
|
|
||||||
|
</xs:schema>
|
|
@ -0,0 +1,3 @@
|
||||||
|
from driver import Driver
|
||||||
|
from wattsup import Wattsup
|
||||||
|
from dummy import Dummy
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
class Driver(Thread):
|
||||||
|
|
||||||
|
def __init__(self, probe_id):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.name = probe_id
|
||||||
|
self.probe_observers = []
|
||||||
|
self.terminate = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def update_value(self, value):
|
||||||
|
for notify_new_value in self.probe_observers :
|
||||||
|
notify_new_value(self.name, value)
|
||||||
|
|
||||||
|
def subscribe(self, observer):
|
||||||
|
self.probe_observers.append(observer)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.terminate = True
|
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from driver import Driver
|
||||||
|
from random import randrange
|
||||||
|
import time
|
||||||
|
|
||||||
|
class Dummy(Driver):
|
||||||
|
|
||||||
|
def __init__(self, probe_id, **kwargs):
|
||||||
|
Driver.__init__(self, probe_id)
|
||||||
|
self.min_value = int(kwargs.get('min_value', 75))
|
||||||
|
self.max_value = int(kwargs.get('max_value', 100))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self.terminate:
|
||||||
|
value = randrange(self.min_value, self.max_value)
|
||||||
|
self.update_value(value)
|
||||||
|
time.sleep(1)
|
|
@ -0,0 +1,48 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from driver import Driver
|
||||||
|
import serial
|
||||||
|
|
||||||
|
class Wattsup(Driver):
|
||||||
|
|
||||||
|
def __init__(self, probe_id, **kwargs):
|
||||||
|
Driver.__init__(self, probe_id)
|
||||||
|
|
||||||
|
# Configure serial port
|
||||||
|
self.serial = serial.Serial(
|
||||||
|
port=kwargs.get('device', '/dev/ttyUSB0'),
|
||||||
|
baudrate=115200,
|
||||||
|
parity=serial.PARITY_NONE,
|
||||||
|
stopbits=serial.STOPBITS_ONE,
|
||||||
|
bytesize=serial.EIGHTBITS,
|
||||||
|
timeout=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear memory
|
||||||
|
self.serial.write('#R,W,0;')
|
||||||
|
self.serial.read(256)
|
||||||
|
|
||||||
|
# Start external logging with interval = 1
|
||||||
|
self.serial.write('#L,W,3,E,1,1;')
|
||||||
|
self.serial.read(256)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self.terminate:
|
||||||
|
packet = self.get_packet()
|
||||||
|
value = self.extract_watts(packet)
|
||||||
|
self.update_value(value)
|
||||||
|
|
||||||
|
def get_packet(self):
|
||||||
|
packet = ''
|
||||||
|
while True:
|
||||||
|
char = self.serial.read(1)
|
||||||
|
if len(char) == 0:
|
||||||
|
raise ValueError('Invalid packet')
|
||||||
|
packet += char
|
||||||
|
if char == ';':
|
||||||
|
return packet
|
||||||
|
|
||||||
|
def extract_watts(self, packet):
|
||||||
|
value = float(packet.split(',')[3])/10.0
|
||||||
|
return value
|
|
@ -0,0 +1,118 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
from subprocess import call
|
||||||
|
import drivers
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import thread
|
||||||
|
import threading
|
||||||
|
import signal
|
||||||
|
|
||||||
|
threads = {}
|
||||||
|
socket_name = ''
|
||||||
|
|
||||||
|
def get_root(schema, xml):
|
||||||
|
# Validating XML schema
|
||||||
|
xsd = etree.parse(schema)
|
||||||
|
schema = etree.XMLSchema(xsd)
|
||||||
|
parser = etree.XMLParser(schema = schema)
|
||||||
|
try:
|
||||||
|
tree = etree.parse(xml, parser)
|
||||||
|
except etree.XMLSyntaxError:
|
||||||
|
return None
|
||||||
|
return tree.getroot()
|
||||||
|
|
||||||
|
def send_value(probe_id, value):
|
||||||
|
if os.path.exists(socket_name):
|
||||||
|
client = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||||
|
client.connect(socket_name)
|
||||||
|
client.send(probe_id + ':' + str(value))
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
def signal_handler(signum, frame):
|
||||||
|
if signum is signal.SIGTERM:
|
||||||
|
terminate()
|
||||||
|
|
||||||
|
def check_probes_alive(interval=60):
|
||||||
|
# TODO : default value because main exit before this thread...
|
||||||
|
print 'Check probes every', interval, 'seconds'
|
||||||
|
for thread in threads.keys():
|
||||||
|
if not threads[thread].is_alive():
|
||||||
|
print threads[thread], ' crashed!'
|
||||||
|
load_probe_from_xml(thread)
|
||||||
|
timer = threading.Timer(interval, check_probes_alive, [interval])
|
||||||
|
timer.daemon = True
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
def terminate():
|
||||||
|
check_probes_alive()
|
||||||
|
for thread in threads.values():
|
||||||
|
thread.stop()
|
||||||
|
for thread in threads.values():
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
def load_probe(class_name, probe_id, kwargs):
|
||||||
|
try:
|
||||||
|
probeClass = getattr(sys.modules['drivers'], class_name)
|
||||||
|
except AttributeError:
|
||||||
|
raise NameError("%s doesn't exist." % class_name)
|
||||||
|
try:
|
||||||
|
probeObject = probeClass(probe_id, **kwargs)
|
||||||
|
except Exception as exception:
|
||||||
|
print 'Probe "' + probe_id + '" error: %s' % exception
|
||||||
|
return
|
||||||
|
probeObject.subscribe(send_value)
|
||||||
|
probeObject.start()
|
||||||
|
threads[probe_id] = probeObject
|
||||||
|
|
||||||
|
def load_probe_from_xml(probe_id):
|
||||||
|
print 'load_probe ', probe_id
|
||||||
|
|
||||||
|
root = get_root('config.xsd', 'config.xml')
|
||||||
|
if root is None:
|
||||||
|
print "Configuration file isn't valid!"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
probe = root.find("./driver/probe[@id='%s']" % probe_id)
|
||||||
|
class_name = probe.getparent().attrib['class']
|
||||||
|
kwargs = {}
|
||||||
|
for argument in probe:
|
||||||
|
kwargs[argument.attrib['name']] = argument.attrib['value']
|
||||||
|
load_probe(class_name, probe_id, kwargs)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Load and validate XML
|
||||||
|
root = get_root('config.xsd', 'config.xml')
|
||||||
|
if root is None:
|
||||||
|
print "Configuration file isn't valid!"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Get socket path
|
||||||
|
socket_name = root.find('collector').attrib['socket']
|
||||||
|
|
||||||
|
# Load probes
|
||||||
|
probe_ids = root.findall("./driver/probe")
|
||||||
|
for probe_id in probe_ids:
|
||||||
|
load_probe_from_xml(probe_id.attrib['id'])
|
||||||
|
|
||||||
|
# Load probes
|
||||||
|
# for driver in root.findall('driver'):
|
||||||
|
# for probe in driver.findall('probe'):
|
||||||
|
# class_name = driver.attrib['class']
|
||||||
|
# probe_id = probe.attrib['id']
|
||||||
|
# kwargs = {}
|
||||||
|
# for argument in probe.findall('arg'):
|
||||||
|
# kwargs[argument.attrib['name']] = argument.attrib['value']
|
||||||
|
# thread.start_new_thread(load_probe, (class_name, probe_id, kwargs))
|
||||||
|
|
||||||
|
# Check probe crashes
|
||||||
|
check_probes_alive(60)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
try:
|
||||||
|
signal.pause()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
terminate()
|
Loading…
Reference in New Issue