MQTT Client Plugin

Thanks. How are you publishing the miflora data to mqtt?

Im using GitHub - ThomDietrich/miflora-mqtt-daemon: Linux service to collect and transfer Xiaomi Mi Flora plant sensor data via MQTT to your smart home system, with cluster support 🌱🌼🥀🏡🌳 together with Eclipse Mosquitto Broker

Looks cool. So the data goes from Miflora(BLE)->Miflora-mqtt-daemon->mosquitto->vera-mqtt so therein lies the opportunity. Not sure what you are running your miflora-mqtt-daemon on, but one option is to also run nodered to organize the miflora measurements into a payload more convenient for the vera-mqtt plugin to display the combined data on a single virtual device. It would look something like this Miflora(BLE)->Miflora-mqtt-daemon->mosquitto->nodered->mosquitto->vera-mqtt. Using nodered, you would configure it to consume miflora messages from mosquitto, tidy up the json they way you would like and republish it to mosquitto using a topic that the vera-mqtt client subscribes to. I also noticed that there is a miflora node available for nodered so you could use nodered to collect the miflora measurements directly so you wouldn’t need to run miflora-mqtt-daemon at all.

The reason that I only publish data from the vera using vera-mqtt is because I have a nodered instance subscribed to the vera mqtt messages that streams the measurements to an influxdb instance for visual analysis using Grafana. Sounds complicated but it is actually very simple because nodered is so flexible. It would be a great way to view your miflora measurements unless your goal is to use the miflora measurements as triggers on the Vera.

sound good. I am actually familiar with influxdb, grafana and telegraf but for server utilization observation. But it might be a way to go to visualize, as you say. I will definately look into the miflora-nodered tonight. might be a better solution. though the main problem to solve would be how to structure the data to somehow make it better for vera-mqtt.

Hi Blacey, i solved it using your idea. miflora-mqtt->node red-> http request to vera. i think it was the quickest way, as my programming skills are somewhat limited :-).

// Marcus

1 Like

Hi Marcus, excellent and congrats! Also, I haven’t personally used it but there is also a NodeRed Vera/MIOS node available and NodeRed users active on the Vera forums here. If you so desire, it looks like you can replace the http request with the MIOS/Vera Node node.

Thanks a bunch. I will totally look into that.

Hi @vosmont and others. I’ve had the SensorMqtt plugin (vosmont’s version 0.3) for a good while on VeraPlus and it works great. Now I’m trying to get it to work on openLuup and I’m running into a problem.

I’m not a lua person so I don’t really understand this error. I’m running openLuup on a fairly minimal Alpine Linux 3.7 and it’s possible I’m just missing a dependency. But I don’t know how to figure that out.

root@alpine-openluup-766746687c-b22j9:/etc/cmh-ludl# tail -F logs/LuaUPnP.log|grep -i mqtt
tail: logs/LuaUPnP.log has been replaced; following end of new file
2019-10-01 16:28:26.082 openLuup.scheduler:: [33] MQTT Client device startup
2019-10-01 16:28:26.083 luup_log:33: SensorMqtt: Loading plugin module L_SensorMqtt1 …
2019-10-01 16:28:26.097 luup_log:33: SensorMqtt: Plugin module L_SensorMqtt1 loaded
2019-10-01 16:28:26.097 luup_log:33: SensorMqtt: Initializing SensorMqtt
2019-10-01 16:28:26.101 luup.variable_set:: 33.urn:upnp-sensor-mqtt-se:serviceId:SensorMqtt1.mqttServerConnected was: 0 now: 0 #hooks:0
2019-10-01 16:28:26.102 luup_log:33: SensorMqtt: Connecting as MQTT client: Vera-88800000 to mqttServerIp: $$$.cloudmqtt.com mqttServerPort: 1$$$…
2019-10-01 16:28:26.102 luup_log:33: SensorMqtt: Authenticating with username: Vera$$$$$
2019-10-01 16:28:26.102 openLuup.context_switch:: ERROR: /usr/share/lua/5.1/mqtt_library.lua:244: attempt to index global ‘socket’ (a nil value)
2019-10-01 16:28:26.102 openLuup.scheduler:: job aborted : /usr/share/lua/5.1/mqtt_library.lua:244: attempt to index global ‘socket’ (a nil value)

My only guess is that something should be providing the global ‘socket’ object. This is the line of code that tanks:

self.socket_client = socket.connect(self.hostname, self.port)

I’ve installed lua5.1 lua-socket lua-filesystem lua-sec but like i said I might need more. The rest of openLuup is working fine.

Thanks!

In the early part of /usr/share/lua/5.1/mqtt_library.lua I’ll bet you find this:

if (not isPsp()) then
  require("socket")
  require("io")
  require("ltn12")
--require("ssl")
end

