Implemented web server that uses the official Arduino command line to upload sketches

zeedee
Nick Johnson 2015-06-20 13:03:24 +01:00
parent 43b8b03dbc
commit 89d2147ea9
4 changed files with 158 additions and 82 deletions

View File

@ -36,13 +36,17 @@ The preffered way is to put the BlocklyDuino/web folder into a web server and op
### Integrated Arduino upload
To avoid the tedious step of manually pasting code to the Arduino IDE, you can run a mini webserver that uses
[ino](https://github.com/gumbypp/ino) to upload the code to a connected Arduino board on Mac OS X and Linux systems.
the [Arduino IDE](https://www.arduino.cc/en/Main/Software) to upload the code to a connected Arduino board on Windows, Mac OS X and Linux systems.
Invoke this command from the BlocklyDuino root folder:
```
python ino_web_server.py
python arduino_web_server.py
```
You can optionally specify the port with `--port=COM3` (or `--port=/dev/tty.foo` on Linux and Mac); if you don't, it will try and guess which port to use.
When the webserver is running, you can access BlocklyDuino itself on [http://127.0.0.1:8080/](http://127.0.0.1:8080/).
### Usage
1. Open browser to BlocklyDuino, drag and drop blocks to make an Arduino program

151
arduino_web_server.py Normal file
View File

@ -0,0 +1,151 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# credit: http://sheep.art.pl/Wiki%20Engine%20in%20Python%20from%20Scratch
import BaseHTTPServer
import SimpleHTTPServer
import itertools
import logging
import platform
import os
import re
import subprocess
import tempfile
import urllib
from optparse import OptionParser
logging.basicConfig(level=logging.DEBUG)
arduino_cmd = None
def get_arduino_command():
"""Attempt to find or guess the path to the Arduino binary."""
global arduino_cmd
if not arduino_cmd:
if platform.system() == "Darwin":
arduino_cmd_guesses = ["/Applications/Arduino.app/Contents/MacOS/Arduino"]
elif platform.system() == "Windows":
arduino_cmd_guesses = [
"c:\Program Files\Arduino\Arduino.exe",
"c:\Program Files (x86)\Arduino\Arduino.exe"
]
else:
arduino_cmd_guesses = []
for guess in arduino_cmd_guesses:
if os.path.exists(guess):
logging.info("Found Arduino command at %s", guess)
arduino_cmd = guess
break
else:
logging.info("Couldn't find Arduino command; hoping it's on the path!")
arduino_cmd = "arduino"
return arduino_cmd
def guess_port_name():
"""Attempt to guess a port name that we might find an Arduino on."""
portname = None
if platform.system() == "Windows":
import _winreg as winreg
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM")
# We'll guess it's the last COM port.
for i in itertools.count():
try:
portname = winreg.EnumValue(key, i)[1]
except WindowsError:
break
else:
# We'll guess it's the first non-bluetooth tty. or cu. prefixed device
ttys = [filename for filename in os.listdir("/dev")
if (filename.startswith("tty.") or filename.startswith("cu."))
and not "luetooth" in filename]
ttys.sort(key=lambda k:(k.startswith("cu."), k))
if ttys:
portname = "/dev/" + ttys[0]
logging.info("Guessing port name as %s", portname)
return portname
parser = OptionParser()
parser.add_option("--port", dest="port", help="Upload to serial port named PORT", metavar="PORT")
parser.add_option("--board", dest="board", help="Board definition to use", metavar="BOARD")
parser.add_option("--command", dest="cmd", help="Arduino command name", metavar="CMD")
class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_HEAD(self):
"""Send response headers"""
if self.path != "/":
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
self.send_response(200)
self.send_header("content-type", "text/html;charset=utf-8")
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
def do_GET(self):
"""Send page text"""
if self.path != "/":
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
else:
self.send_response(302)
self.send_header("Location", "/blockly/apps/blocklyduino/index.html")
self.end_headers()
def do_POST(self):
"""Save new page text and display it"""
if self.path != "/":
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_POST(self)
options, args = parser.parse_args()
length = int(self.headers.getheader('content-length'))
if length:
text = self.rfile.read(length)
print "sketch to upload: " + text
dirname = tempfile.mkdtemp()
sketchname = os.path.join(dirname, os.path.basename(dirname)) + ".ino"
f = open(sketchname, "wb")
f.write(text + "\n")
f.close()
print "created sketch at %s" % (sketchname,)
# invoke arduino to build/upload
compile_args = [
options.cmd or get_arduino_command(),
"--upload",
"--port",
options.port or guess_port_name(),
]
if options.board:
compile_args.extend([
"--board",
options.board
])
compile_args.append(sketchname)
print "Uploading with %s" % (" ".join(compile_args))
rc = subprocess.call(compile_args)
if not rc == 0:
print "arduino --upload returned " + `rc`
self.send_response(400)
else:
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
else:
self.send_response(400)
if __name__ == '__main__':
print "Blocklyduino can now be accessed at http://127.0.0.1:8080/"
server = BaseHTTPServer.HTTPServer(("127.0.0.1", 8080), Handler)
server.pages = {}
server.serve_forever()

View File

@ -237,7 +237,7 @@ function uploadCode(code, callback) {
function uploadClick() {
var code = document.getElementById('content_arduino').value;
alert("Ready to upload to Arduino.\n\nNote: this only works on Mac OS X and Linux at this time.");
alert("Ready to upload to Arduino.");
uploadCode(code, function(status, errorInfo) {
if (status == 200) {

View File

@ -1,79 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# credit: http://sheep.art.pl/Wiki%20Engine%20in%20Python%20from%20Scratch
import BaseHTTPServer, urllib, re, os
class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
template = u"""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd"><html><body><h1>Arduino INO web server</h1>To upload to an Arduino board connected to this computer, POST to /.</body></html>"""
def escape_html(self, text):
"""Replace special HTML characters with HTML entities"""
return text.replace(
"&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")
def do_HEAD(self):
"""Send response headers"""
self.send_response(200)
self.send_header("content-type", "text/html;charset=utf-8")
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
def do_GET(self):
"""Send page text"""
self.do_HEAD()
self.wfile.write(self.template)
def do_POST(self):
"""Save new page text and display it"""
length = int(self.headers.getheader('content-length'))
if length:
text = self.rfile.read(length)
print "sketch to upload: " + text
# create ino project (if it doesn't exist already)
os.system("mkdir ino_project")
os.chdir("ino_project")
rc = os.system("ino init")
# 32512 probably means ino is not installed
if rc == 32512:
print "ino init returned " + `rc`
self.send_response(501)
else:
# write to file
fo = open("src/sketch.ino", "wb")
fo.write(text + "\n");
fo.close()
print "created src/sketch.ino"
# invoke ino to build/upload
# skip_lib_includes is used to avoid "line too long" errors with IDE 1.5.8+
rc = os.system("ino build --skip_lib_includes")
# 512 probably means invalid option (skip_lib_includes)
if rc == 512:
print "ino build returned " + `rc` + " - trying again without skip_lib_includes"
rc = os.system("ino build")
if not rc == 0:
print "ino build returned " + `rc`
self.send_response(400)
else:
rc = os.system("ino upload")
if not rc == 0:
print "ino upload returned " + `rc`
self.send_response(500)
else:
self.send_response(200)
os.chdir("..")
if __name__ == '__main__':
print "running local web server at 127.0.0.1:8080..."
server = BaseHTTPServer.HTTPServer(("127.0.0.1", 8080), Handler)
server.pages = {}
server.serve_forever()