on_disconnect mqtt client side not ever being called - mqtt

I am trying to monitor an mqtt broker so I can fire a notification if there is a connection interruption.
My approach was to create a cloud client who does nothing besides monitor the broker. "on_disconnect" seems like the appropriate method however I cannot get it to trigger. (I have been loading and unloading the broker service in a different terminal).
The method is a skeleton:
import random
import time
def RepresentsInt(s):
try:
int(s)
return True
except ValueError:
return False
def on_message(client, userdata, message):
print message
def on_connect(client, userdata, flags, rc):
print("Connected with result code "+str(rc))
mqttc.subscribe('control/iterate',qos=0)
def on_disconnect(client, userdata, rc):
print("Disconnected")
mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_message = on_message
mqttc.connect('10.147.17.234', port=1883, keepalive=1)
print("test")
mqttc.loop_forever()
Obviously there are easy ways to do this but I feel like there is an elegant solution to this problem that I am just missing.

You've not actually added the on_disconnect call back in your code:
import random
import time
def RepresentsInt(s):
try:
int(s)
return True
except ValueError:
return False
def on_message(client, userdata, message):
print message
def on_connect(client, userdata, flags, rc):
print("Connected with result code "+str(rc))
mqttc.subscribe('control/iterate',qos=0)
def on_disconnect(client, userdata, rc):
print("Disconnected")
mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_message = on_message
#added the following line
mqttc.on_disconnect = on_disconnect
mqttc.connect('10.147.17.234', port=1883, keepalive=1)
print("test")
mqttc.loop_forever()

Related

MQTT Broker does not deliver the messages sent by publisher, on time

I wrote this MQTT publisher code:
import paho.mqtt.client as mqtt
import time
HOST = "localhost"
PORT = 1883
KEEP_ALIVE_INT = 100
TOPIC = "noti"
def sendMsg():
MSG = ["1111", "2222", "3333", "4444", "5555"]
i = 0
try:
while i<5:
client.publish(TOPIC, MSG[i], qos=0)
i+=1
time.sleep(1)
except Exception as e:
print("Caught Exception: " + e)
def onConnect(client, userdata, flags, rc):
if rc == 0:
print("Connected successfully")
sendMsg()
else:
print("Connection failed, result code: " + str(rc))
def onPublish(client, userdata, mid):
print ("Message is published")
client = mqtt.Client("pub")
client.on_connect = onConnect
client.on_publish = onPublish
client.connect(HOST, PORT, KEEP_ALIVE_INT)
client.loop_forever()
And, the following is the MQTT subscriber code:
import paho.mqtt.client as mqtt
import time
HOST = "localhost"
PORT = 1883
KEEP_ALIVE_INT = 100
TOPIC = "noti"
def onConnect(client, userdata, flags, rc):
if rc == 0:
print("=> Connected successfully")
client.subscribe(TOPIC, 0)
else:
print("=> Connection failed, result code: " + str(rc))
def onSubscribe(mosq, obj, mid, granted_qos):
print ("=> Subscribed to topic: " + TOPIC)
print ("Granted QOS: "+str(granted_qos))
def onMessage(client, userdata, msg):
print("=> Received message: " + msg.topic +" - " + msg.payload.decode("utf-8"))
client = mqtt.Client("sub")
client.on_message = onMessage
client.on_connect = onConnect
client.on_subscribe = onSubscribe
client.connect(HOST, PORT, KEEP_ALIVE_INT )
client.loop_forever()
I am using Mosquitto broker in my PC.
The publish is done in every 1 second, but I can see the print "Message is published" 5 times after all 5 messages are published. Also, the subscriber receives the messages together after 5 seconds, not in every 1 second.
Please help me understand the mistake, suggest modification.
This is because all callbacks and message handling happen on the client network loop thread and you are blocking that thread by not returning from the on_connect() callback.
So the calls to client.publish() are queued up until the on_connect() callback returns.
You need to find a way to trigger the sendMsg() function not on the client loop. (Probably on a separate thread)

async endpoints for swagger generated server using tornado

I have generated a server using swagger editor. Then I move to using Tornado as http-server like:
def main():
app = App(system_manager=system_manager, import_name=__name__,
specification_dir='./swagger/', server='tornado')
app.app.json_encoder = encoder.JSONEncoder
app.add_api('swagger.yaml', arguments={
'title': 'API'}, pythonic_params=True)
app.run(port=8085)
Where App is:
class App(connexion.App):
def __init__(self, system_manager, import_name, server='tornado', **kwargs):
super(App, self).__init__(import_name, server=server, **kwargs)
if not issubclass(type(system_manager), SystemManager):
raise ValueError(
"App.init: 'system_manager' is not a subclass of 'SystemManager'")
self.__system_manager = system_manager
def run(self, port=None, server=None, debug=None, host=None, **options):
server_type = server or self.server
if server_type != 'tornado':
super(App, self).run(port=port, server=server,
debug=debug, host=host, **options)
return None
if port is not None:
self.port = port
elif self.port is None:
self.port = 5000
self.host = host or self.host or '0.0.0.0'
if server is not None:
self.server = server
if debug is not None:
self.debug = debug
wsgi_container = tornado.wsgi.WSGIContainer(self.app)
http_server = tornado.web.Application([
(r'/websocket/.*', WebSocket, dict(system_manager=self.__system_manager)),
(r'^/v1/wifi(/all)*$', AsyncFallbackHandler,
dict(fallback=wsgi_container)),
(r'.*', tornado.web.FallbackHandler, dict(fallback=wsgi_container))
], websocket_ping_interval=5)
http_server.listen(self.port, address=self.host)
tornado.ioloop.IOLoop.instance().start()
For some reasons I have some endpoints that are taking ~30secs to respond and because I am using WSGIContainer all the requests are synchronous. Which means that every request comming after those ones will be dealyed until they are done. Cite from documentation:
WSGI is a synchronous interface, while Tornado's concurrency model is
based on single-threaded asynchronous execution. This means that
running a WSGI app with Tornado's WSGIContainer is less scalable than
running the same app in a multi-threaded WSGI server like gunicorn or
uwsgi.
I have tried to:
Keep using WSGIContainer but in a handler that will make calls async. Didn't work out. I'm getting: RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-0_0'
class AsyncFallbackHandler(tornado.web.RequestHandler):
def initialize(
self, fallback: Callable[[httputil.HTTPServerRequest], None]
) -> None:
self.fallback = fallback
async def prepare(self, *args, **kwargs):
await self.run_in_executor()
self._finished = True
self.on_finish()
async def run_in_executor(self):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, self.fallback, self.request)],
return_when=asyncio.ALL_COMPLETED
)
Create another RequestHandler which is not using WSGIContainer. But here when the request is 404 it fails to json encode ConnexionResponse. Neither I can write a ConnexionResponse to pipe as it has to be string/bytes/dict.
class WifiRequestHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
# await tornado.gen.sleep(20)
ids = self.get_arguments('ids')
method = get_wifi
if self.request.path.startswith('/v1/wifi/all'):
method = get_wifi_all
self.write(await self.run_in_executor(method, ids))
self.set_header('Content-Type', 'application/json')
async def post(self, *args, **kwargs):
logger.info(kwargs)
body = tornado.escape.json_decode(self.request.body)
self.write(await self.run_in_executor(update_wifi, body))
self.set_header('Content-Type', 'application/json')
async def run_in_executor(self, method, *args):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, method, args)],
return_when=asyncio.ALL_COMPLETED
)
result = done.pop().result()
if type(result) is ConnexionResponse:
return result
enc = JSONEncoder()
return enc.encode(result)
Please help me find a way to make some of my endpoints asynchronous
I made my second solution to work by unpacking the ConnexionResponse and setting the status_code. This is how it looks after fix:
async def run_in_executor(self, method, *args):
loop = tornado.ioloop.IOLoop.instance().asyncio_loop
done, pending = await asyncio.wait(
fs=[loop.run_in_executor(None, method, args)],
return_when=asyncio.ALL_COMPLETED
)
result = done.pop().result()
if type(result) is ConnexionResponse:
self.set_status(result.status_code)
return result.body
enc = JSONEncoder()
return enc.encode(result)

