用于连接远程传感器的Python架构

时间:2015-05-25 18:17:00

标签: python raspberry-pi rabbitmq tornado zeromq

我有一个遥感器,比如一个测量温度的Raspberry Pi。 我希望能够远程控制这个传感器。

它通过一些未知的方式连接到互联网,这可以通过Wifi,以太网或GSM / 3G。这意味着我无法将静态IP地址绑定到传感器,因此无法托管服务器(据我所知)。但它可以轻松地向已知服务器发出请求。我需要向设备发出请求,获取数据,告诉它打开/关闭等。但是它还需要将信息推送到服务器,如日志等。

到目前为止,我一直在考虑使用Tornado Web服务器,使用中央服务器来回复来自设备的HTTP或websocket请求,以便为传感器命令和接收数据。这样做的好处是它很容易设置,但我觉得我正在把Tornado拴在一些它不想做的事情上。

另一种可能性是像ZeroMQ或RabbitMQ这样的消息传递服务 - 但对我来说,他们似乎更努力,因为它们稍微低一些级别(我需要编写路由器等)并且感觉有点矫枉过正。

任何意见或建议都将不胜感激。

1 个答案:

答案 0 :(得分:0)

ZeroMQ应该不会太难。这就是我要做的事。

在云中编写一个龙卷风HTTP服务器,并结合两个端口用于zeroMQ PUB和SUB套接字。 Server为用户提供REST端点。在树莓派上将两个zeroMQ PUB SUB套接字连接到云服务器。

当您在云服务器上收到请求时,请在PUB套接字上发送命令。通过zeroMQ SUB套接字设置对接收数据的回调。当您收到数据时,在树莓派上运行您的传感器代码。如果有回复,则将其发送到覆盆子派的PUB频道。在服务器上,回调将回复用户。

下面是服务器(云)和客户端(树莓派)的代码片段。

在云服务器上运行。

  #!/usr/bin/env python
import json
import tornado
import tornado.web
import zmq
from tornado import httpserver
from zmq.eventloop import ioloop
from zmq.eventloop.zmqstream import ZMQStream

ioloop.install()
tornado.ioloop = ioloop
import sys


def ping_remote():
    """callback to keep the connection with remote server alive while we wait
    Network routers between raspberry pie and cloud server will close the socket
    if there is no data exchanged for long time.
    """
    pub_inst.send_json_data(msg="Ping", req_id="##")
    sys.stdout.write('.')
    sys.stdout.flush()


pending_requests = {}


class ZMQSub(object):
    def __init__(self, callback):
        self.callback = callback
        context = zmq.Context()
        socket = context.socket(zmq.SUB)
        # socket.connect('tcp://127.0.0.1:5559')
        socket.bind('tcp://*:8081')
        self.stream = ZMQStream(socket)
        self.stream.on_recv(self.callback)
        socket.setsockopt(zmq.SUBSCRIBE, "")

    def shutdown_zmq_sub(self):
        self.stream.close()


class ZMQPub(object):
    def __init__(self):
        context = zmq.Context()
        socket = context.socket(zmq.PUB)
        socket.bind('tcp://*:8082')
        self.publish_stream = ZMQStream(socket)

    def send_json_data(self, msg, req_id):
        topic = str(req_id)
        self.publish_stream.send_multipart([topic, msg])

    def shutdown_zmq_sub(self):
        self.publish_stream.close()


def SensorCb(msg):
    # decode message from raspberry pie and the channel ID.
    key, msg = (i for i in msg)
    if not key == "##":
        msg = json.loads(msg)

        if key in pending_requests.keys():
            req_inst = pending_requests[key]
            req_inst.write(msg)
            req_inst.finish()
            del pending_requests[key]
        else:
            print "no such request"
            print pending_requests
    else:
        print "received ping"


class Handler(tornado.web.RequestHandler):
    def __init__(self, *args, **kwargs):
        super(Handler, self).__init__(*args, **kwargs)

        # get the unique req id
        self.req_id = str(self.application.req_id) + "#"
        self.application.req_id += 1

        # set headers
        self.set_header("Access-Control-Allow-Origin", "*")
        self.set_header("Access-Control-Allow-Headers", "x-requested-with")
        self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT')

    @tornado.web.asynchronous
    def get(self):
        print self.request
        if self.req_id not in pending_requests.keys():
            pending_requests[self.req_id] = self
        else:
            print "WTF"
        pub_inst.send_json_data(msg=json.dumps({"op": "ServiceCall"}), req_id=self.req_id)


if __name__ == "__main__":
    pub_inst = ZMQPub()
    sub_inst = ZMQSub(callback=SensorCb)
    application = tornado.web.Application(
        [(r'/get_sensor_data', Handler), (r'/(.*)')])
    application.req_id = 0
    server = httpserver.HTTPServer(application, )
    port = 8080
    server.listen(port)
    print "Sensor server ready on port: ", port
    ping = ioloop.PeriodicCallback(ping_remote, 3000)
    ping.start()
    tornado.ioloop.IOLoop.instance().start()

在Raspberry Pie上,运行以下客户端。

import zmq
from zmq.eventloop import ioloop
from zmq.eventloop.zmqstream import ZMQStream
import sys

ioloop.install()


def ping_remote():
    """keep pinging the server so that the connection between server and client is not lost"""
    pub_inst.send_json_data(msg="Ping", req_id="##")
    sys.stdout.write('.')
    sys.stdout.flush()


pending_requests = {}


class ZMQSub(object):
    def __init__(self, callback):
        self.callback = callback
        context = zmq.Context()
        socket = context.socket(zmq.SUB)
        socket.connect('tcp://127.0.0.1:8082')  # replace by cloud server's IP address
        self.stream = ZMQStream(socket)
        self.stream.on_recv(self.callback)
        socket.setsockopt(zmq.SUBSCRIBE, "")

    def shutdown_zmq_sub(self):
        self.stream.close()


class ZMQPub(object):
    def __init__(self):
        context = zmq.Context()
        socket = context.socket(zmq.PUB)
        socket.connect('tcp://127.0.0.1:8081')  # replace by cloud server's IP address
        self.publish_stream = ZMQStream(socket)

    def send_json_data(self, msg, req_id):
        topic = str(req_id)
        self.publish_stream.send_multipart([topic, msg])

    def shutdown_zmq_sub(self):
        self.publish_stream.close()


def SensorCb(msg):
    # decode image and the channel ID.
    key, msg = (i for i in msg)
    if not key == "##":
        print key, msg
        #####
        #  Do your sensor specific work here. And built a reply for remote server.
        #####
        resp = "response from raspi"
        pub_inst.send_json_data(msg=resp, req_id=key)
    else:
        print "received ping"


if __name__ == "__main__":
    pub_inst = ZMQPub()
    sub_inst = ZMQSub(callback=SensorCb)
    ioloop.PeriodicCallback(ping_remote, 3000).start()
    ioloop.IOLoop.instance().start()