Some versions of Lua will define a global with the name of the loaded module when using require, and some do not. Lua on my Veras does, but on my Ubuntu VM that I regularly use for development (and openLuup) it does not. To fix:

if (not isPsp()) then
  socket = require("socket")
  io = require("io")
  ltn12 = require("ltn12")
--ssl = require("ssl")
end

(I put the fix in the comment even though it won’t be used, just for consistency)

Yes, and thanks for that. After banging on this for a few more hours I had concluded similarly that the “if not Psp” block wasn’t setting a global collection, therefore there was no global. What is Psp anyway?

But that’s interesting that some luas will push require to a global and some not.

I got socket and therefore the connection to work, but after a bit there is a new error. So onto the next puzzle I guess.

ERROR: files/L_SensorMqtt1.lua:331: attempt to index upvalue ‘alias’ (a boolean value)

2019-10-01 19:26:28.248   luup_log:33: SensorMqtt: Watch event - device: 10320 variable: ArmedTripped value 0 => 1
2019-10-01 19:26:28.248   openLuup.context_switch::  ERROR: files/L_SensorMqtt1.lua:331: attempt to index upvalue 'alias' (a boolean value)
2019-10-01 19:26:28.248   openLuup.scheduler:: 10320.urn:micasaverde-com:serviceId:SecuritySensor1.ArmedTripped ERROR files/L_SensorMqtt1.lua:331: attempt to index upvalue 'alias' (a boolean value) function: 0x882820
2019-10-01 19:26:32.900   luup_log:33: SensorMqtt: Connection down: socket_client:receive(): closed
2019-10-01 19:26:32.901   luup_log:33: SensorMqtt: MQTT connection status changed from "Connected" to "Disconnected"
2019-10-01 19:26:32.901   luup.variable_set:: 33.urn:upnp-sensor-mqtt-se:serviceId:SensorMqtt1.mqttServerStatus was: Connected now: Disconnected #hooks:0
2019-10-01 19:26:32.901   luup.variable_set:: 33.urn:upnp-sensor-mqtt-se:serviceId:SensorMqtt1.mqttServerConnected was: 1 now: 0 #hooks:0
2019-10-01 19:26:38.071   luup_log:33: SensorMqtt: Connection down: /usr/share/lua/5.1/mqtt_library.lua:382: MQTT.client:handler(): Not connected
2019-10-01 19:26:43.109   luup_log:33: SensorMqtt: Connection down: /usr/share/lua/5.1/mqtt_library.lua:382: MQTT.client:handler(): Not connected

Everything working great now.

Hmm The subscribe functionality of this plugin could be used to get data from Zwave2MQTT as it is still work in progress, I am considering to create a device bridge from openLuup to an instance of Zwave2Mqtt so as to replace the vera with a much more open and reliable alternative with that project turns out to work well and indeed better than where the vera is currently going.

I did make a small change to L_SensorMqtt1.lua to re-subscribe to subscribed topics after reconnecting to the broker if the connection was lost for any reason. I was having a problem with my broker disconnecting me. I ditched the broker and built my own instead but I still think this is a useful change. 3 lines added after the “connectToMqtt()”


– Publish an MQTT message


function publishMessage(topic, payload)

    log_debug("Publish topic: " .. topic .. " message:" .. payload)

    -- If we aren't connected for some reason, then connect first
    if not connectedToBroker() then
            connectToMqtt()
            if connectedToBroker() then
                    Subscriptions.retrieve()
            end
    end

    -- Try to publish.  Mqtt standard is fire and forget on publishing.
    local ok, result = pcall(mqttClient.publish, mqttClient, topic, payload)
    if ( not ok ) then
            log_warn("Unable to publish, connection down.  Discarding message: " .. payload)
            setConnectionStatus()
    end

end

I also created a simple Reactor group that watches the connected status and initiates the equivalent of a sensor trip once per minute until the connection is restored. Since the sensor trips are published a reconnect will be attempted each time. This helps minimize any ‘outage’.

1 Like

I have the plugin running on openLuup without any error messages. I have created 4 virtual switches, each with the MQTT device as the parent device. Each switch subscribes to a different topic (all topics are individual tasmota switch statuses) on my MQTT broker, Mosquito. I can see in the log that there is a recognized payload: the payload for each switch is simply “ON” or “OFF”.

However, I cannot get the virtual switch (which represents the tasmota switch), to toggle on or off when a payload arrives.

The bit of example code included in the plugin readme, that represents a “lua formula”, has me completely baffled:

urn:upnp-org:serviceId:SwitchPower1,Status=payload.value and ((payload.value=="alarm") and "1" else "0")

I have tried:

urn:upnp-org:serviceId:SwitchPower1,Status=payload.value and ((payload.value=="ON") and "1" else "0")
urn:upnp-org:serviceId:SwitchPower1,Status= if (payload.value=="ON") then "1" else "0" end

