Can I subscribe in an on_subscribe callback? - python-3.x

I have created a function that returns on_subscribe callback to subscribe to multiple topics. However, it didn't work and the callback is not being processed. I did not subscribe in the on_connect callback, is it even necessary?
This the function created in a class
def subscriber(self):
def on_subscribe(client, userdata, mid, granted_qos):
client.subscribe(self._topic_sub) #topic_sub is an attribute of the class and the topic to
#subscribe to
return on_subscribe
def connection(self):
def on_connect(client, userdata, flags, rc):
print("Connection to the broker. Result : "+str(rc))
return on_connect
This is where I am calling the callback, and it is a part of the code
from class_light import Light
from test_class import Mqtt
import paho.mqtt.client as mqtt
import sys
import threading
import time
#from untitled3 import Location
from MongoDB import is_circadian_Light
Broker = "broker.mqttdashboard.com"
username = "growthtechnology"
password = "growthtechnology"
PortaBroker = 1883
KeepAliveBroker = 60
client_name = "Local"
topic_sub = "testtopic/1"
topic_pub = "testtopic/3"
light = Mqtt(topic_sub)
occupancy_sensor =Mqtt([("testtopic/4", 0), ("testtopic/5", 0)])
tunable_light = Light("deviceID")
try:
print("[STATUS] Inicializando MQTT...")
#inicializing MQTT:
client = mqtt.Client()
client.username_pw_set(username, password)
client.on_connect = light.connection()
client.on_message = light.message()
client.on_publish = light.publisher()
client.on_disconnect = light.disconnection()
client.on_subscribe = light.subscriber()
client.on_subscribe = occupancy_sensor.subscriber()
client.connect(Broker, PortaBroker, KeepAliveBroker)
def publishing():
while True:
msg = light.messages # calling for the variable "messages" to get PUBLISH messages
#print(msg)
#time.sleep(3)
topic = light.topic
for i, topics in enumerate(topic):
if topics == "testtopic/4":
if msg[i] == "1":
if is_circadian_Light("device2") == 1 :
client.publish("testtopic/Bedroom",tunable_light.circadianLight())
time.sleep(10)

You've set the on_subscribe callback twice. As discussed in your last question, you can not do that.
client.on_subscribe = light.subscriber()
client.on_subscribe = occupancy_sensor.subscriber()
And as I pointed out in the comments, it doesn't make sense to try and subscribe in the callback that is only triggered when a subscription is successful.

I removed the on_subscribe callback from the function subscriber() and I passed as a parameter client :
def subscriber(self, client):
client.subscribe(self._topic_sub)
then I called the function in main:
client = mqtt.Client()
client.username_pw_set(username, password)
client.on_connect = Mqtt.connection()
client.on_message = light.message()
client.on_publish = Mqtt.publisher()
client.on_disconnect = Mqtt.disconnection()
client.connect(Broker, PortaBroker, KeepAliveBroker)
light.subscriber(client)
occupancy_sensor.subscriber(client)
And now it works well!

Related

how to communicate between regular MQTT and tornado websocket?

Is there any way to make a mqtt client send messages to tornado websocket?
Using this code, tornado works. While from MQTT no message passes.
import os.path
import tornado.httpserver
import tornado.web
import tornado.ioloop
import tornado.options
import tornado.httpclient
import tornado.websocket
import json
import random
from paho.mqtt import client as mqtt_client
broker = 'xxxxxxx'
port = 1883
topic = "xxx/xxxx"
client_id = f'xxxx-{random.randint(0, 100)}'
clients = []
def connect_mqtt() -> mqtt_client:
def on_connect(clientMQTT, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker!")
else:
print("Failed to connect, return code %d\n", rc)
clientMQTT = mqtt_client.Client(client_id)
# clientMQTT.username_pw_set(username, password)
clientMQTT.on_connect = on_connect
clientMQTT.connect(broker, port)
return clientMQTT
def subscribe(clientMQTT: mqtt_client):
def on_message(clientMQTT, userdata, msg):
print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")
send_to_all_clients(msg.payload.decode())
clientMQTT.subscribe(topic)
clientMQTT.on_message = on_message
def runMQTT():
clientMQTT = connect_mqtt()
subscribe(clientMQTT)
clientMQTT.loop_start()
# clientMQTT.loop_forever()
def send_to_all_clients(message):
for clientws in clients:
clientws.write_message(message)
class SocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print("new client")
clients.append(self)
def on_close(self):
clients.remove(self)
print("removing client")
def on_message(self, message):
pass
# for client in utility.clients:
# if client != self:
# client.write_message(msg)
if __name__ == '__main__':
app = tornado.web.Application(
handlers = [
(r"/monitor", SocketHandler)
],
debug = True,
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "static")
)
runMQTT()
app.listen(8200)
tornado.ioloop.IOLoop.instance().start()
I hope someone can give me some help.
Thank you all
If I try to listen on mqtt, with no tornado, it works.
I would like to be able to send messages from mqtt to browsers via tornado websocket.

Django + MQTT how to make request and wait for response in view

I am writing a django application and I need a way how to ask for data on MQTT and wait for response (or timeout).
Something like this in view:
mqtt.subscribe('response/topic')
mqtt.publish('request/topic', '{my json request message}')
for m in mqtt.on_message():
if m.payload == '......'
# OK I have response, render data from response to page, ...
# timeout could be implemented by raising exception in on_message()
How can i integrate paho mqtt? Or is there any other way? I need to wait for response on several places in views, one global on_message callback is not really useful.
Where should I put mqtt.loop_forever() in django?
Thank you
EDIT: my first solution is this:
import queue
import paho.mqtt.client as mqtt
def mqtt_request(req_topic, resp_topic, payload, timeout=2.0):
q = queue.Queue()
def on_message(client, userdata, message):
q.put(message)
def on_connect(client, userdata, flags, rc):
c.subscribe(resp_topic)
def on_subscribe(client, userdata, mid, granted_qos):
c.publish(req_topic, payload=payload, qos=0, retain=False)
c = mqtt.Client()
c.on_message = on_message
c.on_connect = on_connect
c.on_subscribe = on_subscribe
c.connect_async('127.0.0.1')
c.loop_start()
m = None
# FIXME: it seems that there is some delay between on_message and return
# of the message...
try:
m = q.get(timeout=timeout)
except queue.Empty:
pass
c.loop_stop()
return m
usage in view:
r = mqtt_request('test', 'test2', 'Ahoj')
if r:
print(r.payload)

How to reconnect to a websocket when connection fails in python

Hi I created async websocket client which I can receive and send messages asynchronously.
Here is the clint class that I use.
import websockets
import asyncio
from models import Heartbeat
from model_helper import ModelHelper
from json_helper import JSONHelper
from os.path import dirname, abspath
import logging
import time
waiting_time = 0.5
class WebSocketClient():
def __init__(self, websocket_queue, configs):
self.Heartbeat = Heartbeat().message
self.websocket_queue = websocket_queue
self.configs = configs
def get_connection_string(self):
server_url = self.configs.URL + ":" + str(self.configs.Port) + self.configs.Root
return server_url
async def connect(self):
try:
server_url = self.get_connection_string()
self.connection = await websockets.client.connect(server_url)
if self.connection.open:
print("Connection stablished. Client correcly connected")
# Send greeting
await self.connection.send(self.Heartbeat)
return self.connection
else:
print("Can not connect")
except ConnectionRefusedError as err:
print("Connection Error: {}".format(err))
async def send_message_to_socket(self, connection):
while True:
message = self.websocket_queue.get_send_queue()
try:
if message is not None:
message_ = ModelHelper.to_outgoing_request_model(message)
await connection.send(message_)
else:
await asyncio.sleep(waiting_time)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed')
self.connect()
async def receive_message_from_socket(self, connection):
while True:
try:
message = await connection.recv()
obj = JSONHelper.toObject(message)
print("Received object from websocket: {}".format(obj))
#If a websocket entry has SendMessage in its Action property
#Consider it as an sms content to be sent.
if(obj.Action == "SendMessage"):
self.websocket_queue.add_received_queue(obj)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed1')
async def send_heartbeat_to_socket(self, connection):
while True:
#print("Heartbeat loop\n")
try:
await connection.send(self.Heartbeat)
await asyncio.sleep(3)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed2')
And this is the code where I listen for websocket messages:
def listen_websocket_routine(sms_queue, websocket_queue):
while True:
time.sleep(waiting_time)
#Check Websocket queue for new messages
message = websocket_queue.get_received_queue()
# If Incoming websocket JSON has SendMessage string in its Action attribute transform it to request_model
if message is not None and send_sms.Action == "SendMessage":
# Transform it to outgoing_sms_model
reset = ModelHelper.to_outgoing_sms_model(message.Data)
# Add To send_queue of sms
sms_queue.add_send_queue(reset)
finally How I initiate them with asyncio and threading.
client = WebSocketClient(websocket_queue, configs)
loop = asyncio.get_event_loop()
connection = loop.run_until_complete(client.connect())
task1 = asyncio.ensure_future(client.receive_message_from_socket(connection))
task2 = asyncio.ensure_future(client.send_heartbeat_to_socket(connection))
task3 = asyncio.ensure_future(client.send_message_to_socket(connection))
listen_websocket_thread = threading.Thread(target=listen_websocket_routine, args=(sms_queue, websocket_queue))
listen_websocket_thread.start()
loop.run_forever()
So my question is, whenever connetion breaks, I need to re establish the connection. But I am not sure where should I do that. Should it be before everytime I try to send message or receive or should I do that in more general approach?
Since update 10.0 you could do it with an async iterator, like this(example from the official doc):
async for websocket in websockets.connect(...):
try:
...
except websockets.ConnectionClosed:
continue