Messages in Django Channels sent outside consumer not being received

I'm trying to send messages to specific websocket instances, but neither channel_layer.send, nor using channel_layer.group_send with unique groups for each instance seems to be working, no errors are being raised, they just aren't received by the instances.
The function that sends the message is:
def listRequest(auth_user, city):
request_country = city["country_name"]
request_city = city["city"]
request_location = request_city +", "+request_country
concatenate_service_email = auth_user.service + "-" + auth_user.email
this_request = LoginRequest(service_and_email=concatenate_service_email, location=request_location)
this_request.generate_challenge()
this_request.set_expiry(timezone.now() + timezone.timedelta(minutes=5))
this_request.save()
channel_layer = get_channel_layer()
print(auth_user.current_socket)
async_to_sync(channel_layer.group_send)(
auth_user.current_socket,{
"type": "new.request",
"service_and_email" : concatenate_service_email
},
)
My current working consumers.py (receive and scanrequest don't have anything that's likely to be relevant to the issue):
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .helpers import verify_identity, unique_group
from django.utils import timezone
from .models import Authentication, LoginRequest
import json
import time
class AuthConsumer(WebsocketConsumer):
account_list =[]
def connect(self):
print("Connect attempted")
print(self.channel_name)
print(unique_group(self.channel_name))
async_to_sync(self.channel_layer.group_add)(unique_group(self.channel_name), self.channel_name)
self.accept()
def disconnect(self, close_code):
print("Disconnect attempted")
async_to_sync(self.channel_layer.group_discard)(unique_group(self.channel_name), self.channel_name)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket("NONE")
auth_user.save()
except:
print("Error user %s does not exist" %i)
pass
def receive(self, text_data):
print("Receiving data")
if text_data[0:7] == "APPROVE":
data_as_list = text_data.split(",")
serviceEmailSplit = data_as_list[1].split("-")
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
this_request = LoginRequest.objects.get(service_and_email=data_as_list[1],approved=False, expiry__gt=timezone.now())
if verify_identity(auth_user.public_key, data_as_list[2], this_request.challenge):
this_request.set_approved()
self.send("Request Approved!")
else:
self.send("ERROR: User verification failed")
else:
self.account_list = text_data.split(",")
self.account_list.pop(-1)
print(self.account_list)
for i in self.account_list:
serviceEmailSplit = i.split("-")
try:
auth_user = Authentication.objects.get(service=serviceEmailSplit[0],email=serviceEmailSplit[1])
auth_user.set_socket(unique_group(self.channel_name))
auth_user.save()
except:
self.send("Error user %s does not exist" %i)
self.scanRequest()
def scanRequest(self):
requestSet = LoginRequest.objects.filter(service_and_email__in = self.account_list, approved = False, request_expiry__gt = timezone.now())
if requestSet.count() > 0:
for request in requestSet:
self.send(request.service_and_email+","+request.location+","+str(request.challenge))
else:
self.send("NOREQUESTS")
def new_request(self,event):
print("NEW REQUEST!")
this_request = LoginRequest.objects.filter(service_and_email = event["service_and_email"]).latest('request_expiry')
self.send(this_request.service_and_email+","+this_request.location+","+str(this_request.challenge))
And my routing.py:
from django.urls import re_path
from . import consumers
from django.conf.urls import url
websocket_urlpatterns = [
url(r"^ws/$", consumers.AuthConsumer.as_asgi()),
]
"NEW REQUEST!" is never printed, having tried to call it both by sending a message directly, and neither does using groups like I have written above.
My redis server appears to be working from testing like the documentation for the channels tutorial suggests:
https://channels.readthedocs.io/en/stable/tutorial/part_2.html
I'm pretty stumped after attempts to fix it, and I've looked at the other posts on stackoverflow with the same/similar issues and I'm already following whatever solutions they have in my code.

Python-socketio: How to emit message regulary from server to client?

I want to send a message from server to client at 1 second intervals,
writing the string 'send clock' to console before the message is sent.
However, client does not receive "clock" message.
I have got the echo message is working fine, so it's not a connection issue.
How can I send a message at 1 second intervals?
Server code(python):
import threading
import time
import socketio
import eventlet
socket = socketio.Server(async_mode='eventlet')
app = socketio.WSGIApp(socket)
#socket.on('echo')
def echo(sid, message):
socket.emit('echo', message)
def worker1():
eventlet.wsgi.server(eventlet.listen(('', 5030)), app)
def worker2():
while(1):
print("send clock")
socket.emit('clock', '1 sec')
time.sleep(1)
def main():
t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)
t1.start()
t2.start()
if __name__ == '__main__':
main()
Client code(nodejs):
const io = require("socket.io-client");
socket = io("http://xxx.xxx.xxx.xxx:5030", {
transports: ["websocket"],
});
socket.emit("echo", "test");
socket.on("echo", (data) => {
console.log(data);
});
socket.on("clock", (data) => {
console.log("receive clock");
});
My server side environment:
python: 3.7.3
python-socketio: 4.5.1
eventlet: 0.25.1
Thank you for reading it until the very end. <3
You can't combine threads or the sleep() function with eventlet, you should use greenlet based equivalents instead. The easiest way to do this is to start your background tasks with the socket.start_background_task() helper function, and sleep with socket.sleep(). I'm writing this by memory so there might be small mistakes, but your application could do something like the following:
import socketio
import eventlet
socket = socketio.Server(async_mode='eventlet')
app = socketio.WSGIApp(socket)
#socket.on('echo')
def echo(sid, message):
socket.emit('echo', message)
def worker1():
eventlet.wsgi.server(eventlet.listen(('', 5030)), app)
def worker2():
while(1):
print("send clock")
socket.emit('clock', '1 sec')
socket.sleep(1)
def main():
socket.start_background_task(worker2)
worker1()
if __name__ == '__main__':
main()

Issues with controlling picamera using MQTT messages

I am trying to start a camera recording using Pi Zero W (which acts as MQTT client) upon receiving message and stop the recording on receiving the stop message. Below is my code:
continueRecording = 1
Broker = "192.168.0.105"
pub_topic = "picamera1"
sub_topics = ["Rpi_Master", 0]
def on_connect(client, userdata, flags, rc):
if rc == 0:
pass
else:
print("Bad Connection with result code: " + str(rc))
for topic in sub_topics:
client.subscribe(topic)
def on_message(client, userdata, msg):
global message_topic, message
global continueRecording
message = str(msg.payload.decode("utf-8"))
print("Received message is" + message)
message_start = str(message[:4])
print("Command to start recording is " + message_start)
if message_start == "shop":
print("Enter shop")
with picamera.PiCamera as camera:
camera.resolution = (640, 480)
camera.framerate=20
camera.start_recording("/home/pi/camera-recording/shop/shoprecording.h264")
time.sleep(0.5)
while continueRecording == 1:
camera.wait_recording(.01)
if message == "OK":
print("Stopping to record")
camera.stop_recording()
continueRecording = 0
def on_publish(mosq, obj, mid):
pass
# on mqtt disconnection#
def on_disconnect(client, userdata, rc):
if rc == 0:
pass
elif rc != 0:
print("Unexpected MQTT disconnection. Will try to reconnect")
try:
client.username_pw_set(username="ab", password="abcdef")
client.connect(Broker, 1883, 60)
except:
print("Error in trying to reconnect with the Broker")
# mqtt client broker Connection
def clientBrokerConnection():
print("Client Broker Function Running")
global client
client = mqtt.Client("piCamera1") # creating a new instance
##Defining the callback functions
client.username_pw_set(username="pi", password="lotus56789")
client.on_connect = on_connect
client.on_message = on_message
client.on_publish = on_publish
client.on_disconnect = on_disconnect
##End of callback functions
client.connect(Broker, 1883, 60) # Connecting to Broker
client.loop_start()
clientBrokerConnection()
The issue I am facing is that the Pi is recieving the correct message to start recording but it fails to enter the with picamera.PiCamera as camera: loop and the recording doesn't start. The compiler does not show any error in the code. I am unable understand why the recording doesn't start. I have checked the camera and it works fine. Thanks for your help and time in advance.

Resources