简体   繁体   中英

How to use multiple ioloop in tornado and share data between the ioloop(s)?

I have a Beaglebone Black connect to a CAN bus devices: Battery.

A tornado web running on Beaglebone Black as GUI.

CAN bus reading loop keep reading data from CAN bus to update the status of Battery instance

But how can I make the two IOLOOP work together and share Battery instance?

在此处输入图片说明

tornado web:

class Battery(object):
    status = {}



class API_Handler(web.RequestHandler):
    def get(self, dev, cmd):
        if cmd == 'data':
            self.write(self.application.battery0.status)


class Application(web.Application):
    def __init__(self):

        self.battery0 = Battery('bat0')    

        routing = [
            (r'/api/battery/(data|)', API_Handler),
        ]

        settings = {
            'template_path': os.path.join(os.path.dirname(__file__), "templates"),
            'static_path': os.path.join(os.path.dirname(__file__), "static"),
        }

        web.Application.__init__(self, routing, debug=True, **settings)


if __name__ == "__main__":
    import tornado

    app = Application()
    app.listen(address='0.0.0.0', port=8888)
    tornado.ioloop.IOLoop.instance().start()

CAN bus reading loop, code:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import errno
import functools
import tornado.ioloop
import socket
import struct


can_frame_fmt = "=IB3x8s"
can_frame_size = struct.calcsize(can_frame_fmt)

def build_can_frame(can_id, data):
    can_dlc = len(data)
    data = data.ljust(8, b'\x00')
    return struct.pack(can_frame_fmt, can_id, can_dlc, data)

def dissect_can_frame(frame):
    can_id, can_dlc, data = struct.unpack(can_frame_fmt, frame)
    return (can_id, can_dlc, data[:can_dlc])

def connection_ready(sock, fd, events):
    while True:
        try:
            cf, addr = sock.recvfrom(can_frame_size)
        except socket.error as e:
            if e.args[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
                raise
            return
        dissect_can_frame(cf)


if __name__ == '__main__':
    sock = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
    sock.bind(('can0',))
    sock.setblocking(0)

    io_loop = tornado.ioloop.IOLoop.current()
    callback = functools.partial(connection_ready, sock)
    io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
    io_loop.start()

As I can see you are running two applications, so it will be difficult to share instance of Battery. First solution - combine all functionality in one application so Battery instance will be simply available, but you will face challenges with serving HTTP requests and handling socket events from CAN in one ioloop.

So here is another solution, keep two application but not try to share a Battery instance, just make an http request from CAN listener to your GUI app.

For example in CAN file:

from tornado.httpclient import AsyncHTTPClient

...

def connection_ready(sock, fd, events):
    while True:
        try:
           cf, addr = sock.recvfrom(can_frame_size)
        except socket.error as e:
            if e.args[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
                raise
            return
        http_client = AsyncHTTPClient()
        http_client.fetch("http://localhost:8888/update-battery",
                          method="POST",
                          body="can_id={}&can_dlc={}&data={}".format(
                              dissect_can_frame(cf)))

Would be better to use urlencode from python urllib to encode body if you have bytes in data.

In GUI file:

class Battery_Update_Handler(web.RequestHandler):
    def post(self):
        self.application.battery0.status = dict(
            can_id=self.get_argument("can_id", None),
            can_dlc=self.get_argument("can_dlc", None),
            data=self.get_argument("data", None))


class Application(web.Application):
    def __init__(self):

        self.battery0 = Battery('bat0')    

        routing = [
            (r'/api/battery/(data|)', API_Handler),
            (r'/update-battery', Battery_Update_Handler)
        ]

        settings = {
            'template_path': os.path.join(os.path.dirname(__file__), "templates"),
            'static_path': os.path.join(os.path.dirname(__file__), "static"),
        }

        web.Application.__init__(self, routing, debug=True, **settings)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM