###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))
創建偽終端通信簡圖:

