4
\$\begingroup\$

I am trying to build a websocket server to run on the Raspberry Pi. The websocket server has to push periodic realtime update to a browser. Here is a sample code that I am planning to use. The example code here sends the current time instead of sensor data.

Most of the code is built form an example. However, what I need feedback on is

  • The way stpoll is coded (sensor status poll)
  • The global variable that passes the message to terminate the thread. I don't like it. Is there a better way to terminate a task that runs forever?
  • The way the time (sensor data) is broadcasted to all connected clients. (another global variable)

I had to code this without much documentation.

# -*- coding: utf-8 -*-
import argparse
import random
import os
import time
import threading
import signal
import sys

import cherrypy

from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
from ws4py.websocket import WebSocket
from ws4py.messaging import TextMessage

class ChatWebSocketHandler(WebSocket):
    def received_message(self, m):
        cherrypy.engine.publish('websocket-broadcast', m)

    def closed(self, code, reason="A client left the room without a proper explanation."):
        cherrypy.engine.publish('websocket-broadcast', TextMessage(reason))

class Root(object):
    def __init__(self, host, port, ssl=False):
        self.host = host
        self.port = port
        self.scheme = 'wss' if ssl else 'ws'

    @cherrypy.expose
    def index(self):
        return """<html>
    <head>
      <script type='application/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'></script>
      <script type='application/javascript'>
        $(document).ready(function() {
          websocket = '%(scheme)s://%(host)s:%(port)s/ws';
          if (window.WebSocket) {
            ws = new WebSocket(websocket);
          }
          else if (window.MozWebSocket) {
            ws = MozWebSocket(websocket);
          }
          else {
            console.log('WebSocket Not Supported');
            return;
          }
          window.onbeforeunload = function(e) {
            $('#chat').val($('#chat').val() + 'Bye bye...\\n');
            ws.close(1000, '%(username)s left the room');
            if(!e) e = window.event;
            e.stopPropagation();
            e.preventDefault();
          };
          ws.onmessage = function (evt) {
             $('#chat').val($('#chat').val() + evt.data + '\\n');
          };
          ws.onopen = function() {
             ws.send("%(username)s entered the room");
          };
          ws.onclose = function(evt) {
             $('#chat').val($('#chat').val() + 'Connection closed by server: ' + evt.code + ' \"' + evt.reason + '\"\\n');
          };
          $('#send').click(function() {
             console.log($('#message').val());
             ws.send('%(username)s: ' + $('#message').val());
             $('#message').val("");
             return false;
          });
        });
      </script>
    </head>
    <body>
    <form action='#' id='chatform' method='get'>
      <textarea id='chat' cols='35' rows='10'></textarea>
      <br />
      <label for='message'>%(username)s: </label><input type='text' id='message' />
      <input id='send' type='submit' value='Send' />
      </form>
    </body>
    </html>
    """ % {'username': "User%d" % random.randint(0, 100), 'host': self.host, 'port': self.port, 'scheme': self.scheme}

    @cherrypy.expose
    def ws(self):
        cherrypy.log("Handler created: %s" % repr(cherrypy.request.ws_handler))

def stpoll(arg):
  global pollStatus
  while pollStatus:
    tm=time.localtime();
    print tm.tm_sec
    if 'WebSocket' in globals():
      print "WS !"
      cherrypy.engine.publish('websocket-broadcast', '%s ' %tm)
    time.sleep(2)
  print "exitting therad"

def signal_handler(signal, frame):
  global pollStatus
  pollStatus=False
  cherrypy.engine.stop()
  cherrypy.engine.exit()
  print('You pressed Ctrl+C!')

if __name__ == '__main__':
  import logging
  from ws4py import configure_logger
  configure_logger(level=logging.DEBUG)
  signal.signal(signal.SIGINT, signal_handler)
  pollStatus=True 
  thread = threading.Thread(target = stpoll, args = (10, ))
  thread.daemon = True
  thread.start()
  parser = argparse.ArgumentParser(description='Echo CherryPy Server')
  parser.add_argument('--host', default='127.0.0.1')
  parser.add_argument('-p', '--port', default=9000, type=int)
  parser.add_argument('--ssl', action='store_true')
  args = parser.parse_args()

  cherrypy.config.update({'server.socket_host': args.host,
                          'server.socket_port': args.port,
                          'tools.staticdir.root': os.path.abspath(os.path.join(os.path.dirname(__file__), 'static'))})

  if args.ssl:
      cherrypy.config.update({'server.ssl_certificate': './server.crt',
                              'server.ssl_private_key': './server.key'})

  WebSocketPlugin(cherrypy.engine).subscribe()
  cherrypy.tools.websocket = WebSocketTool()

  cherrypy.quickstart(Root(args.host, args.port, args.ssl), '', config={
      '/ws': {
          'tools.websocket.on': True,
          'tools.websocket.handler_cls': ChatWebSocketHandler
          },
      '/js': {
            'tools.staticdir.on': True,
            'tools.staticdir.dir': 'js'
          }
      }
  )
  thread.join()
\$\endgroup\$
2
\$\begingroup\$

Your code is nice, but it could use a few changes:

  • % {}: rather than using that formatting method, you should use the string.format() method, involving {0} {1} instead of % as it is recommended by PEP8, Python's official style guide.
  • print "exitting therad": both exiting and thread are spelt incorrectly
  • global pollStatus: you shouldn't be naming your variables like camelCase, rather snake_case instead, as also expressed by PEP8
  • tm=time.localtime(): you should have whitespace between your binary operators, and also you shouldn't abbreviate your variables like tm: local_time would be much better.

Other than that, your code looks nice, well done!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.