diff --git a/apps/dashboard/xaal/dashboard/app.py b/apps/dashboard/xaal/dashboard/app.py index 1944d25cd573535e2bc2690e4284de9a1a3dacbf..bb797b97c659d67fcae5be3d519ff899e00caf29 100644 --- a/apps/dashboard/xaal/dashboard/app.py +++ b/apps/dashboard/xaal/dashboard/app.py @@ -8,7 +8,7 @@ from gevent.pywsgi import WSGIServer from geventwebsocket.handler import WebSocketHandler -from .pages import default_pages +from .pages import default_pages,warp10 from .core import xaal_core from .core import sio @@ -32,6 +32,7 @@ def run(): TEMPLATE_PATH.append(root) xaal_core.setup() + warp10.setup() # debug disable template cache & enable error reporting debug(True) bottle_app = default_app() diff --git a/apps/dashboard/xaal/dashboard/core/xaal_core.py b/apps/dashboard/xaal/dashboard/core/xaal_core.py index 7068b42bd71af53c76ed8a6ff50e5101493bb81f..9ed852e1d988552715f17142894a05be85bd42a4 100644 --- a/apps/dashboard/xaal/dashboard/core/xaal_core.py +++ b/apps/dashboard/xaal/dashboard/core/xaal_core.py @@ -33,8 +33,8 @@ def event_handler(ev_type,dev): def setup(): """ setup xAAL Engine & Device. And start it in a Greenlet""" - helpers.setup_console_logger() global monitor + helpers.setup_console_logger() engine = Engine() cfg = tools.load_cfg(PACKAGE_NAME) if not cfg: diff --git a/apps/dashboard/xaal/dashboard/pages/base.py b/apps/dashboard/xaal/dashboard/pages/base.py index 020d61f7fb6a2c161723937bdc1bab1192e0b5f7..caaef6938b8219abb627e571d31c6dce76c0de33 100644 --- a/apps/dashboard/xaal/dashboard/pages/base.py +++ b/apps/dashboard/xaal/dashboard/pages/base.py @@ -85,8 +85,6 @@ def save_metadata(addr): - - @route('/grid') @view('grid.mako') def test_grid(): @@ -102,3 +100,4 @@ def socketio_latency_test(): @view('links.mako') def links(): return {'title':'Links'} + diff --git a/apps/dashboard/xaal/dashboard/pages/warp10.py b/apps/dashboard/xaal/dashboard/pages/warp10.py new file mode 100644 index 0000000000000000000000000000000000000000..358e14c78b4e2ba968e1de878c9dcb96376dd934 --- /dev/null +++ b/apps/dashboard/xaal/dashboard/pages/warp10.py @@ -0,0 +1,80 @@ +from xaal.lib import tools +from .default import view,route +from .default import xaal_core as xaal + + +from bottle import get,response +import logging +import ujson +import requests + +TOKEN=None +URL=None +CLASS = None + + +DAILY_REQ = """ [ $TOKEN '%s' { 'devid' '%s' } NOW 49 h ] FETCH """ + +bindings = {'thermometer.basic' : 'temperature', + 'barometer.basic' : 'pressure', + 'hygrometer.basic' : 'humidity', + 'powermeter.basic' : 'power', + 'co2meter.basic' : 'co2', + } + +logger = logging.getLogger(__name__) + +def setup(): + global TOKEN,URL,CLASS + cfg = tools.load_cfg(xaal.PACKAGE_NAME).get('config',None) + if cfg: + TOKEN = cfg.get('warp10_token',None) + URL = cfg.get('warp10_url',None) + CLASS = cfg.get('warp10_class',None) + logger.warning("%s %s" % (URL,CLASS)) + +def init_req(): + return """'%s' 'TOKEN' STORE\n""" % (TOKEN) + + +def query(req): + r = requests.post(URL,req) + print(r) + return r.text + + +def to_chartjs(data): + r = [] + for tuple_ in data: + date = round(tuple_[0] / 1000) + value = tuple_[1] + #r.append({"x": date, "y":value }) + r.append((date,value)) + return r + +@get('/warp10/daily/<addr>') +def daily(addr): + global CLASS + response.headers['Content-Type'] = 'application/json' + res = {} + dev=xaal.monitor.devices.get_with_addr(addr) + if dev: + req = init_req() + devtype = dev.devtype + var = bindings.get(devtype,None) + if not var: + return '{}' + class_ = '%s.%s.%s' % (CLASS,devtype,var) + tmp = DAILY_REQ % (class_,addr) + req = init_req() + tmp + data=query(req) + + res = ujson.loads(data)[0][0]['v'] + return ujson.dumps(to_chartjs(res)) + + +@route('/warp10/graph/<addr>') +@view('graph.mako') +def graph(addr): + r = {"title" : "Warp10 daily","addr":addr} + return r diff --git a/apps/dashboard/xaal/dashboard/static/css/btns.css b/apps/dashboard/xaal/dashboard/static/css/btns.css new file mode 100644 index 0000000000000000000000000000000000000000..2b14630f807b5abc703d427588d3969f62e89fd3 --- /dev/null +++ b/apps/dashboard/xaal/dashboard/static/css/btns.css @@ -0,0 +1,45 @@ +.onoffswitch { + position: relative; width: 60px; + -webkit-user-select:none; -moz-user-select:none; -ms-user-select: none; +} +.onoffswitch-checkbox { + display: none; +} +.onoffswitch-label { + display: block; overflow: hidden; cursor: pointer; + border: 1px solid #999999; border-radius: 20px; +} +.onoffswitch-inner { + display: block; width: 200%; margin-left: -100%; + transition: margin 0.1s ease-in 0s; +} +.onoffswitch-inner:before, .onoffswitch-inner:after { + display: block; float: left; width: 50%; height: 20px; padding: 0; line-height: 20px; + font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold; + box-sizing: border-box; +} +.onoffswitch-inner:before { + content: ""; + padding-left: 10px; + background-color: #ED054E; color: #FFFFFF; +} +.onoffswitch-inner:after { + content: ""; + padding-right: 10px; + background-color: #00bbd7; color: #DEDEDE; + text-align: right; +} +.onoffswitch-switch { + display: block; width: 16px; margin: 2px; + background: #FFFFFF; + position: absolute; top: 0; bottom: 0; + right: 36px; + border: 1px solid #999999; border-radius: 20px; + transition: all 0.1s ease-in 0s; +} +.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner { + margin-left: 0; +} +.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { + right: 0px; +} diff --git a/apps/dashboard/xaal/dashboard/static/css/site.css b/apps/dashboard/xaal/dashboard/static/css/site.css index 19366f587575e29a35a3bdbf79f6c2d7eb634e5d..91c27fc533ebbde8121ae2df2dd8733e8266ca9c 100644 --- a/apps/dashboard/xaal/dashboard/static/css/site.css +++ b/apps/dashboard/xaal/dashboard/static/css/site.css @@ -152,8 +152,8 @@ ul.menu li a:hover { } .grid-background { - /*background-image: url("https://wallpaperscraft.com/image/spruce_shadow_dark_background_branch_68089_1920x1200.jpg");*/ - background-image: url("/static/imgs/bg1.jpg"); + /* background-image: url("https://wallpaperscraft.com/image/spruce_shadow_dark_background_branch_68089_1920x1200.jpg"); */ + background-image: url("/static/imgs/bg3.jpg"); min-height: 900px; padding: 0.5em; } @@ -164,7 +164,7 @@ ul.menu li a:hover { border-radius: 3px; padding: 8px; font-size: 120%; - height: 200px; + height: 250px; border: 1px solid var(--color2); /* display: flex; */ diff --git a/apps/dashboard/xaal/dashboard/static/imgs/bg3.jpg b/apps/dashboard/xaal/dashboard/static/imgs/bg3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d68260900bcfda53c3fea335b020aa841217adf6 Binary files /dev/null and b/apps/dashboard/xaal/dashboard/static/imgs/bg3.jpg differ diff --git a/apps/dashboard/xaal/dashboard/static/imgs/bg5.jpg b/apps/dashboard/xaal/dashboard/static/imgs/bg5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d68260900bcfda53c3fea335b020aa841217adf6 Binary files /dev/null and b/apps/dashboard/xaal/dashboard/static/imgs/bg5.jpg differ diff --git a/apps/dashboard/xaal/dashboard/static/js/site.js b/apps/dashboard/xaal/dashboard/static/js/site.js index e421ad5bc90fe0886b69f1b1bfb705c1733ffaa9..ce71c031c7de319c9595b62e99aeb682f1dd5789 100644 --- a/apps/dashboard/xaal/dashboard/static/js/site.js +++ b/apps/dashboard/xaal/dashboard/static/js/site.js @@ -113,7 +113,8 @@ function run_sio() { }); } - +//================ refresh attributes ======================================= +// the old refresh_attributes blocks the page rendering process. function sio_refresh_attributes_old() { console.log('refresh_attributes'); var addrs = []; diff --git a/apps/dashboard/xaal/dashboard/static/manifest.json b/apps/dashboard/xaal/dashboard/static/manifest.json index ad4812e507972d545d86c727d57df3f928bd63e2..3f3098ccbb8ff5ac62c780d7c417a7b4c3bcdda2 100644 --- a/apps/dashboard/xaal/dashboard/static/manifest.json +++ b/apps/dashboard/xaal/dashboard/static/manifest.json @@ -3,10 +3,10 @@ "name": "xAAL SocketIO DashBoard", "background_color": "#fff", "display": "standalone", - "start_url": "/", + "start_url": "../", "icons": [ { - "src": "/static/imgs/launcher-icon-4x.png", + "src": "./imgs/launcher-icon-4x.png", "type": "image/png", "sizes": "192x192" } diff --git a/apps/dashboard/xaal/dashboard/static/tags/barometer.tag b/apps/dashboard/xaal/dashboard/static/tags/barometer.tag new file mode 100644 index 0000000000000000000000000000000000000000..25ba7249202ee7c286e5071ad6e7bcfee304bef5 --- /dev/null +++ b/apps/dashboard/xaal/dashboard/static/tags/barometer.tag @@ -0,0 +1,25 @@ +<barometer> + +<span class="barometer"> + <span class="pressure">{ pressure } hPa</span><br/> +</span> + + +<script> + this.addr = opts.xaal_addr; + this.pressure = null; + receive(data) { + this.pressure = Math.round(data['attributes']['pressure']); + this.update(); + } +</script> + +<style> +.barometer { + font-weight: bold; + color : var(--color1); + align: center; +} +</style> + +</barometer> diff --git a/apps/dashboard/xaal/dashboard/static/tags/co2meter.tag b/apps/dashboard/xaal/dashboard/static/tags/co2meter.tag new file mode 100644 index 0000000000000000000000000000000000000000..5318d6839f8c4eef73633eaef83c440fbb2d0a0f --- /dev/null +++ b/apps/dashboard/xaal/dashboard/static/tags/co2meter.tag @@ -0,0 +1,25 @@ +<co2meter> + +<span class="co2meter"> + <span if={value} class="co2">{ value } ppm</span> +</span> + + +<script> + this.addr = opts.xaal_addr; + this.value = ''; + receive(data) { + this.value = data['attributes']['co2']; + this.update(); + } +</script> + +<style> +.co2 { + font-weight: bold; + color : var(--color1); + align: center; +} +</style> + +</co2meter> diff --git a/apps/dashboard/xaal/dashboard/static/tags/door.tag b/apps/dashboard/xaal/dashboard/static/tags/door.tag new file mode 100644 index 0000000000000000000000000000000000000000..229ccf69f0807772e14e9527526ca0da73a8de22 --- /dev/null +++ b/apps/dashboard/xaal/dashboard/static/tags/door.tag @@ -0,0 +1,39 @@ +<door> + +<span class="door"> + <span class="{class}">⚛</span> +</span> + + +<script> + this.addr = opts.xaal_addr; + this.class = 'close'; + + receive(data) { + state = data['attributes']['position']; + if (state == true) { + this.class = 'open' + } + else { + this.class = 'close' + } + this.update(); + } +</script> + +<style> +.open { + font-weight: bold; + color : var(--color3); + align: center; +} + +.close { + font-weight: bold; + color : var(--color2); + align: center; +} + +</style> + +</door> diff --git a/apps/dashboard/xaal/dashboard/static/tags/hygrometer.tag b/apps/dashboard/xaal/dashboard/static/tags/hygrometer.tag index abc3a4725d3c389aedf614315f9c1064164f663e..bd099460a60532b8e3ecb24a89e776019eb15069 100644 --- a/apps/dashboard/xaal/dashboard/static/tags/hygrometer.tag +++ b/apps/dashboard/xaal/dashboard/static/tags/hygrometer.tag @@ -1,15 +1,15 @@ <hygrometer> <span class="hygrometer"> - <span class="humidity">{ humidity } %</span> + <span if={value} class="humidity">{ value } %</span> </span> <script> this.addr = opts.xaal_addr; - this.humidity = '__'; + this.value = null; receive(data) { - this.humidity = data['attributes']['humidity']; + this.value = data['attributes']['humidity']; this.update(); } </script> diff --git a/apps/dashboard/xaal/dashboard/static/tags/lamp.tag b/apps/dashboard/xaal/dashboard/static/tags/lamp.tag index 775960434f84e5bfa57d0034dd66710feafe3f03..0d7bec2a4a2988c444e532c9a47c0ba38f2d6205 100644 --- a/apps/dashboard/xaal/dashboard/static/tags/lamp.tag +++ b/apps/dashboard/xaal/dashboard/static/tags/lamp.tag @@ -1,48 +1,33 @@ <lamp> -<div class="lamp"> -<div class="{stateClass}">{ light }</div> - <button class="button ripple" name="btn_on" onclick={ btn }>ON</button> - <button class="button ripple" name="btn_off" onclick={ btn }>OFF</button> +<div class="onoffswitch"> + <input type="checkbox" class="onoffswitch-checkbox" id={ tag_id } onchange={ chk } checked={checked}> + <label class="onoffswitch-label" for={ tag_id }> + <span class="onoffswitch-inner"></span> + <span class="onoffswitch-switch"></span> + </label> </div> <script> this.addr = opts.xaal_addr - this.light = '__'; - this.stateClass = 'state-unknow'; - + this.tag_id = 'btn_'+Math.random(); + receive(data) { - state = data['attributes']['light'] + state = data['attributes']['light'] if (state == true) { - this.light = 'ON' - this.stateClass = 'state-on' + this.checked = true } else { - this.light = 'OFF' - this.stateClass = 'state-off' + this.checked = false } this.update() - } + } - btn(e) { - if (e.target.name =='btn_on') + chk(e) { + if (e.target.checked == true) sio_send_request(this.addr,'on',{}) - if (e.target.name =='btn_off') + else sio_send_request(this.addr,'off',{}) } </script> - -<style> - .state-on { - color: var(--color3); - font-weight: bold; - padding: 8px; - } - - .state-unknow, .state-off { - color: var(--color1); - font-weight: bold; - padding: 8px; - } -</style> </lamp> diff --git a/apps/dashboard/xaal/dashboard/static/tags/motion.tag b/apps/dashboard/xaal/dashboard/static/tags/motion.tag new file mode 100644 index 0000000000000000000000000000000000000000..c4d90c1a8ff91f2617e326dfcb72860147301ef6 --- /dev/null +++ b/apps/dashboard/xaal/dashboard/static/tags/motion.tag @@ -0,0 +1,42 @@ +<motion> + +<span class="motion"> + <span class="{class}">⚛</span> +</span> + + +<script> + this.addr = opts.xaal_addr; + this.presence = false; + this.class = 'no_motion'; + + receive(data) { + state = data['attributes']['presence']; + if (state == true) { + this.presence = true + this.class = 'motion' + } + else { + this.presence = false + this.class = 'no_motion' + } + this.update(); + } +</script> + +<style> +.motion { + font-weight: bold; + color : var(--color3); + align: center; +} + +.no_motion { + font-weight: bold; + color : var(--color2); + align: center; +} + +</style> + +</motion> diff --git a/apps/dashboard/xaal/dashboard/static/tags/powermeter.tag b/apps/dashboard/xaal/dashboard/static/tags/powermeter.tag index 8cb5c6a4a69876dbf61f0a471fd084f735061059..90978f9786d467908f2145e9e6803f4fb9b08c43 100644 --- a/apps/dashboard/xaal/dashboard/static/tags/powermeter.tag +++ b/apps/dashboard/xaal/dashboard/static/tags/powermeter.tag @@ -1,7 +1,7 @@ <powermeter> <span class="powermeter"> - <span if={power} class="power">{ power } W</span><br/> + <span class="power">{ power } W</span><br/> <span if={energy} class="energy">{ energy } kW</span> </span> diff --git a/apps/dashboard/xaal/dashboard/static/tags/powerrelay.tag b/apps/dashboard/xaal/dashboard/static/tags/powerrelay.tag index 1efb80c84d4d561d165472c1b39be0c085cd5dae..8b8ce9a13d64ef864bdc9bf25cc3a71519dd1dda 100644 --- a/apps/dashboard/xaal/dashboard/static/tags/powerrelay.tag +++ b/apps/dashboard/xaal/dashboard/static/tags/powerrelay.tag @@ -1,52 +1,40 @@ <powerrelay> -<div class="powerrelay"> -<div class="{stateClass}">{ power }</div> - <button class="button ripple" name="btn_on" onclick={ btn }>ON</button> - <button class="button ripple" name="btn_off" onclick={ btn }>OFF</button> +<div class="onoffswitch"> + <input type="checkbox" class="onoffswitch-checkbox" id={ tag_id } onchange={ chk } checked={checked}> + <label class="onoffswitch-label" for={ tag_id }> + <span class="onoffswitch-inner"></span> + <span class="onoffswitch-switch"></span> + </label> </div> <script> this.addr = opts.xaal_addr - this.power = '__'; - this.stateClass = 'state-unknow'; - + this.tag_id = 'btn_'+Math.random(); + receive(data) { state = data['attributes']['power'] if (state == true) { - this.power = 'ON' - this.stateClass = 'state-on' + this.checked = true } else { - this.power = 'OFF' - this.stateClass = 'state-off' + this.checked = false } this.update() } - btn(e) { - if (e.target.name =='btn_on') + chk(e) { + if (e.target.checked == true) sio_send_request(this.addr,'on',{}) - if (e.target.name =='btn_off') + else sio_send_request(this.addr,'off',{}) } + </script> <style> .powerrelay { } - .state-on { - color: var(--color3); - font-weight: bold; - padding: 8px; - } - - .state-unknow, .state-off { - color: var(--color1); - font-weight: bold; - padding: 8px; - } - </style> </powerrelay> diff --git a/apps/dashboard/xaal/dashboard/static/tags/thermometer.tag b/apps/dashboard/xaal/dashboard/static/tags/thermometer.tag index 7785a06013fe7e2724a5e6b202a88658d877ebb5..e22d204004ea07026b403eb95480743d13ca8f73 100644 --- a/apps/dashboard/xaal/dashboard/static/tags/thermometer.tag +++ b/apps/dashboard/xaal/dashboard/static/tags/thermometer.tag @@ -1,14 +1,14 @@ <thermometer> <span class="thermometer"> - <span class="temperature">{ temperature } °</span> + <span if={value} class="temperature">{ value } °</span> </span> <script> this.addr = opts.xaal_addr - this.temperature = '__._'; + this.value = null receive(data) { - this.temperature = data['attributes']['temperature'] + this.value = data['attributes']['temperature'] this.update() } </script> diff --git a/apps/dashboard/xaal/dashboard/templates/base.mako b/apps/dashboard/xaal/dashboard/templates/base.mako index 30cae818db1e5d1254767ff46a14dd172276337a..b8a44fc027cc064170a059a91bc549b13fbbde8c 100644 --- a/apps/dashboard/xaal/dashboard/templates/base.mako +++ b/apps/dashboard/xaal/dashboard/templates/base.mako @@ -11,6 +11,7 @@ <meta name="mobile-web-app-capable" content="yes"> <link rel="manifest" href="/static/manifest.json"> <link rel="icon" href="/static/imgs/favicon.ico"> + <meta name="theme-color" content="#333" /> <!-- CSS & Fonts --> <link href="/static/css/site.css" rel="stylesheet"> @@ -21,16 +22,7 @@ <!-- Menu --> <div> - <ul class="menu"> - <li><a href="#" onclick="openNav()">☰</a></li> - <li><a href="/devices">Devices</a></li> - <li class="active"><a href="/grid">Grid</a></li> - <!-- - <li><a href="/bottle_info">HTTPD</a></li> - <li><a href="/links">Links</a></li> - <li><a href="/stats">Stats</a></li> - --> - </ul> + <%include file="./menu.mako" /> </div> diff --git a/apps/dashboard/xaal/dashboard/templates/devices.mako b/apps/dashboard/xaal/dashboard/templates/devices.mako index 3c1fb1f2d26e04775cd5a2b045fd9f9826423489..77202645275a319a360a3f3d3b1ce7f7c3d9fc66 100644 --- a/apps/dashboard/xaal/dashboard/templates/devices.mako +++ b/apps/dashboard/xaal/dashboard/templates/devices.mako @@ -1,6 +1,6 @@ <%inherit file="base.mako"/> -<script src="/static/js/sorttable.js"></script> +<script src="../static/js/sorttable.js"></script> <!-- https://www.w3schools.com/howto/howto_js_filter_table.asp --> @@ -44,9 +44,9 @@ function filterFunc(input_id,col) { <tr><th width=20%>Address</th><th width=15%>devtype</th><th width=15%>Name</th><th width=15%>Info</th><th width=35%>Attributes</th></tr> % for dev in devs: <tr> - <td><a href="/generic/${dev.address}">${dev.address}</a></td> + <td><a href="./generic/${dev.address}">${dev.address}</a></td> <td>${dev.devtype}</td> - <td><a href="/edit_metadata/${dev.address}">➠</a> ${dev.display_name}</td> + <td><a href="./edit_metadata/${dev.address}">➠</a> ${dev.display_name}</td> %if 'info' in dev.description.keys(): <td>${dev.description['info']}</td> %else: diff --git a/apps/dashboard/xaal/dashboard/templates/generic.mako b/apps/dashboard/xaal/dashboard/templates/generic.mako index bdc635c754228507f5c3c4b2db74a692ab818ae7..b1412d6897c392fb2d44307c067cbf6f4eb96a50 100644 --- a/apps/dashboard/xaal/dashboard/templates/generic.mako +++ b/apps/dashboard/xaal/dashboard/templates/generic.mako @@ -28,7 +28,7 @@ <h2>Attributes</h2> <div data-is="generic-attrs" xaal_addr="${dev.address}"></div> -<script type="riot/tag" src="/static/tags/generic_attrs.tag"></script> +<script type="riot/tag" src="../static/tags/generic_attrs.tag"></script> <h2>Meta Data</h2> <table width=100%> @@ -37,4 +37,4 @@ <tr><td>${k}</td><td>${dev.db[k]}</td></tr> % endfor </table> -<a href="/edit_metadata/${dev.address}">Edit meta-data</a> +<a href="../edit_metadata/${dev.address}">Edit meta-data</a> diff --git a/apps/dashboard/xaal/dashboard/templates/graph.mako b/apps/dashboard/xaal/dashboard/templates/graph.mako new file mode 100644 index 0000000000000000000000000000000000000000..82f8f4cc54cf6aa478e3f63b3bf9f670bccaf128 --- /dev/null +++ b/apps/dashboard/xaal/dashboard/templates/graph.mako @@ -0,0 +1,122 @@ +<%inherit file="base.mako"/> + +<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script> +<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script> +<script src="https://cdn.jsdelivr.net/npm/hammerjs@2.0.8"></script> +<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@0.7.0"></script> + + + + + +<br/> +<div class="content"> + <div id="data">Warp10 datas for ${addr} daily</div> + <canvas id="chart"></canvas> +</div> + + + +<script> + +//================ JS tools ==================================================== +// dumbs functions to mimic jQuery selectors +var _ = function ( elem ) { + return document.querySelector( elem ); +} + +var __ = function ( elem ) { + return document.querySelectorAll( elem ); +} + + +function update(data) { + //console.log(data); + //_('#data').innerHTML = "<pre>" + data +"</pre>"; + clearData(chart); + addData(chart,data); +} + +function clearData(chart) { + if (chart.hasOwnProperty('datasets')) { + chart.datasets.forEach((dataset) => { + dataset.data = []; + }) + } + chart.data.labels=[]; + chart.update(); +} + +function load(addr) { + var getUrl = window.location; + var url = getUrl.protocol + "//" + getUrl.host + "/warp10/daily/"+addr; + console.log(url); + fetch(url) + .then(response => response.json()) + .then(data => update(data)) + .catch(err => console.log(err)) +} + + + +function addData(chart, data) { + data.forEach((kv) => { + chart.data.labels.push(kv[0]); + chart.data.datasets[0].data.push(kv[1]); + } + ); + //chart.data.labels.push(label); + //chart.data.datasets.forEach((dataset) => { + // dataset.data.push(data); + //}); + chart.update(); + chart.render(); +} + + + +var config = { + type: 'line', + data: { + datasets: [{ + label: "Warp10 data", + borderColor: "#00bbd7", + pointRadius: 1, + borderWidth: 2, + lineTension: 0.2, + }] + }, + + options: { + responsive: true, + //maintainAspectRatio: false, + scales: { + xAxes: [{ + type: 'time', + time: { + parser: 'X', + displayFormats: { hour: 'H:mm'} + } + }], + }, + } +}; + + +var ctx = document.getElementById("chart").getContext("2d"); +chart=new Chart(ctx, config); + +function refresh() { + console.log('Loading datas'); + load('${addr}'); +} + + +refresh(); +/* +setInterval(() => { + refresh(); + },1000 * 60); +*/ + +</script> diff --git a/apps/dashboard/xaal/dashboard/templates/grid.mako b/apps/dashboard/xaal/dashboard/templates/grid.mako index 54163ecb20c5d751ee62af144328cdf0d6640448..05f5c8d7e45cd39b5f6d4948a9c21b4d7d142719 100644 --- a/apps/dashboard/xaal/dashboard/templates/grid.mako +++ b/apps/dashboard/xaal/dashboard/templates/grid.mako @@ -1,90 +1,99 @@ <%inherit file="base.mako"/> <%namespace name="widgets" file="widgets.mako" /> +<link href="../static/css/btns.css" rel="stylesheet"> + <div class="grid-background"> <div class="grid"> <div class="grid-box"> - <div style="text-align:center;"> - ${widgets.lamp('lamp_entree')} - ${widgets.lamp('lamp_couloir')} - </div> + <b>Eclairage</b> + ${widgets.list_devices(['lamp_entree','lamp_couloir','lamp_salon'])} </div> <div class="grid-box"> - <div style="text-align:center;"> - ${widgets.lamp('lamp_salon')} - ${widgets.lamp('lamp_salle')} - </div> + <b>Eclairage</b> + ${widgets.list_devices(['lamp_salle','lamp_cuisine','lamp_sdb','relay_spot'])} </div> <div class="grid-box"> - <div style="text-align:center;"> - ${widgets.lamp('lamp_cuisine')} - ${widgets.lamp('lamp_sdb')} - </div> + <b>Relais</b> + ${widgets.list_devices(['relay_pepper','relay_bouilloire'])} </div> <div class="grid-box"> <div style="text-align:center;"> - <b>Volet cuisine</b> - <span data-is="shutter" xaal_addr="2fe70f46-3ece-44d1-af34-2d82e10fb854"></span> + ${widgets.shutter('shutter_sdb')} </div> </div> <div class="grid-box"> <div style="text-align:center;"> - <b>Volet SDB</b> - <span data-is="shutter" xaal_addr="e4b05165-be5d-46d5-acd0-4da7be1158ed"></span> + ${widgets.shutter('shutter_cuisine')} </div> </div> - <div class="grid-box"> - <div style="text-align:center;"> - ${widgets.lamp('lamp_test')} - </div> + <b>Température</b> + ${widgets.list_devices(['temp_netatmo','temp_owm','temp_bureau',])} </div> <div class="grid-box"> - <b>Température</b> - ${widgets.list_thermometer(['temp_owm','temp_bureau'])} + <b>Humidité</b> + ${widgets.list_devices(['rh_netatmo','rh_owm','rh_bureau'])} </div> + <div class="grid-box"> - <b>Humidité</b> - ${widgets.list_hygrometer(['rh_owm','rh_bureau'])} + <b>Aquara</b> + ${widgets.list_devices(['temp_aqara1','rh_aqara1','press_aqara1'])} + ${widgets.list_devices(['temp_aqara2','rh_aqara2','press_aqara2'])} </div> - <div class="grid-box"> - <div style="text-align:center;"> - <b>Wall Plug</b> - <span data-is="powerrelay" xaal_addr="5e50a1ed-5290-4cdb-b00f-1f968eee4401"></span> - <br/> - <span data-is="powermeter" xaal_addr="5e50a1ed-5290-4cdb-b00f-1f968eee4402"></span> + + <div class="grid-box"> + <b>Ouverture</b> + ${widgets.list_devices(['door1','door2','door3','door4'])} </div> - </div> -<div class="grid-box"> + + + <div class="grid-box"> + <b>Consommations</b> + ${widgets.list_devices(['pmeter_spot','pmeter_robot','pmeter_bouilloire'])} + </div> + + <div class="grid-box"> + <b>Mouvement</b> + ${widgets.list_devices(['motion1','motion2','motion3','motion4','motion5'])} + </div> + + <div class="grid-box"> + <b>CO2</b> + ${widgets.list_devices(['co2_1','co2_2','co2_3','co2_4'])} + </div> + + + +<!-- +<div class="grid-box two"> <div style="text-align:center;"> - <b>Test1</b> - <span data-is="lamp" xaal_addr="dde4ab86-076f-11e8-b7ec-00fec8f71301"></span> - <span data-is="lamp" xaal_addr="0a238b82-0760-11e8-b576-00fec8f71301"></span> + <iframe src="https://aal.enstb.org/grafana/d-solo/mNvuqkJmz/xaal-lab?refresh=5s&panelId=24&orgId=1" width="310" height="100" frameborder="0"></iframe> + <iframe src="https://aal.enstb.org/grafana/d-solo/mNvuqkJmz/xaal-lab?refresh=1m&panelId=27&orgId=1" width="310" height="100" frameborder="0"></iframe> </div> </div> +--> - <div class="grid-box two"> - <div data-is="generic-attrs" xaal_addr="7b81512c-0a96-11e8-ad38-3c77e618c6f7"></div> - </div> - + <!-- <div class="grid-box two" style="align:center;"> - <!-- img src="http://10.77.3.51/video3.mjpg" width=250 --> + <img src="http://10.77.3.51/video3.mjpg" width=250> </div> - + --> + <div class="grid-box" style="text-align:center;"> <br/><br/><br/> <span data-is="clock"/> @@ -94,13 +103,18 @@ </div> <!-- end of grid --> </div><!-- end of grib background --> -<script type="riot/tag" src="/static/tags/powerrelay.tag"></script> -<script type="riot/tag" src="/static/tags/hygrometer.tag"></script> -<script type="riot/tag" src="/static/tags/thermometer.tag"></script> -<script type="riot/tag" src="/static/tags/powermeter.tag"></script> -<script type="riot/tag" src="/static/tags/lamp.tag"></script> -<script type="riot/tag" src="/static/tags/shutter.tag"></script> +<script type="riot/tag" src="../static/tags/powerrelay.tag"></script> +<script type="riot/tag" src="../static/tags/hygrometer.tag"></script> +<script type="riot/tag" src="../static/tags/thermometer.tag"></script> +<script type="riot/tag" src="../static/tags/powermeter.tag"></script> +<script type="riot/tag" src="../static/tags/lamp.tag"></script> +<script type="riot/tag" src="../static/tags/shutter.tag"></script> +<script type="riot/tag" src="../static/tags/barometer.tag"></script> +<script type="riot/tag" src="../static/tags/co2meter.tag"></script> +<script type="riot/tag" src="../static/tags/motion.tag"></script> +<script type="riot/tag" src="../static/tags/door.tag"></script> + -<script type="riot/tag" src="/static/tags/generic_attrs.tag"></script> -<script type="riot/tag" src="/static/tags/clock.tag"></script> +<script type="riot/tag" src="../static/tags/generic_attrs.tag"></script> +<script type="riot/tag" src="../static/tags/clock.tag"></script> diff --git a/apps/dashboard/xaal/dashboard/templates/menu.mako b/apps/dashboard/xaal/dashboard/templates/menu.mako index 05de5d5934a30a0890ccf0d82221360dff4a2fa1..2ddf696d9dabbf4a341a02d0208646d7263347f6 100644 --- a/apps/dashboard/xaal/dashboard/templates/menu.mako +++ b/apps/dashboard/xaal/dashboard/templates/menu.mako @@ -1,12 +1,19 @@ - <!-- Menu --> +<% + menu = [('devices' ,'Eléments'), + ('grid' ,'Général'), + ] +%> + +<!-- Menu --> <ul class="menu"> <li><a href="#" onclick="openNav()">☰</a></li> -% for item in menu.get(): - % if item.has_key('active'): - <li class="active"><a href="${item['url']}">${item['value']}</a></li> - % else: - <li><a href="${item['url']}">${item['value']}</a></li> - % endif +% for item in menu: +% if item[0] == active_menu: + <li class="active"><a href="/${item[0]}">${item[1]}</a></li> +% else: + <li><a href="/${item[0]}">${item[1]}</a></li> +% endif + % endfor </ul> - <!-- EOF Menu --> +<!-- EOF Menu --> diff --git a/apps/dashboard/xaal/dashboard/templates/widgets.mako b/apps/dashboard/xaal/dashboard/templates/widgets.mako index 532c83c871be5c1ef535d1ae19a1399bac8b6c34..076751923b8c026b7a95bbf4541b64788f64ad07 100644 --- a/apps/dashboard/xaal/dashboard/templates/widgets.mako +++ b/apps/dashboard/xaal/dashboard/templates/widgets.mako @@ -1,58 +1,117 @@ <%def name="thermometer(addr)"> -<a href="/generic/${addr}"> +<a href="./warp10/graph/${addr}"> <div data-is="thermometer" xaal_addr=${addr}></div> </a> </%def> <%def name="hygrometer(addr)"> -<a href="/generic/${addr}"> +<a href="./warp10/graph/${addr}"> <div data-is="hygrometer" xaal_addr=${addr}></div> </a> </%def> +<%def name="powerrelay(addr)"> + <div data-is="powerrelay" xaal_addr=${addr}></div> +</%def> + +<%def name="lamp_(addr)"> + <div data-is="lamp" xaal_addr=${addr}></div> +</%def> + + +<%def name="generic(addr)"> + <div data-is="generic-attrs" xaal_addr=${addr}></div> +</%def> + + +<%def name="powermeter(addr)"> +<a href="./warp10/graph/${addr}"> + <div data-is="powermeter" xaal_addr=${addr}></div> +</a> +</%def> + +<%def name="barometer(addr)"> +<a href="./warp10/graph/${addr}"> + <div data-is="barometer" xaal_addr=${addr}></div> +</a> +</%def> + +<%def name="co2meter(addr)"> +<a href="./warp10/graph/${addr}"> + <div data-is="co2meter" xaal_addr=${addr}></div> +</a> +</%def> + + +<%def name="motion(addr)"> + <div data-is="motion" xaal_addr=${addr}></div> +</%def> + +<%def name="door(addr)"> + <div data-is="door" xaal_addr=${addr}></div> +</%def> -<%def name="lamp(nickname)"> + +<%def name="shutter(nickname)"> <% dev = devices.fetch_one_kv('nickname',nickname) %> % if dev: - <b>${dev.get_kv('name')}</b><a href="./generic/${dev.address}"> </a> - <span data-is="lamp" xaal_addr=${dev.address}></span> + <b>${dev.display_name}</b> + <span data-is="shutter" xaal_addr=${dev.address}></span> % else: device not found: <b>${nickname}</b> % endif </%def> -<%def name="list_thermometer(values)"> -<table> -% for nick in values: -<% dev = devices.fetch_one_kv('nickname',nick) %> +<%! + def tag(dev): + type_ = dev.devtype + if type_.startswith('thermometer.') : return 'thermometer' + if type_.startswith('hygrometer.') : return 'hygrometer' + if type_.startswith('shutter.') : return 'shutter' + if type_.startswith('lamp.') : return 'lamp_' + if type_.startswith('powerrelay.') : return 'powerrelay' + if type_.startswith('powermeter.') : return 'powermeter' + if type_.startswith('barometer.') : return 'barometer' + if type_.startswith('co2meter.') : return 'co2meter' + if type_.startswith('motion.') : return 'motion' + if type_.startswith('door.') : return 'door' + + return 'generic' +%> + + +<%def name="device(nickname)"> +<% dev = devices.fetch_one_kv('nickname',nickname) %> % if dev: -<tr> - <td>${dev.get_kv('name')}</td> - <td> - <a href="./generic/${dev.address}"> - <span data-is="thermometer" xaal_addr=${dev.address}> - </a> - </td> -</tr> + ${ self.template.get_def(tag(dev)).render(dev.address) } +% else: + Device not found ${nickname} +% endif +</%def> + +<%def name="device_addr(addr)"> +<% dev = devices.get_with_addr(addr) %> +% if dev: + ${ self.template.get_def(tag(dev)).render(dev.address) } +% else: + Device not found [${addr}] % endif -% endfor -</table> </%def> -<%def name="list_hygrometer(values)"> -<table> +<%def name="list_devices(values)"> +<table width=98%> % for nick in values: <% dev = devices.fetch_one_kv('nickname',nick) %> % if dev: <tr> - <td>${dev.get_kv('name')}</td> <td> - <a href="./generic/${dev.address}"> - <span data-is="hygrometer" xaal_addr=${dev.address}> - </a> + <a href="./generic/${dev.address}">➠</a>${dev.display_name} + </td> + <td> + ${ self.template.get_def(tag(dev)).render(dev.address) } </td> </tr> % endif diff --git a/apps/tools/xaal/tools/isalive.py b/apps/tools/xaal/tools/isalive.py index 8bee1a03a4a67db1f9df0f75f0bdd3f8fd871c6a..dde7774c82b4c39529f7ab0808d149212597c3f9 100644 --- a/apps/tools/xaal/tools/isalive.py +++ b/apps/tools/xaal/tools/isalive.py @@ -49,12 +49,13 @@ class Scanner: print("="*70) self.loop() print("="*70) + print("Found %d devices" % len(self.seen)) def loop(self): t0 = time.time() while 1: self.eng.loop() - if time.time() > (t0 + 1): + if time.time() > (t0 + 2): break def parse_answer(self,msg): diff --git a/apps/tools/xaal/tools/log.py b/apps/tools/xaal/tools/log.py index feedf118ce833d5ec75a68f51074a29b7115b8c5..5b61424b8101e54cf36ebcc668663fb3f7d5ba3b 100644 --- a/apps/tools/xaal/tools/log.py +++ b/apps/tools/xaal/tools/log.py @@ -8,8 +8,8 @@ helpers.set_console_title("xaal-log") def print_evt(msg): if msg.is_alive(): return - if msg.is_attributes_change() or msg.is_notify(): - print("%s %s %s %s" % (time.ctime(),msg.source,msg.devtype,msg.body)) + if msg.is_notify(): + print("%s %s %s %s %s" % (time.ctime(),msg.source,msg.devtype,msg.action,msg.body)) def main(): diff --git a/devices/loggers/warp10/xaal/warp10/delete.py b/devices/loggers/warp10/xaal/warp10/delete.py new file mode 100644 index 0000000000000000000000000000000000000000..0c149e594055184c989019f0182b5e2376011937 --- /dev/null +++ b/devices/loggers/warp10/xaal/warp10/delete.py @@ -0,0 +1,22 @@ +from xaal.lib import config,tools +import urllib3 +import sys + + +PACKAGE_NAME = "xaal.warp10" + +def main(): + serie = sys.argv[1] + cfg = tools.load_cfg_or_die(PACKAGE_NAME)['config'] + http = urllib3.PoolManager() + DATA_BUF="" + url = '/'.join(cfg['url'].split('/')[:-1]) + '/delete?deleteall&selector=%s' % sys.argv[1] + print(url) + rsp = http.request('GET',url,headers={'X-Warp10-Token':cfg['token']},body=DATA_BUF,retries=2) + print(rsp.data) + + + +if __name__ == '__main__': + main() + diff --git a/devices/notifications/pushbullet/xaal/pushbullet/dev.py b/devices/notifications/pushbullet/xaal/pushbullet/dev.py index 9d3638d555028d8413b0bbb7d39a6f11fd3cff6b..94d165c6851b40e6be6333e87adca4058f279cc7 100644 --- a/devices/notifications/pushbullet/xaal/pushbullet/dev.py +++ b/devices/notifications/pushbullet/xaal/pushbullet/dev.py @@ -10,21 +10,22 @@ PACKAGE_NAME = "xaal.pushbullet" logger = logging.getLogger(PACKAGE_NAME) -push_bullet = None +push_bullets = [] def register_device(engine): global push_bullet cfg = tools.load_cfg(PACKAGE_NAME) if not cfg: logger.info('Missing config file, building a new one') cfg = tools.new_cfg(PACKAGE_NAME) - cfg['config']['key']='' + cfg['config']['keys']='' cfg.write() - key = cfg['config'].get('key',None) - if not key: - logger.info('Please setup the Pushbullet Key') + keys = cfg['config'].get('keys',None) + if not keys: + logger.info('Please setup the Pushbullet keys') return - push_bullet = Pushbullet(key) + for k in keys: + push_bullets.append(Pushbullet(k)) info = "%s@%s" % (PACKAGE_NAME,platform.node()) dev = Device("notification.pushbullet") dev.address = cfg['config']['addr'] @@ -38,17 +39,16 @@ def register_device(engine): atexit.register(notify,info,'shutdown..') def notify(_title,_msg): - global push_bullet - if push_bullet: - try: - push_bullet.push_note(_title,_msg) - except Exception as err: - print("Pushbullet exception: {0}".format(err)) - # some error can occur on a broken link, just try to resend - time.sleep(0.5) - push_bullet.push_note(_title,_msg) - - + global push_bullets + if push_bullets != []: + for pb in push_bullets: + try: + pb.push_note(_title,_msg) + except Exception as err: + print("Pushbullet exception: {0}".format(err)) + # some error can occur on a broken link, just try to resend + time.sleep(0.5) + pb.push_note(_title,_msg) def setup(engine): diff --git a/devices/protocols/Aqara/README.rst b/devices/protocols/Aqara/README.rst index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5fd80c7b2efae79e1dc8d7eee95b1669cd7a634e 100644 --- a/devices/protocols/Aqara/README.rst +++ b/devices/protocols/Aqara/README.rst @@ -0,0 +1,32 @@ +xAAL Aqara Gateway +================== + + +Install & Config +---------------- +- Install with python setup.py develop or install. +- Enable local network on the Aqara Gateway with the Mi Home + Android or iOS app. You can follow this manual: + https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara) + store the secret key somewhere. +- Run the xAAL gateway with: python -m xaal.aqara. The gateway + should detect all Aqara devices on your local network. +- To enable quick discovery and control devices (switches, leds..) + edit the config file ~/.xaal/xaal.aqara.ini and add the key + like this: + + [devices] + [[xxxxxxxxxxxx]] + base_addr = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx + model = gateway + secret = ydt5xis45x7k5x1x + + +Notes +----- +- xAAL gateway supports more than one Aqara Gateway (hub) on the + network make sure to set a secret key to each hub. +- xAAL gateway supports most Aqara devices, feel free to edit (and + submit) xaal/aqara/devices.py if you own a unsupported device. + + diff --git a/devices/protocols/Aqara/xaal/aqara/devices.py b/devices/protocols/Aqara/xaal/aqara/devices.py index 8f5553da131e6264ff7bd4c10474e94f984080dd..208c38c244bd7e57a9b59f286d295624f88e9355 100644 --- a/devices/protocols/Aqara/xaal/aqara/devices.py +++ b/devices/protocols/Aqara/xaal/aqara/devices.py @@ -14,15 +14,17 @@ AQARA_ENCRYPT_IV = b'\x17\x99\x6d\x09\x3d\x28\xdd\xb3\xba\x69\x5a\x2e\x6f\x58\x5 logger = logging.getLogger(__name__) class AqaraDev(object): - def __init__(self,sid,model,base_addr): + def __init__(self,sid,model,base_addr,xaal_gw): self.sid = sid self.model = model self.base_addr = base_addr - self.devices = [] + self.xaal_gw = xaal_gw + # xAAL embeded devices + self.devices = [] logger.info('New AqaraDevice %s %s' % (model,sid)) self.setup() self.init_properties() - + def setup(self): logger.warning('Please overide setup()') @@ -43,6 +45,7 @@ class AqaraDev(object): if cmd in ['report','heartbeat']: pload = pkt.get('data',None) if pload: + # json in json really ? grr data = ujson.decode(pload) if cmd == 'report': self.on_report(data) @@ -55,7 +58,7 @@ class AqaraDev(object): logger.info('Unhandled report %s' % data) def on_heartbeat(self,data): - print(data) + logger.info('Unhandled heartbeat %s' % data) class Switch(AqaraDev): @@ -106,7 +109,6 @@ class Motion(AqaraDev): self.devices.append(devices.motion(self.base_addr+'00')) def on_report(self,data): - print(data) val = data.get('status',None) if val and val == 'motion': self.devices[0].attributes['presence'] = True @@ -134,24 +136,47 @@ class Vibration(AqaraDev):pass class Cube(AqaraDev):pass +class RelayController(AqaraDev):pass + +class WaterLeak(AqaraDev): + # no device yet on this + + def on_report(self,data): + status = data.get('status',None) + if status and status == 'leak': + logger.warning('Leaking..') + if status and status == 'no_leak': + logger.warning('No leaking') + class Gateway(AqaraDev): def setup(self): + self.sids = [] self.ip = None self.token = None + self.ready = False self.rgb = None + self._blink_color = 0 self.connect() lamp = devices.lamp(self.base_addr+'00') lamp.methods['on'] = self.lamp_on lamp.methods['off'] = self.lamp_off lamp.methods['rand'] = self.lamp_rand + lamp.methods['blink'] = self.lamp_blink self.devices.append(lamp) siren = Device('siren.sound',self.base_addr+'01') siren.methods['play'] = self.siren_play siren.methods['stop'] = self.siren_stop + #siren.methods['debug'] = self.debug self.devices.append(siren) + def debug(self): + import pdb;pdb.set_trace() + + #======================================================================== + ## GW Unicast methods + #======================================================================== @property def secret(self): return self._secret @@ -171,33 +196,92 @@ class Gateway(AqaraDev): def send(self,pkt): if not self.ip: - logger.warning("GW not found, please wait") + logger.warning("GW IP not found yet, please wait") return try: - print(pkt) + #print(pkt) self.sock.sendto(pkt,(self.ip,GW_PORT)) ans = self.sock.recv(65507) return ans except Exception as e: logger.warning(e) - def write(self,data = {}): + def send_cmd(self,cmd,sid,data={}): + if not self.token : + logger.warning("No token yet, please wait") + return + if not self.secret: + logger.warn("Please set the secret key in cfg file") + return key = self.make_key() data.update({"key":key}) - pload = {"cmd":"write", - "sid": self.sid, - "data" : data + pload = {"cmd": cmd, + "sid": sid, + "data": data } pkt = ujson.encode(pload).encode('utf8') return self.send(pkt) + + #======================================================================== + ## Gateway Unicast commands + #======================================================================== + def write(self,data = {}): + """send command to the GW, used for siren, lamp, radio?""" + pkt = self.send_cmd("write",self.sid,data) + self.on_receive(pkt) + + def get_id_list(self): + """ retreive the devices list""" + pkt = self.send_cmd("get_id_list",self.sid) + self.on_receive(pkt) + + def discover(self): + """ + query device list and ask for a read, on each sid + + The doc say we should use {"cmd": "discovery"} instead of get_id_list + + read, but I always have a discory error => "missing sid" + http://docs.opencloud.aqara.com/en/development/gateway-LAN-communication/ + """ + self.get_id_list() + for sid in self.sids: + pkt = self.send_cmd("read",sid) + self.on_receive(pkt) + + def on_receive(self,pkt): + if not pkt: return + + pload = ujson.decode(pkt) + cmd = pload.get('cmd',None) + + if cmd == 'get_id_list_ack': + data = pload.get('data','[]') + self.sids = ujson.decode(data) + + if cmd == 'read_ack': + model = pload.get('model',None) + sid = pload.get('sid',None) + dev = self.xaal_gw.get_device(sid,model) + if dev: + data = ujson.decode(pload.get('data','{}')) + dev.on_report(data) + else: + logger.warning("Unknow device %s" % pload) + + if cmd == 'write_ack': + print(pkt) + + + #======================================================================== ## RGB Leds + #======================================================================== def get_rgb(self,red,green,blue,brightness=0xFF): return brightness<<24|( red << 16)|( green << 8)|blue def lamp_set(self,value): data = {"rgb" : value} - print(self.write(data)) + self.write(data) def lamp_on(self): color = self.rgb or self.get_rgb(255,170,170) @@ -214,30 +298,62 @@ class Gateway(AqaraDev): rgb = self.get_rgb(red,green,blue) self.lamp_set(rgb) - ## Siren go here - def siren_play(self,_sound=26,_volume=5): + def _red(self): + self.lamp_set(self.get_rgb(255,0,0)) + + def _white(self): + self.lamp_set(self.get_rgb(255,255,255)) + + def _lamp_blink(self): + if self._blink_color ==0: + self._red() + self._blink_color = 1 + else: + self._white() + self._blink_color = 0 + + def lamp_blink(self): + eng = self.devices[0].engine + eng.add_timer(self.lamp_off,20,0) + eng.add_timer(self._lamp_blink,1,15) + + #======================================================================== + ## Siren + #======================================================================== + def siren_play(self,_sound=2,_volume=5): + logger.info('Playing %s %s' % (_sound,_volume)) data = { - "mid" : _sound, - "volume": _volume, + "mid" : int(_sound), + "volume": int(_volume), } self.write(data) def siren_stop(self): data = { "mid" : 999 } - print(self.write(data)) + self.write(data) + + #======================================================================== + ## GW Mutlicast messages handlers + #======================================================================== def parse(self,pkt): - # the Gateway need to parse the token addtionnal parameter - cmd = pkt.get('cmd',None) - if cmd == 'heartbeat': - token = pkt.get('token',None) - if token: self.token = token + # extract the token before normal parsing + token = pkt.get('token',None) + if token: + self.token = token AqaraDev.parse(self,pkt) def on_heartbeat(self,data): ip = data.get('ip',None) if ip and ip != self.ip: self.ip = ip + logger.info("GW IP found: %s" % ip) + + if not self.ready : + if self.token and self.ip: + self.ready = True + self.discover() + def on_report(self,data): rgb = data.get('rgb',None) diff --git a/devices/protocols/Aqara/xaal/aqara/gw.py b/devices/protocols/Aqara/xaal/aqara/gw.py index 0175f6ab91ed6f2dc09cf2d1a940da86211b4b28..0f8ec8d3e71909d1465922b47e7b6b3e230c8142 100644 --- a/devices/protocols/Aqara/xaal/aqara/gw.py +++ b/devices/protocols/Aqara/xaal/aqara/gw.py @@ -29,6 +29,10 @@ def find_device_class(model): return devices.Vibration if model == 'sensor_cube.aqgl01': return devices.Cube + if model == 'sensor_wleak.aq1': + return devices.RelayController + if model == 'lumi.ctrl_dualchn': + return devices.RelayController return None class GW(gevent.Greenlet): @@ -52,47 +56,54 @@ class GW(gevent.Greenlet): def add_device(self,sid,model,base_addr): klass = find_device_class(model) if klass: - dev = klass(sid,model,base_addr) + dev = klass(sid,model,base_addr,self) self.engine.add_devices(dev.devices) self.devices.update({sid:dev}) return dev else: - logger.info('Unsupported device %s/%s' % (model,sid)) + logger.info('Unsupported device [%s]/[%s]' % (model,sid)) return None def setup(self): self.aqara = AqaraConnector() - devs = self.cfg['devices'] - for sid in devs: - cfg = devs[sid] - model = cfg.get('model',None) - base_addr = cfg.get('base_addr',None) - # TBD : Merge this w/ handle new device + + def get_device(self,sid,model): + # Already running device ? + if sid in self.devices.keys(): + return self.devices[sid] + # Already known device ? + elif sid in self.cfg['devices'].keys(): + cfg = self.cfg['devices'][sid] + model_old = cfg.get('model',None) + base_addr = cfg.get('base_addr',None) + dev = None + if model != model_old: + logger.warn("Device %s wrong model" % sid) if model and base_addr: dev = self.add_device(sid,model,base_addr) if dev and model == 'gateway': - dev.secret = devs[sid].get('secret',None) - + dev.secret = cfg.get('secret',None) + return dev + # Still not found ? => new device + else: + base_addr = tools.get_random_uuid()[:-2] + dev = self.add_device(sid,model,base_addr) + if dev: + cfg = {'base_addr' : base_addr,'model':model} + self.cfg['devices'].update({sid:cfg}) + return dev + def _run(self): while 1: pkt = self.aqara.receive() if pkt: - self.handle(pkt) + self.on_receive(pkt) - def handle(self,pkt): + def on_receive(self,pkt): sid = pkt.get('sid',None) if not sid: return - dev = None - if sid in self.devices.keys(): - dev = self.devices[sid] - else: - model = pkt.get('model',None) - if not model:return - base_addr = tools.get_random_uuid()[:-2] - dev = self.add_device(sid,model,base_addr) - if dev: - cfg = {'base_addr' : base_addr,'model':model} - self.cfg['devices'].update({sid:cfg}) + model = pkt.get('model',None) + dev = self.get_device(sid,model) if dev: dev.parse(pkt) diff --git a/devices/protocols/Aqara/xaal/aqara/network.py b/devices/protocols/Aqara/xaal/aqara/network.py index ff4aceb1f6412605bad5603d69977a9fa8ce15b0..489239d331284df858fc4a3f69771e4b86d3189e 100644 --- a/devices/protocols/Aqara/xaal/aqara/network.py +++ b/devices/protocols/Aqara/xaal/aqara/network.py @@ -11,6 +11,12 @@ logger = logging.getLogger(__name__) class AqaraConnector: + """ + Aqara Device Report/Heartbeat connector, used only to receive event, if you want to send a command, + you must send a unicast datagram to the right Aqara GW. + + Please note, this isn't the Gateway discovery + """ def __init__(self): self.nc = network.NetworkConnector('224.0.0.50',9898,10) self.nc.connect() @@ -19,6 +25,7 @@ class AqaraConnector: buf = self.nc.receive() if buf: try: + #logger.warning(buf) return ujson.decode(buf) except ValueError: logger.debug('JSON decoder Error %s' % buf) diff --git a/devices/protocols/Edisio/README.rst b/devices/protocols/Edisio/README.rst index cc6964443a14368f3d0833646b5cd8c312ac5924..6f01a21e748cde214afbe9e907fed9a407a6b3da 100644 --- a/devices/protocols/Edisio/README.rst +++ b/devices/protocols/Edisio/README.rst @@ -30,4 +30,10 @@ EE = END of Data 2018-09-27 15:38:09 i7 xaal.edisio[18674] DEBUG [16] 6c 76 63 06 73 74 2b 03 01 1b 01 00 03 64 0d 0a 2018-09-27 15:38:09 i7 xaal.edisio[18674] DEBUG [16] 6c 76 63 06 73 74 2b 03 01 1b 01 00 03 64 0d 0a 2018-09-27 15:38:09 i7 xaal.edisio[18674] DEBUG [16] 6c 76 63 06 73 74 2b 03 01 1b 01 00 03 64 0d 0a -2018-09-27 15:38:09 i7 xaal.edisio[18674] DEBUG [16] 6c 76 63 06 73 74 2b 03 01 1b 01 00 03 64 0d 0a \ No newline at end of file +2018-09-27 15:38:09 i7 xaal.edisio[18674] DEBUG [16] 6c 76 63 06 73 74 2b 03 01 1b 01 00 03 64 0d 0a + +Config +====== +- Edit ~/.xaal/xaal.edisio.ini according to your needs, mainly change the serial port, by default + everything else should be Ok. + diff --git a/devices/protocols/KNX/xaal/knx/bindings.py b/devices/protocols/KNX/xaal/knx/bindings.py index e80768bf001ba061a2c841fc3df7c506e8ea69df..1e823bb419b4cae643407f39ff7727473d9a927d 100644 --- a/devices/protocols/KNX/xaal/knx/bindings.py +++ b/devices/protocols/KNX/xaal/knx/bindings.py @@ -21,16 +21,24 @@ def on_off(attribute,dpt,data): def round_(attribute,dpt,data): val = dpts.decode[dpt](data) - attribute.value = round(val) + if val!=None: + attribute.value = round(val) def set_(attribute,dpt,data): val = dpts.decode[dpt](data) - attribute.value = val + if val!=None: + attribute.value = val +def mul1000_(attribute,dpt,data): + val = dpts.decode[dpt](data) + if val!=None: + attribute.value = round(val * 1000) + funct = { "bool" : bool_, "bool_inv" : bool_inv, + "on_off" : on_off, "round" : round_, "set" : set_, - "on_off" : on_off, -} \ No newline at end of file + "mul1000" : mul1000_, +} diff --git a/devices/protocols/KNX/xaal/knx/devices.py b/devices/protocols/KNX/xaal/knx/devices.py index 2a02e2f518d91f307c4e3abf392684d4b34db027..c4d8a41fa2d651d9ec855cd23571457168699c83 100644 --- a/devices/protocols/KNX/xaal/knx/devices.py +++ b/devices/protocols/KNX/xaal/knx/devices.py @@ -22,7 +22,6 @@ class KNXDev: self.dev = None self.setup() - def setup(self): logger.warn("Please define setup() in this device") @@ -82,7 +81,34 @@ class Switch(KNXDev): self.bind_attribute(self.dev.get_attribute('position'),state,funct['bool'],'1') self.dev.info = "KNX %s" % state +class Shutter(KNXDev): + def setup(self): + self.dev = devices.shutter(self.addr) + up_down = self.cfg.get('updown_cmd',None) + stop = self.cfg.get('stop_cmd',None) + if up_down: + self.dev.add_method('up',self.write(up_down,'1',0)) + self.dev.add_method('down',self.write(up_down,'1',1)) + if stop: + self.dev.add_method('stop',self.write(stop,'1',1)) + +class Shutter_position(KNXDev): + def setup(self): + self.dev = devices.shutter_position(self.addr) + up_down = self.cfg.get('updown_cmd',None) + stop = self.cfg.get('stop_cmd',None) + position = self.cfg.get('position_cmd',None) + if up_down: + self.dev.add_method('up',self.write(up_down,'1',0)) + self.dev.add_method('down',self.write(up_down,'1',1)) + if stop: + self.dev.add_method('stop',self.write(stop,'1',1)) + if position: + self.dev.add_method('position',self.write(position,'1',1)) + + + # ============================================================================= # Sensors # ============================================================================= @@ -90,15 +116,78 @@ class PowerMeter(KNXDev): def setup(self): self.dev = devices.powermeter(self.addr) self.dev.unsupported_attributes = ['devices'] + self.dev.del_attribute(self.dev.get_attribute('devices')) power = self.cfg.get('power',None) p_dpt = self.cfg.get('power_dpt','9') p_mod = self.cfg.get('power_mod','set') if power: self.bind_attribute(self.dev.get_attribute('power'),power,funct[p_mod],p_dpt) + else: + self.dev.del_attribute(self.dev.get_attribute('power')) energy = self.cfg.get('energy',None) - e_dpt = self.cfg.get('energy_dpt','9') + e_dpt = self.cfg.get('energy_dpt','13') e_mod = self.cfg.get('energy_mod','set') if energy: self.bind_attribute(self.dev.get_attribute('energy'),energy,funct[e_mod],e_dpt) + else: + self.dev.del_attribute(self.dev.get_attribute('energy')) self.dev.info = "KNX %s" % (power or energy) - \ No newline at end of file + +class Thermometer(KNXDev): + def setup(self): + self.dev = devices.thermometer(self.addr) + temperature = self.cfg.get('temperature',None) + t_dpt = self.cfg.get('temperature_dpt','9') + t_mod = self.cfg.get('temperature_mod','set') + if temperature: + self.bind_attribute(self.dev.get_attribute('temperature'),temperature,funct[t_mod],t_dpt) + self.dev.info = "KNX %s" % temperature + +class Hygrometer(KNXDev): + def setup(self): + self.dev = devices.hygrometer(self.addr) + humidity = self.cfg.get('humidity',None) + h_dpt = self.cfg.get('humidity_dpt','5.001') + h_mod = self.cfg.get('humidity_mod','round') + if humidity: + self.bind_attribute(self.dev.get_attribute('humidity'),humidity,funct[h_mod],h_dpt) + self.dev.info = "KNX %s" % humidity + +class CO2Meter(KNXDev): + def setup(self): + self.dev = devices.co2meter(self.addr) + co2 = self.cfg.get('co2',None) + c_dpt = self.cfg.get('co2_dpt','9') + c_mod = self.cfg.get('co2_mod','round') + if co2: + self.bind_attribute(self.dev.get_attribute('co2'),co2,funct[c_mod],c_dpt) + self.dev.info = "KNX %s" % co2 + +class Luxmeter(KNXDev): + def setup(self): + self.dev = devices.luxmeter(self.addr) + illuminance = self.cfg.get('illuminance',None) + l_dpt = self.cfg.get('illuminance_dpt','7') + l_mod = self.cfg.get('illuminance_mod','set') + if illuminance: + self.bind_attribute(self.dev.get_attribute('illuminance'),illuminance,funct[l_mod],l_dpt) + self.dev.info = "KNX %s" % illuminance + +class Lightgauge(KNXDev): + def setup(self): + self.dev = devices.lightgauge(self.addr) + brightness = self.cfg.get('brightness',None) + b_dpt = self.cfg.get('brightness_dpt','5.001') + b_mod = self.cfg.get('brightness_mod','round') + if brightness: + self.bind_attribute(self.dev.get_attribute('brightness'),brightness,funct[b_mod],b_dpt) + self.dev.info = "KNX %s" % brightness + +class Motion(KNXDev): + def setup(self): + self.dev = devices.motion(self.addr) + state = self.cfg.get('state',None) + if state: + self.bind_attribute(self.dev.get_attribute('presence'),state,funct['bool'],'1') + self.dev.info = "KNX %s" % state + diff --git a/devices/protocols/KNX/xaal/knx/knxrouter.py b/devices/protocols/KNX/xaal/knx/knxrouter.py index c949879ef9716f9f47d34b857fe001dfd4ecdcc1..f01965793ed2283902a4c9b56c6476a3c415e8b4 100644 --- a/devices/protocols/KNX/xaal/knx/knxrouter.py +++ b/devices/protocols/KNX/xaal/knx/knxrouter.py @@ -49,7 +49,8 @@ class KNXcEMI: def parse(self,buf): bytes_ = bytearray(buf) - if len(bytes_) < 17: + #MC if len(bytes_) < 17: + if len(bytes_) < 16: raise KNXcEMIException("Packet to short %d" % len(bytes_)) self.pkt = bytes_ @@ -70,13 +71,15 @@ class KNXcEMI: #self.data # 0x81 => write 1 / 0x80=> write 0 / 0x0 => read / 0x40 => response def check_sanity(self): - if self.header_lenght != 0x6 : raise KNXcEMIException("Wrong header size") - if self.version != 0x10: raise KNXcEMIException("Wrong version") - if self.service != bytearray([0x05,0x30]): raise KNXcEMIException("Wrong service") - if self.msg_code != 0x29 : raise KNXcEMIException("Wrong msg_code") - if self.ctr_field1 != 0xbc : raise KNXcEMIException("Wrong ctr_field1") - if self.ctr_field2 not in [0xe0,0xd0,0xc0]: raise KNXcEMIException("Wrong ctr_field2 0x%x" % self.ctr_field2) - #if self.npdu_lenght != 0x01 : raise KNXcEMIException("Wrong npdu_lenght") + if self.header_lenght != 0x06 : raise KNXcEMIException("Wrong header size 0x%x" % self.header_lenght) + if self.version != 0x10: raise KNXcEMIException("Wrong version 0x%x" % self.version) + if self.service != bytearray([0x05,0x30]): raise KNXcEMIException("Wrong service 0x%x 0x%x" % self.service[0] % self.service[1]) + if self.msg_code != 0x29 : raise KNXcEMIException("Wrong msg_code 0x%x" % self.msg_code) + if self.ctr_field1 not in [0xbc,0xb0] : raise KNXcEMIException("Wrong ctr_field1 0x%x" % self.ctr_field1) + # JKX: ctr_field2 shoub in [0xe0,0xd0,0xc0] but Mael fix that to: + if self.ctr_field2 not in [0xe0,0xd0,0xc0,0xb0,0x40,0x50]: raise KNXcEMIException("Wrong ctr_field2 0x%x" % self.ctr_field2) + # JKX : how could this arrive ? + #if self.npdu_lenght != 0x01 : raise KNXcEMIException("Wrong npdu_lenght 0x%x" % self.npdu_lenght) @property def total_lenght(self): diff --git a/devices/protocols/ZWave/README.rst b/devices/protocols/ZWave/README.rst index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f89279c53cad5ac2fd3c07f5496bd37250efd9af 100644 --- a/devices/protocols/ZWave/README.rst +++ b/devices/protocols/ZWave/README.rst @@ -0,0 +1,18 @@ +xAAL Zwave Gateway +================== + +Install & Config +---------------- +- Install the gateway as usual, python setup.py develop (or install) +- Plug the Zstick +- Run the gateway w/ python -m xaal.zwave +- Change the serial port in the config file xaal.zwave.ini if needed. +- The gateway will detect all paired products with the Zstick so, no + addtionnal config is needed + +Products +-------- +- Supported products are in products/, feel free to add (and submit) + your own devices. + + diff --git a/devices/protocols/ZWave/doc/Z-Stick_2E_manual_-_Aeotec_2.pdf b/devices/protocols/ZWave/doc/Z-Stick_2E_manual_-_Aeotec_2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..619bfcd386e986e884d44ca3f540053140dc2770 Binary files /dev/null and b/devices/protocols/ZWave/doc/Z-Stick_2E_manual_-_Aeotec_2.pdf differ diff --git a/devices/protocols/ZWave/xaal/zwave/debug.py b/devices/protocols/ZWave/xaal/zwave/debug.py new file mode 100644 index 0000000000000000000000000000000000000000..f17a4c0704b076fd3db27809c40c815248396dcc --- /dev/null +++ b/devices/protocols/ZWave/xaal/zwave/debug.py @@ -0,0 +1,7 @@ +from xaal.lib import Engine +from . import gw + +eng = Engine() +zw = gw.GW(eng) +import pdb;pdb.set_trace() + diff --git a/devices/protocols/ZWave/xaal/zwave/gw.py b/devices/protocols/ZWave/xaal/zwave/gw.py index 568646db6d03b4fa2a5ead0053b3b9b0e6d18967..5d7c2c5e85bbb51f668f6f7241f58d3c12cb3081 100644 --- a/devices/protocols/ZWave/xaal/zwave/gw.py +++ b/devices/protocols/ZWave/xaal/zwave/gw.py @@ -161,5 +161,6 @@ class GW(object): def setup(eng): try: gw=GW(eng) + return True except ZWaveException as err: logger.warning('Error w/ Zwave network: %s' % err.value) diff --git a/devices/protocols/ZWave/xaal/zwave/products/Aeotec/ZW100.py b/devices/protocols/ZWave/xaal/zwave/products/Aeotec/ZW100.py index 468c63644c71ba2affc7e12147412e95ad33a7ca..9f57b7d50143062b285d647f3f7294a0d4bc97db 100644 --- a/devices/protocols/ZWave/xaal/zwave/products/Aeotec/ZW100.py +++ b/devices/protocols/ZWave/xaal/zwave/products/Aeotec/ZW100.py @@ -16,11 +16,11 @@ class ZW100(core.ZDevice): dev.new_attribute('humidity') # luxmeter dev = self.new_device("luxmeter.basic") - dev.new_attribute('lux') + dev.new_attribute('illuminance') dev.new_attribute('ultraviolet') # motion sensor dev = self.new_device("motion.basic") - dev.new_attribute("motion") + dev.new_attribute("presence") # shock sensor dev = self.new_device("shock.basic") dev.new_attribute("shock") @@ -45,17 +45,17 @@ class ZW100(core.ZDevice): self.devices[1].attributes['humidity'] = round(value.data) # luxmeter if value == self.get_value('lux'): - self.devices[2].attributes['lux'] = round(value.data) + self.devices[2].attributes['illuminance'] = round(value.data) if value == self.get_value('ultraviolet'): self.devices[2].attributes['ultraviolet'] = round(value.data) # motion & shock if value == self.get_value('burglar'): if value.data == 8: - self.devices[3].attributes['motion'] = True + self.devices[3].attributes['presence'] = True if value.data == 3: self.devices[4].attributes['shock'] = True if value.data == 0: - self.devices[3].attributes['motion'] = False + self.devices[3].attributes['presence'] = False self.devices[4].attributes['shock'] = False # battery if value == self.get_value('battery'): diff --git a/devices/protocols/ZWave/xaal/zwave/products/Zipato/RGBBulb.py b/devices/protocols/ZWave/xaal/zwave/products/Zipato/RGBBulb.py index 6727c3838fe60cf67f6cfa82e9354ca4f12d198f..60bbc417e435251369fbcccc75495bd8e0349233 100644 --- a/devices/protocols/ZWave/xaal/zwave/products/Zipato/RGBBulb.py +++ b/devices/protocols/ZWave/xaal/zwave/products/Zipato/RGBBulb.py @@ -14,7 +14,10 @@ class RGBBulb(core.ZDevice): lamp.add_method('on',self.on) lamp.add_method('off',self.off) lamp.add_method('dim',self.dim) + lamp.add_method('blink',self.blink) self.monitor_value('level',core.COMMAND_CLASS.SWITCH_MULTILEVEL) + self.monitor_value('color',core.COMMAND_CLASS.ZIP_ADV_SERVER,1) + self._blink_color = 0 def set_level(self,value): self.set_value('level',value) @@ -26,9 +29,36 @@ class RGBBulb(core.ZDevice): def off(self): self.set_level(0) - def dim(self,value): - if (value > 0) and (value <100): - self.set_level(value) + def dim(self,_value): + val = int(_value) + if (val > 0) and (val <100): + self.set_level(val) + + def red(self): + self.get_value('color').data = '#FF00000000' + + def white(self): + self.get_value('color').data = '#000000FFFF' + + def _blink(self): + # turn on, but avoid the timer.. + if self._blink_color == 0: + self.red() + self._blink_color = 1 + else: + self.white() + self._blink_color = 0 + + def blink(self): + eng = self.gw.engine + state = self.devices[0].attributes["light"] + #self.set_value('level',0x63) + self.on() + eng.add_timer(self.white,19,1) + if state == False: + eng.add_timer(self.off,20,1) + self.gw.engine.add_timer(self._blink,1,15) + def handle_value_changed(self,value): if value == self.get_value('level'): @@ -38,3 +68,4 @@ class RGBBulb(core.ZDevice): dev.attributes["light"] = False else: dev.attributes["light"] = True + diff --git a/devices/protocols/ZWave/xaal/zwave/test_gw.py b/devices/protocols/ZWave/xaal/zwave/test_gw.py index 91ea3f84a34eabb39c7f9363167800fc15f3dfd2..2586feda151cb173f843ee361aa0c35b1eef4189 100644 --- a/devices/protocols/ZWave/xaal/zwave/test_gw.py +++ b/devices/protocols/ZWave/xaal/zwave/test_gw.py @@ -10,8 +10,8 @@ from pydispatch import dispatcher import sys import time -from cmdclass import COMMAND_CLASS -import auto +from .cmdclass import COMMAND_CLASS +#import auto #device="/dev/ttyACM0" device="/dev/ttyUSB0" diff --git a/libs/lib/xaal/lib/core.py b/libs/lib/xaal/lib/core.py index 12aeba38d10d813c35cd49566488a0c1ab86188d..1b84b912fec716a88e1007377d2a7d81b5592213 100644 --- a/libs/lib/xaal/lib/core.py +++ b/libs/lib/xaal/lib/core.py @@ -259,13 +259,13 @@ class Engine(object): ##################################################### # timers ##################################################### - def add_timer(self,func,period,repeat=-1): + def add_timer(self,func,period,counter=-1): """ func: function to call period: period in second - repeat: number of repeat, -1 => always + counter: number of repeat, -1 => always """ - t = Timer(func,period,repeat) + t = Timer(func,period,counter) self.timers.append(t) return t @@ -287,9 +287,9 @@ class Engine(object): t.func() except CallbackError as e: logger.error(e.description) - if (t.repeat != -1): - t.repeat = t.repeat-1 - if t.repeat == 0: + if (t.counter != -1): + t.counter = t.counter-1 + if t.counter == 0: expire_list.append(t) t.deadline = now + t.period # delete expired timers @@ -414,9 +414,9 @@ def get_args_method(method): class Timer(object): - def __init__(self,func,period,repeat): + def __init__(self,func,period,counter): self.func = func self.period = period - self.repeat = repeat - self.deadline = 0 + self.counter = counter + self.deadline = time.time() + period diff --git a/scripts/btn_relay.py b/scripts/btn_relay.py index aaf9ef139943832828a885ff2ccf44778df819ab..488b4d7f85bafe8587a2f12ececaf75689f0e89d 100644 --- a/scripts/btn_relay.py +++ b/scripts/btn_relay.py @@ -11,6 +11,13 @@ BTN1 = 'ec069c08-92af-11e8-80cd-408d5c18c800' BTN2 = '6fa87ef2-9975-11e8-b1fa-82ed25e6aa00' BTN3 = '821c6026-92ae-11e8-82af-408d5c18c800' +BTN1 = '63b5ece6-c266-11e8-a0fc-400074bcb601' +BTN4 = '63b5ece6-c266-11e8-a0fc-400074bcb605' +BTN5 = 'c38e3f04-92b1-11e8-85ee-408d5c18c800' +BTN3 = '6fa87ef2-9975-11e8-b1fa-82ed25e6aa00' + +RGB1 = 'b97c687c-d700-11e8-b0df-408d5c18c8f7' + dev = None def send(targets,action,body=None): @@ -18,22 +25,42 @@ def send(targets,action,body=None): engine = dev.engine engine.send_request(dev,targets,action,body) +def rand_color_code(): + import random + r = random.randint(0,255) + g = random.randint(0,255) + b = random.randint(0,255) + color='#%x%x%x' % (r,g,b) + return color + def handle_msg(msg): if not msg.is_notify(): return # search for the buttons + """ if msg.action == 'click': - if msg.source == BTN2: + if msg.source == BTN4: send([REL1,REL2],'toggle') - if msg.source == BTN1: + if msg.source in [BTN1,BTN5]: send([REL1],'toggle') if msg.source == BTN3: - send([REL2,],'toggle') - if msg.action == 'double_click': - if msg.source in [BTN1,BTN3]: - send([REL1,REL2],'off') + send([REL2,],'toggle') + """ + if msg.action == 'click': + send([RGB1,],'toggle') + + if msg.action == 'click': + if msg.source in [BTN1,BTN3,BTN5]: + #send([REL1,REL2],'toggle') + #send([RGB1,],'setWhite',{'target':'5000'}) + send([RGB1,],'toggle') + + if msg.source == '6fa87ef2-9975-11e8-b1fa-82ed25e6aa01': + send([RGB1,],'setRGB',{'target':rand_color_code()}) + if msg.source == '6fa87ef2-9975-11e8-b1fa-82ed25e6aa02': + send([RGB1,],'off') def main(): global dev diff --git a/scripts/ensibs_alarm_SDB_haut.py b/scripts/ensibs_alarm_SDB_haut.py new file mode 100644 index 0000000000000000000000000000000000000000..5c5d3fdd160bf35203406d799f5b91c7fe676a8b --- /dev/null +++ b/scripts/ensibs_alarm_SDB_haut.py @@ -0,0 +1,162 @@ +from xaal.lib import Device,Engine,tools,Message,helpers +from xaal.monitor import Monitor,Notification +import platform +import time +import logging +from enum import Enum + + +DELAY = 30 + +ADDR = 'aa4d1cbc-92af-11e8-80cd-408d5c18c801' +PKG_NAME = 'scenario_ensibs_alerte' + +DOOR = 'ce91c3a6-20b1-11e9-a250-a4badbf92500' +MVT = '93e09033-708e-11e8-956e-00fec8f7138c' +LIGHTS = ['93e09007-708e-11e8-956e-00fec8f7138c','93e09008-708e-11e8-956e-00fec8f7138c'] + +BULLET = ['6eb64b73-6e51-11e9-8f96-00fec8f7138c'] +BLINKS = ['aa8cd2e4-8c5d-11e9-b0ba-b827ebe99201','980a639c-20b1-11e9-8d70-a4badbf92500'] +SIREN = ['980a639c-20b1-11e9-8d70-a4badbf92501',] + + +MONITORING_DEVICES = [DOOR,LIGHTS,MVT] + +class States(Enum): + free = 'free' + busy = 'busy' + fail = 'fail' + no_motion = 'no_motion' + alarm = 'alarm' + + +class Devices: + def __init__(self): + self.door = None + self.light0 = None + self.light1 = None + self.motion = None + + @property + def list(self): + return [self.door,self.light0,self.light1,self.motion] + + def check(self): + for k in self.list: + if k == None: return False + if len(k.attributes.keys()) == 0: return False + return True + + def used(self,dev): + for k in self.list: + if k==dev: return True + return False + +logger = logging.getLogger(PKG_NAME) +device = None +devices = Devices() +state = States.fail +motion_timer = 0 + +def send(targets,action,body=None): + global device + device.engine.send_request(device,targets,action,body) + +def alert(): + logger.warning('WARNING !!!!') + send(BULLET,'notify',{'title':'Alarme SDB haut !!','msg':"Personne inerte ou lumière oubliée"}) + send(BLINKS,'blink') + send(SIREN,'play') + + +def update_state(): + global state,device + device.attributes['state'] = state.value + + +def is_light(): + if devices.light0.attributes['light']: return True + if devices.light1.attributes['light']: return True + return False + +def on_event(event,dev): + global state,motion_timer + if event == Notification.new_device: + if dev.address == DOOR : devices.door = dev + if dev.address == MVT : devices.motion = dev + if dev.address == LIGHTS[0] : devices.light0 = dev + if dev.address == LIGHTS[1] : devices.light1 = dev + + + if event == Notification.attribute_change: + if devices.check() == False: + state = States.fail + update_state() + return + + if state == States.fail: + state = States.free + update_state() + + logger.info(dev.attributes) + if dev == devices.door: + # close the door + if dev.attributes['position'] == False: + # mvt + light => busy + if state == States.free and devices.motion.attributes['presence'] == True and is_light() == True: + state = States.busy + # no light => free + if is_light() == False: + state = States.free + else: + # somebody open the door + if state in [States.busy,States.alarm]: + state = States.free + + if dev == devices.motion: + # no motion while busy => start timer + if dev.attributes['presence']==False and state == States.busy: + motion_timer = time.time() + state = States.no_motion + # motion while state no_motion => just busy + if dev.attributes['presence'] == True and state == States.no_motion: + state = States.busy + update_state() + + +def update(): + global state,motion_timer,device + now = time.time() + if state == States.no_motion: + if now > (motion_timer + DELAY): + logger.warning('ALARME !!!') + alert() + state = States.alarm + device.attributes['state'] = state.value + + +def filter_msg(msg): + if msg.source in MONITORING_DEVICES: + return True + return False + + +def main(): + global mon,device + device = Device('scenario.basic',ADDR) + device.new_attribute('state') + device.info = '%s@%s' % (PKG_NAME,platform.node()) + engine = Engine() + engine.add_device(device) + engine.add_timer(update,1) + mon = Monitor(device,filter_func = filter_msg) + mon.subscribe(on_event) + engine.run() + +if __name__ == '__main__': + try: + helpers.setup_console_logger() + main() + except KeyboardInterrupt: + print('Bye bye') + diff --git a/scripts/ensibs_alarm_WC_haut.py b/scripts/ensibs_alarm_WC_haut.py new file mode 100644 index 0000000000000000000000000000000000000000..054392eae78bef5c0f7e9499dc79bfbbc7867e3a --- /dev/null +++ b/scripts/ensibs_alarm_WC_haut.py @@ -0,0 +1,154 @@ +from xaal.lib import Device,Engine,tools,Message,helpers +from xaal.monitor import Monitor,Notification +import platform +import time +import logging +from enum import Enum + + +DELAY = 30 + +ADDR = 'aa4d1cbc-92af-11e8-80cd-408d5c18c800' +PKG_NAME = 'scenario_ensibs_alerte' + +DOOR = 'cbdb198c-20b1-11e9-a250-a4badbf92500' +MVT = '93e09031-708e-11e8-956e-00fec8f7138c' +LIGHT = '93e09005-708e-11e8-956e-00fec8f7138c' + +BULLET= ['6eb64b73-6e51-11e9-8f96-00fec8f7138c'] +BLINKS = ['aa8cd2e4-8c5d-11e9-b0ba-b827ebe99201','980a639c-20b1-11e9-8d70-a4badbf92500'] +SIREN = ['980a639c-20b1-11e9-8d70-a4badbf92501',] + + +MONITORING_DEVICES = [DOOR,LIGHT,MVT] + +class States(Enum): + free = 'free' + busy = 'busy' + fail = 'fail' + no_motion = 'no_motion' + alarm = 'alarm' + + +class Devices: + def __init__(self): + self.door = None + self.light = None + self.motion = None + + @property + def list(self): + return [self.door,self.light,self.motion] + + def check(self): + for k in self.list: + if k == None: return False + if len(k.attributes.keys()) == 0: return False + return True + + def used(self,dev): + for k in self.list: + if k==dev: return True + return False + +logger = logging.getLogger(PKG_NAME) +device = None +devices = Devices() +state = States.fail +motion_timer = 0 + +def send(targets,action,body=None): + global device + device.engine.send_request(device,targets,action,body) + +def alert(): + logger.warning('WARNING !!!!') + send(BULLET,'notify',{'title':'Alarme WC haut !!','msg':"Personne inerte ou lumière oubliée"}) + send(BLINKS,'blink') + send(SIREN,'play') + + +def update_state(): + global state,device + device.attributes['state'] = state.value + + +def on_event(event,dev): + global state,motion_timer + if event == Notification.new_device: + if dev.address == DOOR: devices.door = dev + if dev.address == MVT : devices.motion = dev + if dev.address == LIGHT: devices.light = dev + + if event == Notification.attribute_change: + if devices.check() == False: + state = States.fail + update_state() + return + + if state == States.fail: + state = States.free + update_state() + + logger.info(dev.attributes) + if dev == devices.door: + # close the door + if dev.attributes['position'] == False: + # mvt + light => busy + if state == States.free and devices.motion.attributes['presence'] == True and devices.light.attributes['light'] == True: + state = States.busy + # no light => free + if devices.light.attributes['light'] == False: + state = States.free + else: + # somebody open the door + if state in [States.busy,States.alarm]: + state = States.free + + if dev == devices.motion: + # no motion while busy => start timer + if dev.attributes['presence']==False and state == States.busy: + motion_timer = time.time() + state = States.no_motion + # motion while state no_motion => just busy + if dev.attributes['presence'] == True and state == States.no_motion: + state = States.busy + update_state() + + +def update(): + global state,motion_timer,device + now = time.time() + if state == States.no_motion: + if now > (motion_timer + DELAY): + logger.warning('ALARME !!!') + alert() + state = States.alarm + device.attributes['state'] = state.value + + +def filter_msg(msg): + if msg.source in MONITORING_DEVICES: + return True + return False + + +def main(): + global mon,device + device = Device('scenario.basic',ADDR) + device.new_attribute('state') + device.info = '%s@%s' % (PKG_NAME,platform.node()) + engine = Engine() + engine.add_device(device) + engine.add_timer(update,1) + mon = Monitor(device,filter_func = filter_msg) + mon.subscribe(on_event) + engine.run() + +if __name__ == '__main__': + try: + helpers.setup_console_logger() + main() + except KeyboardInterrupt: + print('Bye bye') + diff --git a/scripts/ensibs_btn.py b/scripts/ensibs_btn.py new file mode 100644 index 0000000000000000000000000000000000000000..a566758b506f890c82bc806c64fac60a980ee43b --- /dev/null +++ b/scripts/ensibs_btn.py @@ -0,0 +1,113 @@ +from xaal.lib import Engine +from xaal.schemas import devices +from xaal.monitor import Monitor +import platform + +PKG_NAME = 'scenario_btn_ensibs' + +LAMPS=['aa8cd2e4-8c5d-11e9-b0ba-b827ebe99201'] + +# Edisio labo +EDISIO=['43c06446-8c5c-11e9-a105-b80673742b01','43c06446-8c5c-11e9-a105-b80673742b03','43c06446-8c5c-11e9-a105-b80673742b05', + '43c06446-8c5c-11e9-a105-b80673742b07','43c06446-8c5c-11e9-a105-b80673742b08'] + +# Aquara sw1 +AQUARA_1=['fb1f8648-20ba-11e9-b352-a4badbf92500','fb1f8648-20ba-11e9-b352-a4badbf92501','fb1f8648-20ba-11e9-b352-a4badbf92502'] +AQUARA_2=['00796898-20bb-11e9-b352-a4badbf92500','00796898-20bb-11e9-b352-a4badbf92501','00796898-20bb-11e9-b352-a4badbf92502'] +AQUARA_R= '1ec6bbd0-20b5-11e9-b352-a4badbf92500' + +SHUTERS=['e4b05165-be5d-46d5-acd0-4da7be1158ed','2fe70f46-3ece-44d1-af34-2d82e10fb854'] + +SIREN = ['980a639c-20b1-11e9-8d70-a4badbf92501',] +PUSH_BULLET=['bc5bb184-8d16-11e9-b4db-9cebe88e1963'] + +LAMPS_A = [LAMPS[0]] + +BLINKS = ['aa8cd2e4-8c5d-11e9-b0ba-b827ebe99201','980a639c-20b1-11e9-8d70-a4badbf92500'] + +RELAYS = ['d34fd5be-8c58-11e9-8999-b827ebe99201'] + +mon = None +dev = None + +def send(targets,action,body=None): + global dev + dev.engine.send_request(dev,targets,action,body) + +def search_for_light(lamps): + for l in lamps: + dev = mon.devices.get_with_addr(l) + if dev: + light = dev.attributes.get('light',None) + if light: + return True + return False + +def on_off_light(lamps): + print('on_off %s' % lamps) + if search_for_light(lamps): + send(lamps,'off') + return False + else: + send(lamps,'on') + return True + +def siren_play(snd=2): + send(SIREN,'play',{'sound':snd}) + +def siren_stop(): + send(SIREN,'stop') + +def pushbullet(title,msg): + send(PUSH_BULLET,'notify',{'title': title,'msg':msg}) + +def blink(): + send(BLINKS,'blink') + + +def handle_msg(msg): + if not msg.is_notify(): + return + # search for the buttons + if msg.action == 'click': + if msg.source == AQUARA_R: + blink() + siren_play(2) + pushbullet('Alerte','') + + if msg.source == AQUARA_1[0]: + #on_off_light(LAMPS_A) + send(RELAYS,'on') + + if msg.source == AQUARA_1[1]: + #on_off_light(LAMPS_A) + send(RELAYS,'off') + + if msg.source == AQUARA_2[0]: + siren_play(20) + + if msg.source == AQUARA_2[1]: + pushbullet('Alerte','Intrusion') + siren_play(1) + + if msg.source == AQUARA_2[2]: + siren_stop() + + #send(SHUTERS,'stop') + + +def main(): + global mon,dev + dev = devices.basic() + dev.info = '%s@%s' % (PKG_NAME,platform.node()) + engine = Engine() + engine.add_device(dev) + engine.add_rx_handler(handle_msg) + mon = Monitor(dev) + engine.run() + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print('Bye bye')