3
\$\begingroup\$

I did this project last year, under some interesting constraints: it's a thin webserver which lives in a Raspberry Pi, which is networked to an embedded system in an industrial sign which is running a proprietary and heavily modified version of *nix. I didn't touch the embedded system in the sign at all.

The whole thing is a kinetic sculpture, installed in a private residence.

Because of some of the project constraints (i.e., we were working in someone's house) I wasn't as aggressive refactoring as I would otherwise have been. There are also no tests. Bad, I know, but it was a situation where the complexity of the project was almost certainly not going to grown in any dramatic way beyond its current level.

Here is the code for the Tornado server. A sanitized fork of the entire repo is here.

The stack is Python/Tornado, HTML, JS and SASS.

""" theSculpture server.
 Launch the server with 'python sculptureController.py' Currently the server defaults to port
 8888. The server provides an interface to the theSculpture,
 allowing the sculpture's mode to be changed.
 Front end and back end code by Liav Koren 2013 - 2014. """
 import tornado.httpserver
 import tornado.ioloop
 import tornado.options
 import tornado.web
 import tornado.auth
 import tornado.escape
 import os
 from tornado.options import define, options
 import tornado.httputil
 import time
 import paramiko
 import subprocess
 import json
 define("port", default=8888, help="run on the given port", type=int)
 class ModeHandler(tornado.web.RequestHandler):
 ''' Handler for the main interface. This serves up the mode selector page. '''
 ssh = ""
 def get(self):
 self.render('modes.html', current_mode='TESTING')
 def post(self):
 """ Mode handler. The mode selector page presents 'slow', 'medium', 'fast',
 'return to landing page' and 'logout' options to user. This handler is also
 the endpoint for the main page options 'ON' and 'OFF'. Slow, medium, fast, on
 and off all initiate a series of remote operations on theSculpture that involve
 opening an SSH tunnel, triggering various shell scripts and transfering files
 between the Pi and theSculpture. """
 path = ""
 data = self.request.arguments
 if 'slow' in data or 'slow-swatch' in data:
 path = '/home/pi/theSculpture/dat/slower1.dat'
 if 'medium' in data or 'medium-swatch' in data:
 path = '/home/pi/theSculpture/dat/med.dat'
 if 'fast' in data or 'fast-swatch' in data:
 path = '/home/pi/theSculpture/dat/AGH4.dat'
 if 'on' in data or 'on-swatch' in data:
 self.kill_theSculpture()
 self.reset_theSculpture()
 time.sleep(5)
 self.write(json.dumps({"file_transfer": "finished"}))
 if 'off' in data or 'off-swatch' in data:
 self.kill_theSculpture()
 time.sleep(5)
 self.write(json.dumps({"file_transfer": "finished"}))
 if path:
 self.file_transfer(path)
 #self.file_transfer_mock(path)
 def file_transfer_mock(self, path):
 """ A debugging utility method for accepting commands but not
 passing them on to theSculpture. """
 new_path = "new path is: %s" % path
 print new_path
 time.sleep(10)
 self.write(json.dumps({"file_transfer": "finished"}))
 def kill_theSculpture(self):
 """ Turns theSculpture off. """
 cmd = "cd /bin; ./kill_theSculpture.sh"
 self.talk_to_sign(cmd)
 def reset_theSculpture(self):
 """ Restarts theSculpture from an off or currently running state. """
 self.kill_theSculpture()
 self.talk_to_sign("/bin/reset_theSculpture.sh")
 def file_transfer(self, path):
 """ This function deals with resetting theSculpture and
 transferring the new dat files. """
 self.kill_theSculpture()
 print "waiting 30s"
 time.sleep(30)
 print "starting scp"
 proc = subprocess.Popen(['scp', path,
 'root@***.**.57.230:/data/theSculpture.dat'],
 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 proc.wait()
 print "stdOut: %s " % proc.stdout.readline()
 print "stdErr: %s" % proc.stderr.readline()
 self.talk_to_sign("/bin/reset_theSculpture.sh")
 self.write(json.dumps({"file_transfer": "finished"}))
 def talk_to_sign(self, cmd):
 ''' Talk_to_sign calls open_ssh to get an ssh client object, then emits
 commands to the target system.'''
 if not self.ssh:
 self.ssh = self.open_ssh_tunnel('***.**.57.230', 'root', 'foobar')
 stdin, stdout, stderr = self.ssh.exec_command(cmd)
 if stderr.readlines():
 print "Error: %s" % stderr.readlines()
 if stdout.readlines():
 print "Cmd returned: %s" % stdout.readlines()
 def get_current_sign_status(self): # ToDo: narrow the exception handling
 ''' Tries to get the current state of the sign and return it to the
 client. '''
 if not self.ssh:
 self.ssh = self.open_ssh_tunnel('***.***.1.105', 'pi', 'foobar')
 stdin, stdout, stderr = self.ssh.exec_command('cat test')
 if stderr.readlines():
 print "Error reading current sign state."
 output = stdout.readlines()
 if output:
 print "current sign state is: "
 print output[0][:-1]
 return output[0][:-1]
 def open_ssh_tunnel(self, ip, username, passwd):
 ''' Provides a ssh client to the talk_to_sign method '''
 try:
 s = paramiko.SSHClient()
 s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 s.connect(ip, username = username, password = passwd)
 return s
 except paramiko.AuthenticationException:
 self.write(json.dumps(
 {"status": "Failed to login to sign server successfully."}))
 return
 except paramiko.ChannelException:
 self.write(json.dumps(
 {"status": "Error opening channel to sign server."}))
 return
 except paramiko.SSHException:
 self.write(json.dumps(
 {"status": "SSH error when trying to contact sign server."}))
 return
 class MainHandler(ModeHandler):
 """ The landing page once the user has logged in. """
 def get(self):
 self.render('main_revised.html')
 def main():
 tornado.options.parse_command_line()
 settings = dict(
 template_path=os.path.join(os.path.dirname(__file__), "static"),
 debug=True,
 static_path=os.path.join(os.path.dirname(__file__), "static"),
 login_url="/login",
 )
 application = tornado.web.Application([
 (r"/mode", ModeHandler),
 (r"/", MainHandler),
 (r"/main", MainHandler),
 ], **settings)
 http_server = tornado.httpserver.HTTPServer(application)
 http_server.listen(options.port)
 tornado.ioloop.IOLoop.instance().start()
 if __name__ == "__main__":
 main()
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked May 10, 2014 at 17:07
\$\endgroup\$
2
  • \$\begingroup\$ I can't comment on the code, but a full Rails stack with Webrick runs fine on RP with no modification. \$\endgroup\$ Commented May 11, 2014 at 19:09
  • \$\begingroup\$ Hey Gene. It was a pretty light-weight application, I really just needed something that could stand-up some static webpages and handle requests -- anything with an ORM would have been major overkill. \$\endgroup\$ Commented May 11, 2014 at 22:00

1 Answer 1

1
\$\begingroup\$

Seems reasonable given your tools at hand and the constraints.

Next time you want to programmatically drive SSH look into http://www.fabfile.org/. It uses paramiko internally and makes all of the complexities much nicer.

answered May 12, 2014 at 23:13
\$\endgroup\$
1
  • \$\begingroup\$ We use Fab at our office. I hadn't been introduced to it at the time, but it's really nice. \$\endgroup\$ Commented May 13, 2014 at 14:56

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.