[记录]python使用serial模块实现实时WebConsole
###tornado+websocket+多进程实现:
1.index.html
<!DOCTYPE HTML> <html> <head> <style> body { margin: 0px; padding: 20px; } #received { width: 500px; height: 400px; border: 1px solid #dedede; overflow-y:scroll;} #sent { width: 500px; } </style> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script type="text/javascript" src="static/main.js"></script> </head> <body> <h1>Websockets serial console</h1> <p>Data received from serial port</p> <div id="received"> </div> <button id="clear">Clear</button> <p>Send data to serial port</p> <form id="sent"> <input type="text" id="cmd_value"> <button id="cmd_send">Send</button> </form> </body> </html>
2.main.js
$(document).ready(function(){ var received = $('#received'); var socket = new WebSocket("ws://localhost:8080/ws"); socket.onopen = function(){ console.log("connected"); }; socket.onmessage = function (message) { console.log("receiving: " + message.data); received.append(message.data); received.append($('<br/>')); }; socket.onclose = function(){ console.log("disconnected"); }; var sendMessage = function(message) { console.log("sending:" + message.data); socket.send(message.data); }; // GUI Stuff // send a command to the serial port $("#cmd_send").click(function(ev){ ev.preventDefault(); var cmd = $('#cmd_value').val(); sendMessage({ 'data' : cmd}); $('#cmd_value').val(""); }); $('#clear').click(function(){ received.empty(); }); });
3.serialworker.py
import serial import time import multiprocessing ## Change this to match your local settings SERIAL_PORT = '/dev/ttyACM0' SERIAL_BAUDRATE = 115200 class SerialProcess(multiprocessing.Process): def __init__(self, input_queue, output_queue): multiprocessing.Process.__init__(self) self.input_queue = input_queue self.output_queue = output_queue self.sp = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, timeout=1) def close(self): self.sp.close() def writeSerial(self, data): self.sp.write(data) # time.sleep(1) def readSerial(self): return self.sp.readline().replace("\n", "") def run(self): self.sp.flushInput() while True: # look for incoming tornado request if not self.input_queue.empty(): data = self.input_queue.get() # send it to the serial device self.writeSerial(data) print "writing to serial: " + data # look for incoming serial data if (self.sp.inWaiting() > 0): data = self.readSerial() print "reading from serial: " + data # send it back to tornado self.output_queue.put(data)
4.server.py
import tornado.httpserver import tornado.ioloop import tornado.web import tornado.websocket import tornado.gen from tornado.options import define, options import os import time import multiprocessing import serialworker import json define("port", default=8080, help="run on the given port", type=int) clients = [] input_queue = multiprocessing.Queue() output_queue = multiprocessing.Queue() class IndexHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') class StaticFileHandler(tornado.web.RequestHandler): def get(self): self.render('main.js') class WebSocketHandler(tornado.websocket.WebSocketHandler): def open(self): print 'new connection' clients.append(self) self.write_message("connected") def on_message(self, message): print 'tornado received from client: %s' % json.dumps(message) #self.write_message('ack') input_queue.put(message) def on_close(self): print 'connection closed' clients.remove(self) ## check the queue for pending messages, and rely that to all connected clients def checkQueue(): if not output_queue.empty(): message = output_queue.get() for c in clients: c.write_message(message) if __name__ == '__main__': ## start the serial worker in background (as a deamon) sp = serialworker.SerialProcess(input_queue, output_queue) sp.daemon = True sp.start() tornado.options.parse_command_line() app = tornado.web.Application( handlers=[ (r"/", IndexHandler), (r"/static/(.*)", tornado.web.StaticFileHandler, {'path': './'}), (r"/ws", WebSocketHandler) ] ) httpServer = tornado.httpserver.HTTPServer(app) httpServer.listen(options.port) print "Listening on port:", options.port mainLoop = tornado.ioloop.IOLoop.instance() ## adjust the scheduler_interval according to the frames sent by the serial port scheduler_interval = 100 scheduler = tornado.ioloop.PeriodicCallback(checkQueue, scheduler_interval, io_loop = mainLoop) scheduler.start() mainLoop.start()
5.扩展:每个页面用户登录时生成一个唯一的key,用户绑定该用户该会话的操作,在进行操作时,把这个key也传给server端的input队列,然后在serialworker的run循环中对data数据进行处理,在self.output_queue.put(data)操作是又带上这个key,然后返回,server中checkQueue方法执行是只发给这个key的会话,然后前端显示结果。这样来实现每个会话信息互不干扰。如果不用key做标记,那么所有会话操作的信息,所有会话之间都能看到。同时,serial是否可以和os.openpty()结合,实现serial操作新建的伪终端来实现shell终端操作模式。linux的pty创建是成对出现的,一个主,一个从,执行命令的时一个写,另一个读。一般是配合subprocess来指定STDOUT=slave,这样执行命令之后把结果写到slave,然后从master读取后再前端显示。pty创建简单代码如下:
#!/usr/bin/python # -*- coding: UTF-8 -*- import os,sys,time import serial # 主 pty, 从 tty master, slave = os.openpty() print master print slave # 显示终端名 slave_name = os.ttyname(slave) print master print slave_name ser = serial.Serial(slave_name, rtscts=True, dsrdtr=True) print "Open port successful! the port information:" print ser print "\n" while ser.isOpen(): #the return is True or Faulse print "please write the msg(exit to break)" msg = raw_input(">") #add a break reason:::kill the child thread if msg == 'exit': print "\n" print "Please waiting to close the connection......" time.sleep(1) break; msg = msg + '\r' + '\n' #AT form define #data=ser.write(msg) os.write(master, msg) sys.stdout.flush() #flush the buffer print "\n" print ("waiting to recv the data...") time.sleep(2) msg_recv = os.read(slave, 128) print ("\n") print ("\tOK! The recv msg is %s"%(msg_recv))
创建伪终端通信简图: