2
0
mirror of https://github.com/schnidrig/openhab-ansible synced 2026-01-12 00:48:46 +01:00
This commit is contained in:
Christian Schnidrig
2022-02-24 08:47:34 +01:00
parent d2c994a8cf
commit 130da0a017
5 changed files with 390 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
- name: install gardena service file
template:
src: "gardena/gardena.service"
dest: "/etc/systemd/system/gardena.service"
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
systemd:
daemon_reload: yes
- name: Make sure gardena service is running
systemd:
state: started
name: gardena

View 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()

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Service monitoring gardena web service
[Service]
ExecStart=/etc/openhab2/automation/gardena/gardena_monitor_collector.py
[Install]
WantedBy=multi-user.target

View 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

View 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)