Last active
September 15, 2018 21:31
-
-
Save mqu/fc33f3b9aa2d7c09244e2d36c6c7302e to your computer and use it in GitHub Desktop.
Domoticz MQTT gateway
This file contains hidden or 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
| #!/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¶m=switchlight&idx=#{idx}&switchcmd=On") | |
| end | |
| def off! | |
| Domoticz.perform_api_request("type=command¶m=switchlight&idx=#{idx}&switchcmd=Off") | |
| end | |
| def toggle! | |
| Domoticz.perform_api_request("type=command¶m=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