or changing the variable type… then 1 else 0 end etc. Nothing seems to work, and I don’t understand the syntax of the formula in the context of what the readme says the formula is supposed to do: ie turn the virtual switch on or off.

Can anyone help here.

The fragment you have provided is not valid Lua, so I’m presuming that these are parameters to a function call?

This bit:

payload.value and ((payload.value=="alarm")

…is simply a conditional which is true if the value of the payload is “alarm”, but this will never be so since your payload value is “ON” or “OFF”.

Can we see a bit more of the code to fully understand what you need to do here?

AK

Hey AK,

Yes, these are parameters to a function call. However, it’s not clear what the function does, the function’s syntax, and any formatting that is required. The function is described in the readme as follows:

You can subscribe to a topic by creating a child device :

  1. Add a new device and choose a type (e.g. “D_BinaryLight1.xml” or “D_TemperatureSensor1.xml”).
  2. Reload LUUP engine.
  3. Change the attribut “id_parent” with the id of the MQTT plugin device.
  4. Reload LUUP engine and refresh your browser.
  5. You should see variables “mqttTarget” and “mqttTopic” in your newly created device.
  6. Set the topic you want to subcribe to and the target (format: service,variable=(formula in LUA)).
  7. Reload LUUP engine.

If the payload of the received message is in JSON, the plugin will try to decode it and put it in the variable “payload” in the context of the LUA formula.

Examples :

Topic  : Test/#
Target: urn:upnp-org:serviceId:SwitchPower1,Status=payload.value and ((payload.value=="alarm") and "1" or "0")

On a message from topic ‘Test/Something’ with payload ‘{“value”:“alarm”}’, the switch will be powered on.

Topic  : Test/+/Sensor
Target: urn:upnp-org:serviceId:TemperatureSensor1,CurrentTemperature=payload.temperature and (tonumber(payload.temperature) or "0")

On a message from topic ‘Test/Something/Sensor’ with payload "{"temperature “:“15.2”, “hygrometry”:“80”}”, the temperature will be set to 15,2.

The function seems to be stored in a table called subscriptions in the “L_SensorMqtt1.lua” file at around line 460:

 Subscriptions = {

	addToIndex_ = function( subscription, subIndex, subTopics )
		if ( #subTopics == 0 ) then
			if subIndex["*"] then
				table.insert( subIndex["*"], subscription )
			end
			return
		end
		local subTopic = table.remove( subTopics, 1 )
		if ( subTopic == "" ) then
			return
		end
		if ( subIndex[ subTopic ] == nil ) then
			subIndex[ subTopic ] = { ["*"] = {} }
		end
		if ( ( subTopic == "#" ) or ( #subTopics == 0 ) ) then
			table.insert( subIndex[ subTopic ]["*"], subscription )
		else
			Subscriptions.addToIndex_( subscription, subIndex[ subTopic ], subTopics )
		end
	end,

	retrieve = function()

		log_debug("************************************************ Subscriptions Settings ***************************************")

		mqttSubscriptions = {}
		mqttIndexTopics = {}
		local topics = {}
		local strError
		for deviceId, device in pairs( luup.devices ) do
			if ( device.device_num_parent == DEVICE_ID ) then
				local subscription = {
					deviceId = deviceId,
					topic    = getVariableOrInit( deviceId, SERVICE_ID, "mqttTopic", "" ),
					target   = getVariableOrInit( deviceId, SERVICE_ID, "mqttTarget", "" )
				}
				-- service,variable=formula
				-- eg for topic /test/, payload {"value":"alarm"}
				-- urn:upnp-org:serviceId:SwitchPower1,Status=payload.value and ((payload.value=="alarm") and "1" or "0")
				subscription.service, subscription.variable, subscription.formula = subscription.target:match( "^(.*),([^=]*)=?(.*)$" )
				if ( subscription.service == "" ) then
					subscription.service = nil
				end
				if ( subscription.variable == "" ) then
					subscription.variable = nil
				end
				if ( subscription.formula == "" ) then
					subscription.formula = nil
				end
				log_debug( "Device #" .. tostring( subscription.deviceId ) .. ", topic=" .. subscription.topic .. ", service=" .. tostring(subscription.service) .. ", variable=" .. tostring(subscription.variable) .. ", formula=(" .. tostring(subscription.formula) .. ")" )
				if ( subscription.formula ) then
					-- Try to prepare the LUA code of the formula
					subscription.luaFormula, strError = loadstring( "return " .. subscription.formula )
					if ( subscription.luaFormula == nil ) then
						log_error( "Error in target LUA formula: " .. tostring( strError ) )
					else
						-- Put the LUA code in a sandbox
						subscription.jail = { tonumber = tonumber, tostring = tostring }
						setfenv( subscription.luaFormula, subscription.jail )
					end
				end
				table.insert( mqttSubscriptions, subscription )
				if ( subscription.topic ~= "" ) then
					table.insert( topics, subscription.topic )
				end
				-- Add to index
				Subscriptions.addToIndex_( subscription, mqttIndexTopics, string_split( subscription.topic or "", "/" ) )
			end
		end
		subscribeMqttTopics( topics )
	end,

	getFromIndex_ = function( subIndex, subTopics )
		if ( #subTopics == 0 ) then
			return subIndex["*"] or {}
		end
		local subTopic = table.remove( subTopics, 1 )
		local subscriptions = {}
		if subIndex["#"] then
			table_append( subscriptions, subIndex["#"]["*"] )
		end
		if subIndex["+"] then
			table_append( subscriptions, Subscriptions.getFromIndex_( subIndex["+"], subTopics ) )
		end
		if subIndex[ subTopic ] then
			table_append( subscriptions, Subscriptions.getFromIndex_( subIndex[ subTopic ], subTopics ) )
		end
		return subscriptions
	end,

	get = function( topic )
		return Subscriptions.getFromIndex_( mqttIndexTopics, string_split( topic or "", "/" ) )
	end
}

What I cannot see is how to structure the function syntax to turn its host object (virtual switch) off and on. I suppose I could just create my own variable with the function, and then use that variable in a reactor sensor to do the on/off work with a set target action, but this seems clumsy. It’s a nice plugin, with even greater potential, but as is often the case with plugins, specifics are learned the hard way.

Any help is appreciated.

This is what I use to control a virtual switch. This is what is in the mqttTarget variable of the virtual switch

urn:upnp-org:serviceId:SwitchPower1,Status=payload.value

I have a Mqtt client app on my phone that will send a message of either {“value”:“1”} or {“value”:“0”} depending on the state of a button on the app. This is about the simplest use case.

To make use of the state of the virtual switch I can use a variable watch in a scene or a reactor group that does the same thing. Using a trigger in a scene will not work.

If the Mqtt message uses (for example) “ON” and “OFF” instead of “1” and “0”, the function in mqttTarget gets a little more complex.

urn:upnp-org:serviceId:SwitchPower1,Status=((payload.value=="ON") and "1") or ((payload.value=="OFF") and "0") or "0"

For the virtual switch the function must result in a “1” or a “0”. Note these are strings and not numbers. Numbers won’t work. I learned that the hard way.

You mentioned that your payload is either ON or OFF. Does that mean that your actual Mqtt message is {“value”:“ON”} or {“value”:“OFF”}? Or maybe just {“ON”} or {“OFF”}? Or maybe just “ON” or “OFF”?

The plugin examples all follow the first pattern, which is a representation of a JSON object. I have not tried the second or third patterns, though I may give it a try to see what happens.

I have a few other Mqtt fed virtual sensors or devices also and I can show working examples if you’re interested. A few of these required some scene lua also.

mqtt virtual switch (of course)
mqtt virtual temperature sensor
mqtt virtual humidity sensor
mqtt multi-string container
mqtt multiswitch

Hope this helps you out! --D

Hey, thanks, that definitely helps.

Here’s the log entry for the payload:

2019-11-12 12:37:34.710 luup.variable_set:: 227.urn:upnp-sensor-mqtt-se:serviceId:SensorMqtt1.mqttLastReceivedPayload was: ON now: ON #hooks:0

2019-11-12 12:37:34.710 luup_log:227: SensorMqtt: Receive topic: stat/power_main_vera/POWER payload:ON

When I look at the payload in MQTT.fx (an MQTT inspector), there are no field names associated with the payload value, so I believe that the value of “ON” or “OFF” is all that is being sent by Mosquitto.

I tried your revised conditional formula, but it does not seem to pick up on the payload as the “Status” variable remains unchanged at “0”.

Hi,

I am using the MQTT plugin for some time and there was absolutely no issues with the plugin. However I have faced one issue yesterday. I am not able to find out how to change it.

Situation in general:

I am using the “watchdog variables” from Vera plugin (monitoring the hard disk space, free, used, total) in order to sent data to broker and later on input it to Influx for Grafana purposes. So far so good.
However if there is no any changes in the values, nothing is sent towards broker. Normally I am OK with this, but it is crashing all the graphs in Grafana. No new input, no lines in graph. There is only 1 point on the graph and no second point to create the graph.

Is it possible to configure that periodically (for example - 1 time per some 12h) update will be sent, even if there is no update.

Thanks for any hints and solutions.

A simple general solution to this problem, which should work for any plugin with this issue, is to set up a scheduled scene which reads the variables in question, and modifies them (without changing their numerical value.)

However, this does depend on how the plugin determines that the value has not changed.

How to change the variable without changing its value? Since all device variables are, in fact, strings, then you can just add a blank space to the tail of the variable. Try this out manually, first, and see if it triggers an MQTT update.