Send data from an other class through ws after client connected

I want to send data through websockets as soon as a client is connected.
The Data is at an other place then the Websocket Handler. How can i get the data to the client ?
The server should hold the loop and the Handler. In the connector i connect to a tcp socket to get the data out of some hardware. I expect to have not more then 6 Websockets open once a time. The Data comes as a stream out of the TCP socket.
server.py
import os
from tornado import web, websocket
import asyncio
import connector
class StaticFileHandler(web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
def get(self):
self.render('index.html')
class WSHandler(websocket.WebSocketHandler):
def open(self):
print('new connection')
self.write_message("connected")
def on_message(self, message):
print('message received %s' % message)
self.write_message("pong")
def on_close(self):
print('connection closed')
public_root = 'web_src'
handlers = [
(r'/', StaticFileHandler),
(r'/ws', WSHandler),
]
settings = dict(
template_path = os.path.join(os.path.dirname(__file__), public_root),
static_path = os.path.join(os.path.dirname(__file__), public_root),
debug = True
)
app = web.Application(handlers, **settings)
sensorIP = "xxx.xxx.xxx.xxx"
if __name__ == "__main__":
app.listen(8888)
asyncio.ensure_future(connector.main_task(sensorIP))
asyncio.get_event_loop().run_forever()
connector.py
import yaml
import asyncio
class RAMReceiver:
def __init__(self, reader):
self.reader = reader
self.remote_data = None
self.initParams = None
async def work(self):
i = 0
while True:
data = await self.reader.readuntil(b"\0")
self.remote_data = yaml.load(data[:-1].decode("utf-8",
"backslashreplace"))
# here i want to emit some data
# send self.remote_data to websockets
if i == 0:
i += 1
self.initParams = self.remote_data
# here i want to emit some data after open event is
# triggered
# send self.initParams as soon as a client has connected
async def main_task(host):
tasks = []
(ram_reader,) = await asyncio.gather(asyncio.open_connection(host,
51000))
receiver = RAMReceiver(ram_reader[0])
tasks.append(receiver.work())
while True:
await asyncio.gather(*tasks)
You can use Tornado's add_callback function to call a method on your websocket handler to send the messages.
Here's an example:
1. Create an additional method on your websocket handler which will receive a message from connector.py and will send to connected clients:
# server.py
class WSHandler(websocket.WebSocketHandler):
# make it a classmethod so that
# it can be accessed directly
# from class without `self`
#classmethod
async def send_data(cls, data):
# write your code for sending data to client
2. Pass the currently running IOLoop and WSHandler.send_data to your connector.py:
# server.py
from tornado import ioloop
...
if __name__ == "__main__":
...
io_loop = ioloop.IOLoop.current() # current IOLoop
callback = WSHandler.send_data
# pass io_loop and callback to main_task
asyncio.ensure_future(connector.main_task(sensorIP, io_loop, callback))
...
3. Then modify main_task function in connector.py to receive io_loop and callback. Then pass io_loop and callback to RAMReceiver.
4. Finally, use io_loop.add_callback to call WSHandler.send_data:
class RAMReceiver:
def __init__(self, reader, io_loop, callback):
...
self.io_loop = io_loop
self.callback = callback
async def work(self):
...
data = "Some data"
self.io_loop.add_callback(self.callback, data)
...

problems usign paho.mqtt together with python-telegram-bot

Im struggling on get python-telegram-bot working together with paho-mqqt.
something code in updater.start_polling() seems to influence the mqqt thread, that was started thru client.loop_start().
after a a few publishes the mqqt client stops receiving messages.
Does anybody have idea whats going on ?
#!/usr/bin/python3
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
import telegram
import paho.mqtt.client as mqtt
import logging
import time
#...
def start(bot, update):
global client
update.message.reply_text('Hi!')
client.publish("/doorR/test","test")
def on_connect(self,client, userdata, rc):
print("Connected with result code " + str(rc))
self.subscribe("/doorR/#")
def on_message(client, userdata, msg):
print("Topic: ", msg.topic + "\nMessage: " + str(msg.payload))
if msg.topic == "/doorR/status":
bot = telegram.Bot(token=TOKEN)
bot.send_message(chat_id=MYCHATID, text="Funkmodul status geƤndert: " + str(msg.payload))
def main():
global client
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect("127.0.0.1", 1883, 60)
client.loop_start()
updater = Updater(TOKEN)
dp = updater.dispatcher
dp.add_handler(CommandHandler("start", start))
updater.start_polling()
time.sleep(10)
client.loop_stop()
if __name__ == '__main__':
main()

Resources