0

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?

enter image description here

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()
Zhang LongQI
  • 494
  • 1
  • 11
  • 25
  • If you're using Tornado, that is capable of [WebSockets](https://en.wikipedia.org/wiki/WebSocket), why the user has to send a command to get a status? You just need to start a thread, that would read battery status with specified frequency and automatically update the browser via websocket connection. – yegorich Apr 07 '16 at 06:04
  • @yegorich I know your concern. but this is just an example of cmd. I have many other of cmd also. Another reason is the data inside changes much more frequently but the GUI do not need to update as the same frequency. The third reason is that this allow other module using the same API to request data in their own frequency. – Zhang LongQI Apr 07 '16 at 13:30

1 Answers1

0

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)
Community
  • 1
  • 1
sanddog
  • 91
  • 1
  • 4
  • What if the CAN bus receive 1000 messages in one second, Will it be slow if we use POST method? – Zhang LongQI Mar 25 '16 at 09:48
  • That is kind of theoretical question. I have not used CAN, so checked out wikipedia and found it's maximum speed 1Mb/s, while speed of access to localhost is limited by CPU speed ([according to this](http://serverfault.com/questions/234223/how-fast-is-127-0-0-1)) therefore you should not face speed issue. Anyway try to implement it and simulate a massive incoming data through CAN, if my assumption is wrong we could stick to first solution. – sanddog Mar 25 '16 at 14:53
  • Actually, most of the IO come from CAN bus instead of HTTP request. Anyway, I will do the simulation first, Thanks. – Zhang LongQI Mar 30 '16 at 02:42