-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 938c9cf
Showing
18 changed files
with
515 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Changelog | ||
|
||
## 0.0.1 | ||
- Initial setup |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Kappelt gBridge - Domoticz Python Plugin | ||
Python plugin for Domoticz to add integration with [gBridge](https://github.com/kservices/gBridge) project | ||
|
||
## Prerequisites | ||
|
||
Setup and run gBridge server, for now only local setups are supported due to username/password protection of remote MQTT server (https://doc.gbridge.kappelt.net/html/index.html). | ||
|
||
## Installation | ||
|
||
1. Clone repository into your domoticz plugins folder | ||
``` | ||
cd domoticz/plugins | ||
git clone --- gbridge | ||
``` | ||
2. Restart domoticz | ||
3. Go to "Hardware" page and add new item with type "gBridge" | ||
4. Set your MQTT server address, gBridge config etc. to plugin settings | ||
|
||
Once plugin receive any message from mqtt server it will call the domoticz API and switch on a light/switch or change brightness of a device | ||
|
||
## Plugin update | ||
|
||
1. Stop domoticz | ||
2. Go to plugin folder and pull new version | ||
``` | ||
cd domoticz/plugins/gbridge | ||
git pull | ||
``` | ||
3. Start domoticz | ||
|
||
## Supported devices | ||
|
||
For now only the onoff and brightness traits are supported |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from adapters.dimmable_adapter import DimmableAdapter | ||
from adapters.on_off_switch_adapter import OnOffSwitchAdapter | ||
from adapters.scene_adapter import SceneAdapter | ||
|
||
adapter_by_type = { | ||
'brightness': { | ||
'Default': DimmableAdapter() | ||
}, | ||
'onoff': { | ||
'Default': OnOffSwitchAdapter(), | ||
'Scene': SceneAdapter(), | ||
'Group': SceneAdapter() | ||
} | ||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import urllib.parse | ||
import urllib.request | ||
import Domoticz | ||
|
||
class Adapter: | ||
|
||
def __init__(self): | ||
pass | ||
|
||
def handleMqttMessage(self, device_id, data, domoticz_port): | ||
pass | ||
|
||
def callDomoticzApi(self, domoticz_port, command): | ||
url = "http://localhost:%s/json.htm?type=command&%s" % (domoticz_port, command) | ||
Domoticz.Debug('Executing command for device: %s' % command) | ||
req = urllib.request.Request(url) | ||
response = urllib.request.urlopen(req).read() | ||
return response.decode('utf-8') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from adapters.base_adapter import Adapter | ||
import urllib.parse | ||
|
||
class DimmableAdapter(Adapter): | ||
|
||
def handleMqttMessage(self, device_id, data, domoticz_port): | ||
params = { | ||
'param': 'switchlight', | ||
'idx': device_id, | ||
'switchcmd': 'Set Level', | ||
'level': data | ||
} | ||
Adapter.callDomoticzApi(self, domoticz_port, urllib.parse.urlencode(params)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from adapters.base_adapter import Adapter | ||
import urllib.parse | ||
|
||
class OnOffSwitchAdapter(Adapter): | ||
|
||
def __init__(self): | ||
Adapter.__init__(self) | ||
|
||
def handleMqttMessage(self, device_id, data, domoticz_port): | ||
if data == '1': | ||
command = 'On' | ||
else: | ||
command = 'Off' | ||
|
||
params = { | ||
'param': self.getType(), | ||
'idx': device_id, | ||
'switchcmd': command | ||
} | ||
Adapter.callDomoticzApi(self, domoticz_port, urllib.parse.urlencode(params)) | ||
|
||
def getType(self): | ||
return 'switchlight' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from adapters.on_off_switch_adapter import OnOffSwitchAdapter | ||
|
||
class SceneAdapter(OnOffSwitchAdapter): | ||
|
||
def getType(self): | ||
return 'switchscene' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import Domoticz | ||
import json | ||
import re | ||
import urllib.parse | ||
import urllib.request | ||
|
||
class DomoticzClient: | ||
DomoticzPort = "" | ||
|
||
def __init__(self, domoticz_port): | ||
self.DomoticzPort = domoticz_port | ||
Domoticz.Debug("gBridgeClient::__init__") | ||
|
||
def fetchDevicesFromDomoticz(self): | ||
req = urllib.request.Request( | ||
"http://localhost:%d/json.htm?type=devices&filter=all&used=true&order=Name" % self.DomoticzPort) | ||
response = urllib.request.urlopen(req).read().decode('utf-8') | ||
Domoticz.Debug("Fetching all devices from Domoticz %s" % response) | ||
return json.loads(response)['result'] | ||
|
||
def getDevicesByName(self, domoticz_devices): | ||
domoticz_devices_by_name = {} | ||
for device in domoticz_devices: | ||
if "gBridge" in device['Description']: | ||
match = re.search('gBridge:(.*)', device['Description']) | ||
if match: | ||
name = match.group(1) | ||
else: | ||
name = device['Name'] | ||
domoticz_devices_by_name[name] = device | ||
return domoticz_devices_by_name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import Domoticz | ||
import json | ||
import urllib.parse | ||
import urllib.request | ||
from base64 import b64encode | ||
|
||
|
||
class gBridgeClient: | ||
Address = "" | ||
Username = "" | ||
Password = "" | ||
|
||
brightness_devices = ['Blinds Percentage', 'Dimmer', 'Blinds Percentage Inverted'] | ||
|
||
def __init__(self, address, username, password): | ||
Domoticz.Debug("gBridgeClient::__init__") | ||
self.Address = address | ||
self.Username = username | ||
self.Password = password | ||
|
||
def syncDevices(self, domoticz_devices_by_name, bridge_devices, delete_removed_devices): | ||
bridge_devices_by_name = {x['name']: x for x in bridge_devices} | ||
|
||
# Add devices which are not in gBridge yet | ||
for name, device in domoticz_devices_by_name.items(): | ||
if name not in bridge_devices_by_name: | ||
# Switch by default | ||
type = 3 | ||
# On/Off trait | ||
traits = [1] | ||
|
||
if 'SwitchType' in device and device['SwitchType'] in self.brightness_devices: | ||
# Brightness trait | ||
traits.append(2) | ||
# Light | ||
type = 1 | ||
self.createDevice(name, type, traits) | ||
|
||
# remove devices in gbridge which are no longer in domoticz | ||
if delete_removed_devices: | ||
for device in bridge_devices: | ||
if device['name'] not in domoticz_devices_by_name: | ||
self.deleteDevice(device['device_id']) | ||
|
||
def fetchDevicesFromBridge(self): | ||
url = "%s/api/device" % self.Address | ||
req = urllib.request.Request(url) | ||
req.add_header("Authorization", 'Basic %s' % self.getAuthHeader()) | ||
response = urllib.request.urlopen(req).read().decode('utf-8') | ||
Domoticz.Debug("Fetching all devices from gBridge %s" % response) | ||
return json.loads(response) | ||
|
||
def deleteDevice(self, id): | ||
gBridgeUrl = "%s/api/device/%s" % (self.Address, id) | ||
req = urllib.request.Request(gBridgeUrl, method='DELETE') | ||
req.add_header("Authorization", 'Basic %s' % self.getAuthHeader()) | ||
response = str(urllib.request.urlopen(req).read()) | ||
Domoticz.Debug('Delete device %s which is no longer in Domoticz, response: %s' % (id, response)) | ||
return response == b'Created' | ||
|
||
def createDevice(self, name, type, traits): | ||
values = {'name': name, 'type': type, 'traits': traits, 'topicPrefix': name} | ||
data = json.dumps(values).encode('ascii') | ||
req = urllib.request.Request("%s/api/device" % self.Address, data) | ||
req.add_header('Content-Type', 'application/json') | ||
req.add_header("Authorization", 'Basic %s' % self.getAuthHeader()) | ||
response = str(urllib.request.urlopen(req).read()) | ||
Domoticz.Debug( | ||
'Try to create device %s for type %s with traits %s, response: %s' % (name, type, str(traits), response)) | ||
return response == b'Created' | ||
|
||
def getAuthHeader(self): | ||
return b64encode(bytes("%s:%s" % (self.Username, self.Password), 'utf-8')).decode("ascii") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# Based on https://github.com/emontnemery/domoticz_mqtt_discovery | ||
import Domoticz | ||
import time | ||
import json | ||
|
||
class MqttClient: | ||
Address = "" | ||
Port = "" | ||
mqttConn = None | ||
isConnected = False | ||
mqttConnectedCb = None | ||
mqttDisconnectedCb = None | ||
mqttPublishCb = None | ||
|
||
def __init__(self, destination, port, mqttConnectedCb, mqttDisconnectedCb, mqttPublishCb, mqttSubackCb): | ||
Domoticz.Debug("MqttClient::__init__") | ||
self.Address = destination | ||
self.Port = port | ||
self.mqttConnectedCb = mqttConnectedCb | ||
self.mqttDisconnectedCb = mqttDisconnectedCb | ||
self.mqttPublishCb = mqttPublishCb | ||
self.mqttSubackCb = mqttSubackCb | ||
self.Open() | ||
|
||
def __str__(self): | ||
Domoticz.Debug("MqttClient::__str__") | ||
if (self.mqttConn != None): | ||
return str(self.mqttConn) | ||
else: | ||
return "None" | ||
|
||
def Open(self): | ||
Domoticz.Debug("MqttClient::Open") | ||
if (self.mqttConn != None): | ||
self.Close() | ||
self.isConnected = False | ||
|
||
Protocol = "MQTT" | ||
if (self.Port == "8883"): | ||
Protocol = "MQTTS" | ||
|
||
self.mqttConn = Domoticz.Connection(Name=self.Address, Transport="TCP/IP", Protocol=Protocol, Address=self.Address, Port=self.Port) | ||
self.mqttConn.Connect() | ||
|
||
def Connect(self): | ||
Domoticz.Debug("MqttClient::Connect") | ||
if (self.mqttConn == None): | ||
self.Open() | ||
else: | ||
ID = 'Domoticz_'+str(int(time.time())) | ||
Domoticz.Debug("MQTT CONNECT ID: '" + ID + "'") | ||
self.mqttConn.Send({'Verb': 'CONNECT', 'ID': ID}) | ||
|
||
def Ping(self): | ||
Domoticz.Debug("MqttClient::Ping") | ||
if (self.mqttConn == None or not self.isConnected): | ||
self.Open() | ||
else: | ||
self.mqttConn.Send({'Verb': 'PING'}) | ||
|
||
def Publish(self, topic, payload, retain = 0): | ||
Domoticz.Debug("MqttClient::Publish " + topic + " (" + payload + ")") | ||
if (self.mqttConn == None or not self.isConnected): | ||
self.Open() | ||
else: | ||
self.mqttConn.Send({'Verb': 'PUBLISH', 'Topic': topic, 'Payload': bytearray(payload, 'utf-8'), 'Retain': retain}) | ||
|
||
def Subscribe(self, topics): | ||
Domoticz.Debug("MqttClient::Subscribe") | ||
subscriptionlist = [] | ||
for topic in topics: | ||
subscriptionlist.append({'Topic':topic, 'QoS':0}) | ||
if (self.mqttConn == None or not self.isConnected): | ||
self.Open() | ||
else: | ||
self.mqttConn.Send({'Verb': 'SUBSCRIBE', 'Topics': subscriptionlist}) | ||
|
||
def Close(self): | ||
Domoticz.Debug("MqttClient::Close") | ||
#TODO: Disconnect from server | ||
self.mqttConn = None | ||
self.isConnected = False | ||
|
||
def onConnect(self, Connection, Status, Description): | ||
if (Status == 0): | ||
Domoticz.Debug("MQTT connected !!!!successfully.") | ||
self.Connect() | ||
else: | ||
Domoticz.Log("Failed to connect to: " + Connection.Address + ":" + Connection.Port + ", Description: " + Description) | ||
|
||
def onDisconnect(self, Connection): | ||
Domoticz.Debug("MqttClient::onDisonnect Disconnected from: " + Connection.Address+":" + Connection.Port) | ||
self.Close() | ||
# TODO: Reconnect? | ||
if self.mqttDisconnectedCb != None: | ||
self.mqttDisconnectedCb() | ||
|
||
def onMessage(self, Connection, Data): | ||
topic = '' | ||
if 'Topic' in Data: | ||
topic = Data['Topic'] | ||
payloadStr = '' | ||
if 'Payload' in Data: | ||
payloadStr = Data['Payload'].decode('utf8','replace') | ||
payloadStr = str(payloadStr.encode('unicode_escape')) | ||
|
||
if Data['Verb'] == "CONNACK": | ||
self.isConnected = True | ||
if self.mqttConnectedCb != None: | ||
self.mqttConnectedCb() | ||
|
||
if Data['Verb'] == "SUBACK": | ||
if self.mqttSubackCb != None: | ||
self.mqttSubackCb() | ||
|
||
if Data['Verb'] == "PUBLISH": | ||
if self.mqttPublishCb != None: | ||
rawmessage = Data['Payload'].decode('utf8') | ||
message = "" | ||
|
||
try: | ||
message = json.loads(rawmessage) | ||
except ValueError: | ||
message = rawmessage | ||
|
||
self.mqttPublishCb(topic, message) |
Oops, something went wrong.