mirror of
https://github.com/schnidrig/openhab-ansible
synced 2026-01-12 00:48:46 +01:00
added gardena scripts
This commit is contained in:
@@ -1,17 +1,24 @@
|
|||||||
$ANSIBLE_VAULT;1.1;AES256
|
$ANSIBLE_VAULT;1.1;AES256
|
||||||
30303535613532623633306661646164653037323038343838386437643463633937656664653634
|
65343063363130666335313366396139653130333535653437376464666230653230656662663738
|
||||||
6439643934616538373936396465323466323833303633640a326630646662376237376532313761
|
3239336161333434336264386436393738653637346561370a626437323632323866366139613339
|
||||||
65643938613863333235353536323939353761373936303965316262366465633163626234653132
|
36343964666562636666663766613032333133303931356537353334353635333236396630323963
|
||||||
6161363266613162360a656433306536663230316631333430373362643034393930353439626134
|
6133666333633837370a376266346364396264626136363766383735383362343366373134616231
|
||||||
39356135633861646539626661623062326531656539623030623637363634396639323935306230
|
33303732316632376563373330336534623934393166346233633666343136653735653363653538
|
||||||
63383637393438326434626137666637373466363735306334303366646134623332353830633137
|
38613733393366303730323466383136346563386531376338333731643762326232373631653563
|
||||||
66363531633636313739613264616164646366396634313934333566613936303865623162336463
|
64333965313730363133663663613563396664613463333936363833396333363131313164646463
|
||||||
63346330366438393965663134326135633436346532383162316234623065623661613138353838
|
62393861633133366539656662643037616362633964626335373338383563663865306339616438
|
||||||
34666530303838633161643532396535343432633064323938393933353562623366333862333731
|
31373063623635316232313262353563646331346438376538343635373966313235623038643763
|
||||||
39313562623762313437333738396534646466333461393536316239636134313866393234363931
|
35633139363636663837323166393563616132663633633331363136326634363562376138356437
|
||||||
63373934363961376634623966616135333835353066656236666139363965643934376162313439
|
33303166306562663061306437353566386563633030623835376633393865303238313866656262
|
||||||
34653438633630353663363130653434636331376536643765653232323266313462373965343266
|
32653632643765343062363264623338336664656432373934656433663639313635383364646430
|
||||||
34666639656264313933646264623931626230613636313030346637383361393934653964333565
|
37653037386664333437663737626535373463656564623262613638313333643336613663393835
|
||||||
34343432613336373834646136306537303463643463653463353066663062323735653631643132
|
32613663393865643665393931323235653937626533366363326266663666393438623937643265
|
||||||
32303639616533353735336437636634383430623534623935333364323631393536363661303163
|
66626330363238663866393662636561623934633232646536393831623735343162303339313238
|
||||||
33633939613138336637
|
30376536343766333234396539386237333132356637623336313535356564356437303763383332
|
||||||
|
31383437313963306231343166623532383064383938636433313365363333646636383631326330
|
||||||
|
36653630653733383232663639303762653237306333393564323335333130356639393535613030
|
||||||
|
32386338326264343333396233633138363633663234346535346138326661643931306439316261
|
||||||
|
62373831373462326263316232613338626132353564383262643332623563626465363938623932
|
||||||
|
62303436363863386464343135306362636232363833303237393562393037663436353136336538
|
||||||
|
66646333653031306362393539363836333063353765313464366363353361616464333733303463
|
||||||
|
376331376261323236333164343761663362
|
||||||
|
|||||||
@@ -2,17 +2,16 @@
|
|||||||
|
|
||||||
# uncomment the variables and add info
|
# uncomment the variables and add info
|
||||||
|
|
||||||
#vault_graphite_server:
|
vault_openhab_config_repo: "ssh://<user>>@<host>:<port>/path/repo.git"
|
||||||
#vault_openhab_config_repo:
|
|
||||||
|
|
||||||
#vault_nginx_user: user
|
vault_nginx_user: <user>
|
||||||
#vault_nginx_password: passwd
|
vault_nginx_password: <pass>
|
||||||
|
|
||||||
#vault_dynv6_name: "myhost.dynv6.net"
|
vault_dynv6_device: ""
|
||||||
#vault_dynv6_device: ""
|
vault_dynv6_token: '<token>'
|
||||||
#vault_dynv6_token: 'mytoken'
|
|
||||||
|
|
||||||
#vault_letsencrypt_email: myname@domain.com
|
vault_gardena_user: 'user_name'
|
||||||
#vault_fqdn: "example.com"
|
vault_gardena_password: 'password'
|
||||||
|
vault_gardena_api_key: '<api-key>'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,34 @@
|
|||||||
|
|
||||||
- name: install gardena service file
|
- name: install gardena service file
|
||||||
template:
|
template:
|
||||||
src: "gardena.service"
|
src: "gardena/gardena.service"
|
||||||
dest: "/etc/systemd/system/gardena.service"
|
dest: "/etc/systemd/system/gardena.service"
|
||||||
mode: u=rw,g=rw,o=r
|
mode: u=rw,g=rw,o=r
|
||||||
|
|
||||||
|
- name: install gardena service script
|
||||||
|
template:
|
||||||
|
src: "gardena/gardena_monitor_collector.py"
|
||||||
|
dest: "/etc/openhab2/automation/gardena/gardena_monitor_collector.py"
|
||||||
|
mode: u=rwx,g=rx,o=rx
|
||||||
|
group: root
|
||||||
|
owner: root
|
||||||
|
|
||||||
|
- name: install gardena service config
|
||||||
|
template:
|
||||||
|
src: "gardena/gardena.yml"
|
||||||
|
dest: "/etc/openhab2/automation/gardena/gardena.yml"
|
||||||
|
mode: u=rw,g=r,o=r
|
||||||
|
group: openhab
|
||||||
|
owner: openhab
|
||||||
|
|
||||||
|
- name: install gardena jsr223 script
|
||||||
|
template:
|
||||||
|
src: "gardena/gardena.py"
|
||||||
|
dest: "/etc/openhab2/automation/jsr223/gardena.py"
|
||||||
|
mode: u=rw,g=r,o=r
|
||||||
|
group: openhab
|
||||||
|
owner: openhab
|
||||||
|
|
||||||
- name: enable gardena service
|
- name: enable gardena service
|
||||||
systemd:
|
systemd:
|
||||||
daemon_reload: yes
|
daemon_reload: yes
|
||||||
|
|||||||
180
roles/openhab2/templates/gardena/gardena.py
Normal file
180
roles/openhab2/templates/gardena/gardena.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# Copyright (c) 2019 by Christian Schnidrig.
|
||||||
|
|
||||||
|
# https://github.com/TooTallNate/Java-WebSocket
|
||||||
|
|
||||||
|
# jython imports
|
||||||
|
from org.slf4j import LoggerFactory
|
||||||
|
import uuid
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import jsonmerge
|
||||||
|
|
||||||
|
# java imports
|
||||||
|
#from org.eclipse.smarthome.core.scheduler import CronExpression
|
||||||
|
import profile
|
||||||
|
from org.yaml.snakeyaml import Yaml
|
||||||
|
from java.nio.file.StandardWatchEventKinds import ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY
|
||||||
|
try:
|
||||||
|
from org.openhab.core.service import AbstractWatchService
|
||||||
|
except:
|
||||||
|
from org.eclipse.smarthome.core.service import AbstractWatchService
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
# constants
|
||||||
|
|
||||||
|
module_name = "gardena"
|
||||||
|
logger_name = "jython." + module_name
|
||||||
|
module_prefix = module_name + "_"
|
||||||
|
|
||||||
|
# location of script
|
||||||
|
openhab_base_dir = '/etc/openhab2'
|
||||||
|
automationDir = openhab_base_dir + '/automation'
|
||||||
|
gardenaDir = automationDir + '/gardena'
|
||||||
|
gardena_config_file_name = 'gardena.yml'
|
||||||
|
gardena_config_file = gardenaDir + '/' + gardena_config_file_name
|
||||||
|
gardena_data_file_name = 'gardena.json'
|
||||||
|
gardena_data_file = gardenaDir + '/' + gardena_data_file_name
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
# some globals
|
||||||
|
config = None
|
||||||
|
data = None
|
||||||
|
|
||||||
|
# default logger
|
||||||
|
logger = LoggerFactory.getLogger(logger_name)
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
# config
|
||||||
|
class Config():
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = LoggerFactory.getLogger(logger_name + ".Config")
|
||||||
|
self.gardenaConfig = Yaml().load(open(gardena_config_file))
|
||||||
|
self.logger.info("Config loaded")
|
||||||
|
|
||||||
|
def getDeviceMapping(self):
|
||||||
|
return self.gardenaConfig['device_mapping']
|
||||||
|
|
||||||
|
def getItemNamePrefix(self):
|
||||||
|
return self.gardenaConfig['item_name_prefix']
|
||||||
|
|
||||||
|
def getValueMapping(self):
|
||||||
|
return self.gardenaConfig['value_mapping']
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
# gardena monitor
|
||||||
|
|
||||||
|
def gardena_monitor():
|
||||||
|
logger = LoggerFactory.getLogger(logger_name + ".gardena_monitor")
|
||||||
|
config = Config()
|
||||||
|
|
||||||
|
device_mapping = config.getDeviceMapping()
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
with open (gardena_data_file, "r") as data_file:
|
||||||
|
lines=data_file.readlines()
|
||||||
|
for line in lines:
|
||||||
|
json_line = json.loads(line)
|
||||||
|
if 'attributes' in json_line.keys():
|
||||||
|
data = jsonmerge.merge(data, {json_line['type']: { json_line['id']: json_line['attributes'] }})
|
||||||
|
|
||||||
|
logger.debug(json.dumps(data, indent=4))
|
||||||
|
value_mapping = config.getValueMapping()
|
||||||
|
prefix = config.getItemNamePrefix()
|
||||||
|
for type in value_mapping:
|
||||||
|
for value_set in data[type]:
|
||||||
|
valve_number = None
|
||||||
|
id = value_set
|
||||||
|
if type == "VALVE":
|
||||||
|
id, valve_number = id.split(':')
|
||||||
|
if id in device_mapping:
|
||||||
|
device_name = device_mapping[id]
|
||||||
|
if type == "VALVE":
|
||||||
|
device_name = device_name + "_" + str(valve_number)
|
||||||
|
logger.debug("Found device: " + device_name + " of type: " + type)
|
||||||
|
for value_name in value_mapping[type]:
|
||||||
|
if not value_name.endswith('_map'):
|
||||||
|
if value_name in data[type][value_set]:
|
||||||
|
item_suffix = value_mapping[type][value_name]
|
||||||
|
item_name = prefix + "_" + device_name + "_" + item_suffix
|
||||||
|
item = ir.get(item_name)
|
||||||
|
if item == None:
|
||||||
|
logger.info("Item not found: " + item_name)
|
||||||
|
else:
|
||||||
|
value = str(data[type][value_set][value_name]['value'])
|
||||||
|
if value_name + '_map' in value_mapping[type]:
|
||||||
|
value = str(value_mapping[type][value_name + '_map'][value])
|
||||||
|
logger.info("Set item " + item_name + " = " + value)
|
||||||
|
events.postUpdate(item_name, value)
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
# fileWatcher
|
||||||
|
|
||||||
|
class FileWatcher(AbstractWatchService):
|
||||||
|
def __init__(self, path, event_kinds=[ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY], watch_subdirectories=False):
|
||||||
|
AbstractWatchService.__init__(self, path)
|
||||||
|
self.logger = LoggerFactory.getLogger(logger_name + ".FileWatcher")
|
||||||
|
self.event_kinds = event_kinds
|
||||||
|
self.watch_subdirectories = watch_subdirectories
|
||||||
|
self.logger.debug("new fileWatcher for " + str(path) + " created.")
|
||||||
|
|
||||||
|
def getWatchEventKinds(self, path):
|
||||||
|
return self.event_kinds
|
||||||
|
|
||||||
|
def watchSubDirectories(self):
|
||||||
|
return self.watch_subdirectories
|
||||||
|
|
||||||
|
def processWatchEvent(self, event, kind, path):
|
||||||
|
try:
|
||||||
|
self.logger.debug(event.toString())
|
||||||
|
self.logger.debug(kind.toString())
|
||||||
|
self.logger.debug(path.toString())
|
||||||
|
if str(path.toString()) == gardena_config_file or str(path.toString()) == gardena_data_file:
|
||||||
|
logger.info("File " + str(path.toString()) + " changed. Reloading.")
|
||||||
|
try:
|
||||||
|
gardena_monitor()
|
||||||
|
except:
|
||||||
|
logger.error("gardena_monitor failed.")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
except:
|
||||||
|
self.logger.error("processWatchEvent callback failed.")
|
||||||
|
self.logger.error(traceback.format_exc())
|
||||||
|
self.deactivate()
|
||||||
|
self.activate()
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
#######################################################
|
||||||
|
# __main__
|
||||||
|
|
||||||
|
fileWatcherGardena = None
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
# script load/unload hooks
|
||||||
|
def scriptLoaded(id):
|
||||||
|
try:
|
||||||
|
logger.info("scriptLoaded()")
|
||||||
|
fileWatcherGardena = FileWatcher(gardenaDir)
|
||||||
|
fileWatcherGardena.activate()
|
||||||
|
gardena_monitor()
|
||||||
|
except:
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
if fileWatcherGardena is not None:
|
||||||
|
fileWatcherGardena.deactivate()
|
||||||
|
|
||||||
|
def scriptUnloaded():
|
||||||
|
logger.info("scriptUnloaded()")
|
||||||
|
if fileWatcherGardena is not None:
|
||||||
|
fileWatcherGardena.deactivate()
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
Description=Service monitoring gardena web service
|
Description=Service monitoring gardena web service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/etc/openhab2/automation/gardena_monitor_collector.py
|
ExecStart=/etc/openhab2/automation/gardena/gardena_monitor_collector.py
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
26
roles/openhab2/templates/gardena/gardena.yml
Normal file
26
roles/openhab2/templates/gardena/gardena.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Copyright (c) 2019 by Christian Schnidrig.
|
||||||
|
|
||||||
|
########################
|
||||||
|
device_mapping:
|
||||||
|
164f4132-08e0-4d5f-b7f7-85048dd88281: sensor1
|
||||||
|
ab9633cd-9a2a-4937-ac38-4f58717493b7: ic24
|
||||||
|
|
||||||
|
item_name_prefix: "gardena"
|
||||||
|
|
||||||
|
value_mapping:
|
||||||
|
SENSOR:
|
||||||
|
soilHumidity: soil_humidity
|
||||||
|
soilTemperature: soil_temperature
|
||||||
|
lightIntensity: light_intensity
|
||||||
|
ambientTemperature: ambient_temperature
|
||||||
|
VALVE:
|
||||||
|
activity: state
|
||||||
|
activity_map:
|
||||||
|
CLOSED: "CLOSED"
|
||||||
|
MANUAL_WATERING: "OPEN"
|
||||||
|
SCHEDULED_WATERING: "OPEN"
|
||||||
|
name: name
|
||||||
|
COMMON:
|
||||||
|
batteryLevel: battery_level
|
||||||
|
rfLinkLevel: link_level
|
||||||
|
|
||||||
135
roles/openhab2/templates/gardena/gardena_monitor_collector.py
Executable file
135
roles/openhab2/templates/gardena/gardena_monitor_collector.py
Executable file
@@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import websocket
|
||||||
|
from threading import Thread
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# account specific values
|
||||||
|
USERNAME = '{{ vault_gardena_user }}'
|
||||||
|
PASSWORD = '{{ vault_gardena_password }}'
|
||||||
|
API_KEY = '{{ vault_gardena_api_key }}'
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# other constants
|
||||||
|
AUTHENTICATION_HOST = 'https://api.authentication.husqvarnagroup.dev'
|
||||||
|
SMART_HOST = 'https://api.smart.gardena.dev'
|
||||||
|
|
||||||
|
dataFileName = "/etc/openhab2/automation/gardena/gardena.json"
|
||||||
|
logFileName = "/etc/openhab2/automation/gardena/gardena.json.log"
|
||||||
|
|
||||||
|
##############################
|
||||||
|
module_name = "monitor"
|
||||||
|
logger_name = "gardena." + module_name
|
||||||
|
# default logger
|
||||||
|
logger = logging.getLogger(logger_name)
|
||||||
|
|
||||||
|
##############################
|
||||||
|
class Client:
|
||||||
|
|
||||||
|
def __init__(self, dataFile, logFile):
|
||||||
|
self.dataFileName = dataFileName
|
||||||
|
self.logFile = logFile
|
||||||
|
self.logger = logging.getLogger(logger_name + '.Client')
|
||||||
|
self.dataFile = None
|
||||||
|
|
||||||
|
def on_message(self, message):
|
||||||
|
if self.dataFile != None:
|
||||||
|
self.dataFile.write(message)
|
||||||
|
self.dataFile.write('\n')
|
||||||
|
self.dataFile.flush()
|
||||||
|
logFile.write(message)
|
||||||
|
logFile.write('\n')
|
||||||
|
logFile.flush()
|
||||||
|
|
||||||
|
def on_error(self, error):
|
||||||
|
self.logger.error(error)
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
self.live = False
|
||||||
|
self.logger.info("### closed ###")
|
||||||
|
self.dataFile.close()
|
||||||
|
|
||||||
|
def on_open(self):
|
||||||
|
self.logger.info("### connected ###")
|
||||||
|
self.dataFile = open(dataFileName, "w")
|
||||||
|
|
||||||
|
self.live = True
|
||||||
|
|
||||||
|
def run(*args):
|
||||||
|
while self.live:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
Thread(target=run).start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############################
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
try:
|
||||||
|
start = time.time()
|
||||||
|
logger.info(datetime.datetime.now())
|
||||||
|
logFile = open(logFileName, "a")
|
||||||
|
|
||||||
|
payload = {'grant_type': 'password', 'username': USERNAME, 'password': PASSWORD,
|
||||||
|
'client_id': API_KEY}
|
||||||
|
|
||||||
|
logger.debug("Logging into gardena system...")
|
||||||
|
r = requests.post('{}/v1/oauth2/token'.format(AUTHENTICATION_HOST), data=payload)
|
||||||
|
assert r.status_code == 200, r
|
||||||
|
auth_token = r.json()["access_token"]
|
||||||
|
logger.debug("Got token: {}".format(auth_token))
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/vnd.api+json",
|
||||||
|
"x-api-key": API_KEY,
|
||||||
|
"Authorization-Provider": "husqvarna",
|
||||||
|
"Authorization": "Bearer " + auth_token
|
||||||
|
}
|
||||||
|
|
||||||
|
r = requests.get('{}/v1/locations'.format(SMART_HOST), headers=headers)
|
||||||
|
assert r.status_code == 200, r
|
||||||
|
assert len(r.json()["data"]) > 0, 'location missing - user has not setup system'
|
||||||
|
location_id = r.json()["data"][0]["id"]
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"data": {
|
||||||
|
"type": "WEBSOCKET",
|
||||||
|
"attributes": {
|
||||||
|
"locationId": location_id
|
||||||
|
},
|
||||||
|
"id": "does-not-matter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("Logged in (%s), getting WebSocket ID..." % auth_token)
|
||||||
|
r = requests.post('{}/v1/websocket'.format(SMART_HOST), json=payload, headers=headers)
|
||||||
|
|
||||||
|
assert r.status_code == 201, r
|
||||||
|
logger.info("WebSocket ID obtained, connecting...")
|
||||||
|
response = r.json()
|
||||||
|
websocket_url = response["data"]["attributes"]["url"]
|
||||||
|
|
||||||
|
# websocket.enableTrace(True)
|
||||||
|
client = Client(dataFileName, logFile)
|
||||||
|
ws = websocket.WebSocketApp(
|
||||||
|
websocket_url,
|
||||||
|
on_message=client.on_message,
|
||||||
|
on_error=client.on_error,
|
||||||
|
on_close=client.on_close)
|
||||||
|
ws.on_open = client.on_open
|
||||||
|
ws.run_forever(ping_interval=150, ping_timeout=1)
|
||||||
|
|
||||||
|
except:
|
||||||
|
delay = 15 * 60 - (time.time() - start)
|
||||||
|
if (delay > 0):
|
||||||
|
logger.info("Sleeping for: {} seconds before retrying.".format(delay))
|
||||||
|
time.sleep(delay)
|
||||||
Reference in New Issue
Block a user