Hi,
Over the last days i played a bit with the Matrix Theme for HABPanel…
This is the widget i made for Neato BotVac Vacuum Cleaner Connected Series… I may share the code later, need a bit of clean up… it looks pretty neat and i’m very happy with the result… even the way was very long and included some pip python installs…
- grab the Last Cleaning Map from Neato via pybotvac
- take that image, crop/resize and change the background to grafana “fill” color
- “Update Map” runs script via Exec binding
- since the “DOCK” funktion only works if the basestation has been seen from the Robot, this button is only visible and clickable if DockHasBeenSeen item is ON
Thanks to @ysc @pmpkk for this very cool UI!
Edit 28/03/2019:
Here is the code…
HABPanel widget code:
<div class="section">
<div class="sectionIconContainer"><div class="sectionIcon"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#vacuum-cleaner"></use></svg></div></div>
<div class="bigDash">
<div class="description">HoLLe's Alfred - Aktuell</div>
<div class="top">
<div class="icon on"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#event-list"></use></svg></div>
<div class="value">
<div class="main">{{itemValue('AlfredState')}}</div>
</div>
</div>
<div class="bottom">
<div class="sceneGroup">
<div class="scene on" ng-click="sendCmd('AlfredCommand', 'clean')">Clean</div>
</div>
<div class="sceneGroup">
<div class="scene on2" ng-click="sendCmd('AlfredCommand', 'pause')">Pause</div>
</div>
<div class="sceneGroup">
<div class="scene on2" ng-click="sendCmd('AlfredCommand', 'resume')">Resume</div>
</div>
<div class="sceneGroup">
<div class="scene on2" ng-click="sendCmd('AlfredCommand', 'stop')">Stop</div>
</div>
</div>
<div class="bottom">
<div class="sceneGroup">
<div class="scene on2" ng-class="{true: 'disabled'}[itemValue('AlfredDockHasBeenSeen')!='Ja']" ng-click="sendCmd('AlfredCommand', 'dock')">Andocken</div>
</div>
<div class="sceneGroup">
<div class="scene on" ng-click="sendCmd('Alfred_GetNewMap', 'ON')">Update Map</div>
</div>
</div>
<div class="bottom">
<div class="error">{{itemValue('AlfredError')}}</div>
</div>
</div>
<div class="bigDash">
<div class="description">Aktion - Aktuell</div>
<div class="top">
<div class="icon on"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#battery-connected"></use></svg></div>
<div class="value">
<div class="main">{{itemValue('AlfredBattery')}}</div>
<div class="sub">%</div>
</div>
</div>
<div class="bottom">
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#buildings-03"></use></svg></div>
<div class="value">In Station</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredIsDocked')}}</div></div>
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#buildings-29"></use></svg></div>
<div class="value">Station erkannt</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredDockHasBeenSeen')}}</div></div>
</div>
<div class="bottom">
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#charging-1"></use></svg></div>
<div class="value">Am Laden</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredIsCharging')}}</div></div>
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#event-date"></use></svg></div>
<div class="value">Zeitplan</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredIsScheduled')}}</div></div>
</div>
<div class="bottom">
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#buildings-28"></use></svg></div>
<div class="value">Kategorie</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredCategory')}}</div></div>
</div>
<div class="bottom">
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#buildings-38"></use></svg></div>
<div class="value">Reinigungs-Modus</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredMode')}}</div></div>
</div>
<div class="bottom">
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#buildings-37"></use></svg></div>
<div class="value">Reinigungs-Art</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredModifier')}}</div></div>
</div>
<div class="bottom">
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#close-arrows"></use></svg></div>
<div class="value">Spot-Länge</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredSpotHeight')}}</div></div>
<div class="icon off"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/smarthome.svg#close-arrows-4"></use></svg></div>
<div class="value">Spot-Breite</div>
<div class="valueGroup"><div class="value">{{itemValue('AlfredSpotWidth')}}</div></div>
</div>
</div>
<div class="bigDash">
<div class="description">Reinigungs-Karte</div>
<div class="graph">
<img width="300" height="300" src="/static/neato-botvac/botvac-last-map.png" />
<div class="legend">{{itemValue('Alfred_GetNewMap')}} x {{itemValue('Alfred_GetNewMap_exit')}} x {{itemValue('Alfred_GetNewMap_last')}}</div>
</div>
</div>
</div>
items / neato.items:
Group GNeato
//String AlfredName "Name [%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:name"}
Number AlfredBattery "Akku Status [%.0f %%]" <battery> (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:battery-level"}
String AlfredState "Status [MAP(neato.map):%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:state"}
String AlfredError "Fehler [%s]" <error> (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:error"}
//String AlfredVersion "Version [%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:version"}
//String AlfredModel "Model [%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:model-name"}
//String AlfredFirmware "Firmware [%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:firmware"}
String AlfredAction "Aktuelle Aktion [MAP(neato.map):%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:action"}
Switch AlfredDockHasBeenSeen "Ladestation erkannt [MAP(neato.map):%s]" <presence> (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:dock-has-been-seen"}
Switch AlfredIsDocked "In Ladestation [MAP(neato.map):%s]" <presence> (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:is-docked"}
Switch AlfredIsScheduled "Zeitplan [MAP(neato.map):%s]" <time> (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:is-scheduled"}
Switch AlfredIsCharging "Am Laden [MAP(neato.map):%s]" <heating> (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:is-charging"}
String AlfredCategory "Reinigung [MAP(neato.map):%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:cleaning-category"}
String AlfredMode "Reinigungs Modus [MAP(neato.map):%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:cleaning-mode"}
String AlfredModifier "Reinigungs Art [MAP(neato.map):%s]" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:cleaning-modifier"}
Number AlfredSpotWidth "Spot-Breite [%.0f]" <niveau> (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:cleaning-spotwidth"}
Number AlfredSpotHeight "Spot-Höhe [%.0f]" <niveau> (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:cleaning-spotheight"}
//String AlfredNavigationMode "Nav Mode" (GNeato) {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:cleaning-navigation-mode"}
String AlfredCommand "Befehl" <movecontrol> {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:command"}
String AlfredMapId "Map ID" {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:map-id"}
String AlfredMapUrl "Map Url" {channel="neato:vacuumcleaner:OPSXXX-YOUR_ROBOT_SERIAL:map-url"}
//Alexa Dummy Switch - see .rules
Switch AlfredAlexa "Alfred" ["Switchable"]
Switch Alfred_GetLastMapTrigger "TEST %s"
// state of the execution, is running or finished
Switch Alfred_GetNewMap {channel="exec:command:neato_new_map:run"}
// Arguments to be placed for '%2$s' in command line
String Alfred_GetNewMap_Args {channel="exec:command:neato_new_map:input"}
// Output of command line execution
String Alfred_GetNewMap_out {channel="exec:command:neato_new_map:output"}
Number Alfred_GetNewMap_exit {channel="exec:command:neato_new_map:exit"}
DateTime Alfred_GetNewMap_last {channel="exec:command:neato_new_map:lastexecution"}
rules / neato.rules:
rule "Getting last map for neato botvac connected"
when
Item Alfred_GetLastMapTrigger received command ON
then
//Alfred_GetLastMapRunning.postUpdate("Download ...")
executeCommandLine("/usr/bin/python /etc/openhab2/scripts/neato-botvac-getlastmap.py", 5000)
logInfo("Your command exec", "Result:" + Alfred_GetLastMapTrigger.state )
//Alfred_GetLastMapRunning.postUpdate("-")
Alfred_GetLastMapTrigger.postUpdate(OFF)
end
transform / neato.map
ui_alert_busy_charging=beschäftigt mit Aufladen
ui_error_navigation_noprogress=Navigationsproblem
ui_alert_recovering_location=Standort wiederherstellen
ui_error_brush_overload=Bürsten überlastet
ui_error_dust_bin_full=Staubbehälter voll
ui_error_dust_bin_emptied=Staubbehälter entleert
ui_error_brush_stuck=verstopfte Bürsten
// Curent state of the vacuum cleaner
INVALID=Chillen
IDLE=Idle
BUSY=Busy
PAUSED=Pause
ERROR=Error
ON=Ja
OFF=Nein
// Current action of the vacuum cleaner
HOUSE_CLEANING=Haus Reinigung
SPOT_CLEANING=Spot Reinigung
MANUAL_CLEANING=Manuelle Reinigung
DOCKING=Angedockt
USER\ MENU\ ACTIVE=Aktives Benutzermenü
SUSPENDED\ CLEANING=Reinigung abgebrochen
UPDATING=Aktualisieren
COPYING\ LOGS=Logs kopieren
RECOVERING_LOCATION=zum Ausgangspunkt zurückkehren
IEC\ TEST=Iec Test
// Current or last cleaning mode
CLEAN-MODE-ECO=Eco
CLEAN-MODE-TURBO=Turbo
// Modifier of current or last cleaning
CLEAN-MODIFIER-NORMAL=Normal
CLEAN-MODIFIER-DOUBLE=Doppelt
// Current or Last category of the cleaning
CLEAN-CATEGORY-HOUSE=Haus Reinigung
CLEAN-CATEGORY-SPOT=Spot Reinigung
CLEAN-CATEGORY-MANUAL=Manuelle Reinigung
CLEAN-NAVIGATIONMODE-NORMAL=Normal
CLEAN-NAVIGATIONMODE-EXTRACARE=Extra Care
NULL=NULL
-=NA
=NA
UNRECOGNIZED=unerkannt
NORMAL=Normal
TURBO=Turbo
HOUSE=House Reinigung
ECO=Eco
Because the binding doesnt support maps right now… I used these following tools to get the cleaning map thing to work.
you need to install pybotvac -> https://github.com/stianaske/pybotvac
additional for image crop and format:
you need to install PIL Pillow -> https://github.com/python-pillow/Pillow
and
you need to install numpy -> https://github.com/numpy/numpy
scripts / neato-botvac-getlastmap.py
#!/usr/bin/python
from pybotvac import Account
from os import system
from PIL import Image
import numpy as np
email = 'YOUR_NEATO_EMAIL'
password = 'YOUR_NEATO_PASSWORD'
serial = 'OPS49416-884AEAF3DA74'
path_tmp = '/etc/openhab2/html/neato-botvac/tmp/'
path_dst = '/etc/openhab2/html/neato-botvac/'
map_file = 'botvac-last-map.png'
account = Account(email, password)
link = account.maps[serial]['maps'][0]['url']
account.get_map_image(link, path_tmp)
system('mv ' + path_tmp + '*-user-map.png ' + path_dst + map_file)
img = Image.open(path_dst + map_file).convert('RGB')
area = (73, 214, 713, 854)
cropped_img = img.crop(area)
cropped_img.save(path_dst + map_file)
orig_color = (51,61,74)
replacement_color = (38, 49, 29)
img3 = Image.open(path_dst + map_file).convert('RGB')
data = np.array(img3)
data[(data == orig_color).all(axis = -1)] = replacement_color
img2 = Image.fromarray(data, mode='RGB')
img2.save(path_dst + map_file)