Skip to content

Instantly share code, notes, and snippets.

@mqu
Last active September 15, 2018 21:31
Show Gist options
  • Select an option

  • Save mqu/fc33f3b9aa2d7c09244e2d36c6c7302e to your computer and use it in GitHub Desktop.

Select an option

Save mqu/fc33f3b9aa2d7c09244e2d36c6c7302e to your computer and use it in GitHub Desktop.
Domoticz MQTT gateway
#!/usr/bin/ruby
# Domoticz-MQTT Gateway ; author : Marc Quinton / may 2018
# source : https://gist.github.com/mqu/fc33f3b9aa2d7c09244e2d36c6c7302e (transitionnal)
# version 0.02
# fixme :
# - remove hardcoded topics and strings
# - handle "domoticz/out" topics
# - handle mqtt disconnect / done
#
# working :
# - viessmann sensors -> mqtt -> domoticz
# links :
# - domoticz/mqtt :
# - https://sigmdel.ca/michel/ha/domo/domo_03_fr.html
# - https://www.domoticz.com/wiki/MQTT
#
# - JSON API :
# - https://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s
# - Ruby Gem for Domoticz :
# - https://github.com/jankeesvw/domoticz
# - https://github.com/duhlin/domoticz
#
# about Domoticz/MQTT design issue :
# - https://easydomoticz.com/forum/viewtopic.php?f=15&t=6473
# - https://github.com/domoticz/domoticz/issues/2387
# MQTT : testing MQTT link and topics :
# mosquitto_sub -u user -P passwd -h host-or-ip -v -t "#"
# publish to Domoticz
# mosquitto_pub -u user -P passwd -h host -m '{ "idx":3, "nvalue" : 0, "svalue" : "0" }' -t 'domoticz/in'
#
require "pp"
require "json"
require 'openssl'
require 'net/http'
require 'mqtt' # use gem install mqtt
# require "domoticz" # ruby gem domoticz is in this code
# original source from : https://github.com/jankeesvw/domoticz
module Domoticz
VERSION = "0.0.2"
def self.configuration
@configuration ||= Configuration.new
end
def self.configure
yield(configuration) if block_given?
end
def self.reset
@configuration = Configuration.new
end
def self.perform_api_request(params)
username = Domoticz.configuration.username
password = Domoticz.configuration.password
uri = URI(Domoticz.configuration.server + "json.htm?" + params)
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
request.basic_auth(username, password) if username && password
opts={}
# use https ?
if uri.scheme == "https"
opts[:use_ssl] = true
opts[:verify_mode] = Domoticz.configuration.ssl_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
end
response = Net::HTTP.start(uri.hostname, uri.port, opts) { |http| http.request(request) }
JSON.parse(response.body)
end
class Configuration
attr_accessor :server
attr_accessor :username
attr_accessor :password
attr_accessor :ssl_verify
def initialize
self.server = "http://127.0.0.1:8080/"
end
end
class Device
attr_accessor :idx
attr_accessor :data
def seconds_since_update
Time.now - Time.parse(lastupdate)
end
def on!
Domoticz.perform_api_request("type=command&param=switchlight&idx=#{idx}&switchcmd=On")
end
def off!
Domoticz.perform_api_request("type=command&param=switchlight&idx=#{idx}&switchcmd=Off")
end
def toggle!
Domoticz.perform_api_request("type=command&param=switchlight&idx=#{idx}&switchcmd=Toggle")
end
def temperature
temp
end
def dimmer?
isDimmer
end
def method_missing(method_sym, *arguments, &block)
hash = Hash[@data.map { |k, v| [k.downcase, v] }]
key = method_sym.to_s.downcase
if hash.has_key?(key)
hash[key]
else
super
end
end
def topic
return nil unless self.description.match?(/mqtt\:\/\/(.*)/)
return self.description.match(/mqtt\:\/\/(.*)/)[1]
end
def self.find_by_id(id)
all.find { |d| d.idx == id.to_s }
end
def self.all
Domoticz.perform_api_request("type=devices&filter=all&used=true")["result"].map do |json|
Device.new_from_json(json)
end
end
def self.new_from_json(json)
device = self.new
device.data = json
device.idx = json["idx"]
device
end
end
end
# array of devices with specific methods
class Devices < Array
def find_by_id(id)
self.find { |d| d.idx == id.to_s }
end
def find_by_topic(t)
self.find { |d| d.topic == t }
end
end
class Mqtt < MQTT::Client
def initialize opts
super()
self.host = opts[:host]
self.port = opts[:port]
self.username = opts[:user]
self.password = opts[:passwd]
self.base = opts[:topic]
# self.client_id = opts[:id]
# self.port=8883 # over SSL
# self.ssl = false # when true trigger : in `connect': SSL_connect SYSCALL returned=5 errno=0 state=SSLv2/v3 read server hello A (OpenSSL::SSL::SSLError)
end
def base= base
@base=base
end
def publish topic, value, retain=false
# puts "publish() : #{topic} / #{value}"
super(topic, value, retain)
end
end
class MqttGw
def initialize opts
@opts=opts
self.initialize_domoticz_api @opts[:domoticz]
self.load_devices
# @mqtt = self.initialize_mqtt
end
# Domoticz API initialization
def initialize_domoticz_api opts
Domoticz.configure do |config|
config.server = opts[:server]
config.username = opts[:username]
config.password = opts[:password]
config.ssl_verify=opts[:verify_ssl]
end
end
def initialize_mqtt
Mqtt.new @opts[:mqtt]
end
# get devices from Domoticz JSON API
# keep only devices containing description with an MQTT topic (mqtt://my/sensor/topic)
def load_devices
@devices = Devices.new(Domoticz::Device.all.select { |d| d.Description.match?(/mqtt\:\/\/(.*)/)})
end
def run
# self.run_test
topics=[
@opts[:mqtt][:topic], # viessmann mqtt device gateway or you device topic
@opts[:domoticz][:topics][:out] # MQTT topic messages from Domoticz
]
# base_topic=@opts[:mqtt][:topic]
self.run_mqtt_test topics
end
def run_mqtt_test topics
begin
@mqtt = self.initialize_mqtt
@mqtt.connect() do |mqtt|
puts "mqtt connected"
topics.each do |t|
mqtt.subscribe(t)
end
while true do
mqtt.get do |topic,message|
self.mqtt_process_message mqtt, topic, message
end
end
end
rescue MQTT::ProtocolException => e
puts "MQTT::ProtocolException : "
pp e
puts "trying to reconnect ..."
sleep 3
retry
rescue StandardError => e
puts "StandardError : "
pp e
sleep 5
retry
end
end
def mqtt_process_message mqtt, topic, message
case topic
when /v1\/home\/objects\/viessman\/(.*)/
_t=topic.sub "v1/home/objects/viessman/data/", ''
d=@devices.find_by_topic _t
unless d==nil
puts "# '#{_t}'=#{message} (idx:#{d.idx})"
_req={
:idx => d.idx.to_i,
:svalue => message.to_s
}
mqtt.publish('domoticz/in', _req.to_json)
else
puts "# unconfigured topic (on Domoticz):" + _t
end
# this topic should not occured
# when /domoticz\/in/
# puts "topic : domoticz/in"
when /domoticz\/out/
puts "topic : domoticz/out : #{message}"
end
end
def run_test
@devices.each do |d|
puts "idx: " + d.idx
puts "type: " + d.type
puts "description: " + d.description
puts "topic: " + d.topic
end
d=@devices.find_by_id(2)
puts "idx: " + d.idx
puts "type: " + d.type
puts "description: " + d.description
puts "topic: " + d.topic
d=@devices.find_by_topic('settings/power/mode')
puts "idx: " + d.idx
puts "type: " + d.type
puts "description: " + d.description
puts "topic: " + d.topic
d=@devices.find_by_topic('settings/power/mode/unknown')
pp d
exit 0
end
end
$opts={
:domoticz => {
# :server => "https://127.0.0.1:8443/",
:server => "http://127.0.0.1:8080/",
:user => nil,
:passwd => nil,
:verify_ssl => false,
:topics => {
:in => 'domoticz/in',
:out => 'domoticz/out'
}
},
:mqtt => {
:host => "192.168.0.182",
:port => 1883,
:user => "user",
:passwd => "some-password",
# :topic => 'v1/home/objects/viessman/#' # base topic to subscribe to
:topic => '#' # base topic to subscribe to
}
}
# you can write your local configuration in $HOME/.config/domoticz-mqtt.rb ; this will override $opts values
inc=sprintf("%s/.config/domoticz-mqtt-gw.rb", ENV['HOME'])
require inc if File.exists? inc
# at_exit do
# puts "script exiting ... ; time: " + Time.now.strftime("%d/%m/%Y %H:%M\n")
# end
gw = MqttGw.new $opts
gw.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment