Commit 21592d2d authored by Carlos Pereira Atencio's avatar Carlos Pereira Atencio Committed by carlospamg

Adding current version of ArduinoServerCompiler Python Package:

  This allows a user to run a local server, that together with the Arduino IDE allows the compilation and load of Arduino sketchs from the web interface.
parent 31c60dcf
from __future__ import unicode_literals, absolute_import
import os
try:
# 2.x name
import BaseHTTPServer
except ImportError:
# 3.x name
import http.server as BaseHTTPServer
import ArduinoServerCompiler.BlocklyRequestHandler
ADDRESS = 'localhost'
PORT = 8000
def start_server(document_root):
""" Start the server with the document root indicated by argument """
print('\nSetting HTTP Server Document Root to: \n\t' + document_root + "\n")
os.chdir(document_root)
server_address = (ADDRESS, PORT)
server = BaseHTTPServer.HTTPServer(
server_address,
ArduinoServerCompiler.BlocklyRequestHandler.BlocklyRequestHandler)
print('Launching the HTTP service...')
server.serve_forever()
print('The Server closed unexpectedly!!')
if __name__ == "__main__":
start_server(os.getcwd())
from __future__ import unicode_literals, absolute_import
import os
import cgi
try:
# 2.x name
import Tkinter
import urlparse
import tkFileDialog
import SimpleHTTPServer
except ImportError:
# 3.x name
import tkinter as Tkinter
import urllib.parse as urlparse
import tkinter.filedialog as tkFileDialog
import http.server as SimpleHTTPServer
from ArduinoServerCompiler.Py23Compatibility import Py23Compatibility
from ArduinoServerCompiler.ServerCompilerSettings import ServerCompilerSettings
from ArduinoServerCompiler.SketchCreator import SketchCreator
class BlocklyRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""
Simple Python HTTP request handler to pass over the AJAX requests.
"""
def do_POST(self):
"""
Serves the POST request, using form-like data
"""
message_back = ''
parameters = None
content_type, parameters_dict = cgi.parse_header(
self.headers.getheader('content-type'))
content_length = int(self.headers.getheader('content-length'))
if content_type == 'application/x-www-form-urlencoded':
parameters = urlparse.parse_qs(
self.rfile.read(content_length), keep_blank_values=False)
#print(parameters)
#for key in parameters:
# print(str(key) + ": " + str(parameters[key]))
#parameters = cgi.FieldStorage(
# fp=self.rfile,
# headers=self.headers,
# environ={'REQUEST_METHOD':'POST',
# 'CONTENT_TYPE':self.headers['Content-Type'], })
#for item in parameters.list:
# print(item)
elif content_type == 'text/plain':
data_string = self.rfile.read(content_length)
try:
message_back = '//Python test\n\r' + data_string
except Exception as e:
print(e)
print('\nThere was an error manipulating the plain text data!!!')
else:
print('\nError, content type not recognised: ' + str(content_type))
self.send_response(404, "Upps, not found!")
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write('Error: invalid content type')
return
if message_back != '':
handle_sketch(message_back)
if parameters:
message_back = handle_settings(parameters)
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(message_back)
#################
# Main Handlers #
#################
def handle_sketch(sketch_code):
sketch_path = create_sketch_from_string(sketch_code)
load_sketch(sketch_path)
def handle_settings(parameters):
def _get_value(parameters2):
""" Searches for a 'value' parameter in the dictionary. """
value2 = None
for key2 in parameters2:
if str(key2) == 'value':
value2 = str(parameters2[key2])
return value2
message_back = None
for key in parameters:
# Compiler
if str(key) == 'compiler':
if str(parameters[key]) == "['get']":
message_back = get_compiler_path()
elif str(parameters[key]) == "['set']":
message_back = set_compiler_path()
# Sketch
elif str(key) == 'sketch':
if str(parameters[key]) == "['get']":
message_back = get_sketch_path()
elif str(parameters[key]) == "['set']":
message_back = set_sketch_path()
# TODO: Arduino Board
# TODO: COM Port
# Load Only IDE
elif str(key) == 'ideOnly':
if str(parameters[key]) == "['get']":
message_back = get_load_ide_only()
print('ideO: ' + message_back)
elif str(parameters[key]) == "['set']":
value = _get_value(parameters)
if value == "['True']":
message_back = set_load_ide_only(True)
else:
message_back = set_load_ide_only(False)
# The Value parameter is only used in some cases
elif str(key) == 'value':
pass
# Parameter not recognised
else:
print('The "' + str(key) + '" = ' + str(parameters[key]) +
' parameter is not recognised!')
return message_back
#######################################
# Sketch loading to Arduino functions #
#######################################
def load_sketch(sketch_path=None):
"""
Launches a command line that invokes the Arduino IDE to open and/or
load an sketch, which address is indicated in the input parameter
"""
# Input sanitation
if not isinstance(sketch_path, Py23Compatibility.string_type_compare) \
or not sketch_path:
sketch_path = create_sketch_default()
# Concatenates the command string
command_line_command = ServerCompilerSettings().compiler_dir + ' '
if not ServerCompilerSettings().launch_IDE_only:
command_line_command += '--upload '
command_line_command += '--port ' + \
ServerCompilerSettings().com_port + ' '
command_line_command += '--board ' + \
ServerCompilerSettings().get_arduino_board_flag() + ' '
command_line_command += '"' + sketch_path + '"'
print('Command line command:\n\t' + command_line_command)
os.system(command_line_command)
def create_sketch_default():
return SketchCreator().create_sketch()
def create_sketch_from_string(sketch_code):
return SketchCreator().create_sketch(sketch_code)
######################################
# Dealing with Directories and files #
######################################
def browse_file():
"""
Opens a file browser and selects executable files
:return: Full path to selected file
"""
#TODO: Manually set to filder .exe files, need to make it compatible
# with other OSes
root = Tkinter.Tk()
# Make window almost invisible to focus it and ensure directory browser
# doesn't end up loading in the background behind main window.
root.withdraw()
root.overrideredirect(True)
root.geometry('0x0+0+0')
root.deiconify()
root.lift()
root.focus_force()
types = [('Executable', '.exe'), ('All Files', '*')]
file_path = tkFileDialog.askopenfilename(filetypes=types)
root.destroy()
return file_path
def browse_dir():
"""
Opens a directory browser to select a folder.
:return: Full path to the selected folder
"""
root = Tkinter.Tk()
# Make window almost invisible to focus it and ensure directory browser
# doesn't end up loading in the background behind main window.
root.withdraw()
root.overrideredirect(True)
root.geometry('0x0+0+0')
root.deiconify()
root.lift()
root.focus_force()
file_path = tkFileDialog.askdirectory(
parent=root, initialdir="/", title='Please select a directory')
root.destroy()
return file_path
#####################
# Compiler Settings #
#####################
def set_compiler_path():
"""
Open the file browser to select a file. Saves this filepath into
ServerCompilerSettings and if the filepath is different to that stored
already it triggers the new data to be saved into the settings file.
"""
old_path = get_compiler_path()
new_path = browse_file()
if new_path != '':
ServerCompilerSettings().compiler_dir = new_path
new_path = get_compiler_path()
if old_path != new_path:
ServerCompilerSettings().save_settings()
return new_path
def get_compiler_path():
return ServerCompilerSettings().compiler_dir
###################
# Sketch settings #
###################
def set_sketch_path():
"""
Open the directory browser to select a file. Saves this directory into
ServerCompilerSettings and if the directory is different to that stored
already it triggers the new data to be saved into the settings file.
"""
old_directory = get_sketch_path()
new_directory = browse_dir()
if new_directory != '':
ServerCompilerSettings().sketch_dir = new_directory
new_directory = get_sketch_path()
if old_directory != new_directory:
ServerCompilerSettings().save_settings()
return new_directory
def get_sketch_path():
return ServerCompilerSettings().sketch_dir
############################
# Launch IDE only settings #
############################
def set_load_ide_only(new_value):
ServerCompilerSettings().launch_IDE_only = new_value
return get_load_ide_only()
def get_load_ide_only():
return str(ServerCompilerSettings().launch_IDE_only)
########
# Main #
########
def main():
print("This is the BlocklyRequestHandler main.")
if __name__ == "__main__":
main()
"""
This module contains some utilities to maintain compatibility between python 2.5+ and 3
"""
from __future__ import unicode_literals, absolute_import
import sys
import types
# Define different types for comparison
if sys.version_info[0] == 3:
string_type_compare = str
integer_type_compare = int
class_type_compare = type
else:
string_type_compare = basestring
integer_type_compare = (int, long)
class_type_compare = (type, types.ClassType)
This diff is collapsed.
from __future__ import unicode_literals, absolute_import
import os
from ArduinoServerCompiler.Py23Compatibility import Py23Compatibility
from ArduinoServerCompiler.ServerCompilerSettings import ServerCompilerSettings
class SketchCreator(object):
"""
Creates an Arduino Sketch
"""
#This is probably not the best way to create this string, will revisit
_sketch_default_code = """int led = 13;
void setup() {
pinMode(led, OUTPUT);
}
void loop() {
digitalWrite(led, HIGH);
delay(1000);
digitalWrite(led, LOW);
delay(1000);
}"""
#
# Constructor
#
def __init__(self):
pass
#
# Creating files
#
def create_sketch(self, sketch_code=None):
"""
Creates the Ardunino sketch with either the default blinky
code or the code defined in the input parameter
:param sketch_code: Unicode string with the code for the sketch
:return: Unicode string with full path to the sketch file
Return None indicates an error has occurred
"""
sketch_path = self.build_sketch_path()
if isinstance(sketch_code, Py23Compatibility.string_type_compare)\
and sketch_code:
code_to_write = sketch_code
else:
code_to_write = self._sketch_default_code
try:
arduino_sketch = open(sketch_path, 'w')
arduino_sketch.write(code_to_write)
arduino_sketch.close()
except Exception as e:
sketch_path = None
print(e)
print('Arduino sketch could not be created!!!')
return sketch_path
#
# File and directories settings
#
def build_sketch_path(self):
"""
If a valid directory is saved in the settings, it creates the Arduino
folder (if it does not exists already) and returns a string pointing
to the sketch path
:return: unicode string with full path to the sketch file
Return None indicates an error has occurred
"""
sketch_name = ServerCompilerSettings().sketch_name
sketch_directory = ServerCompilerSettings().sketch_dir
sketch_path = None
if os.path.isdir(sketch_directory):
sketch_path = os.path.join(sketch_directory, sketch_name)
if not os.path.exists(sketch_path):
os.makedirs(sketch_path)
sketch_path = os.path.join(sketch_path, sketch_name + '.ino')
else:
print('The sketch directory in the settings does not exists!')
return sketch_path
def main():
# This should never be executed
print("This is the SketchCreator main")
if __name__ == "__main__":
main()
from __future__ import unicode_literals, absolute_import
import os
import unittest
import mock
import ParentDirToSysPath
from BlocklyServerCompiler import BlocklyRequestHandler
class BlocklyRequestHandlerTestCase(unittest.TestCase):
"""
Tests for BlocklyRequestHandler module
"""
#
# Command line tests
#
@mock.patch('BlocklyServerCompiler.BlocklyRequestHandler.os')
@mock.patch('BlocklyServerCompiler.BlocklyRequestHandler.create_sketch_default')
@mock.patch.object(BlocklyRequestHandler.ServerCompilerSettings, 'get_compiler_dir', autospec=True)
def test_command_line_launch(self, mock_settings, mock_sketch, mock_os):
"""
Tests that a compiler path and arduino sketch path can be set
and that a command line can be launched to open the sketch in the
Arduino IDE
"""
# Set the compiler settings
test_sketch_path = os.path.join(os.getcwd(), 'sketch.ino')
mock_sketch.return_value = test_sketch_path
test_compiler_dir = os.path.join(os.getcwd(), 'arduino.exe')
mock_settings = BlocklyRequestHandler.ServerCompilerSettings()
mock_settings.__compiler_dir__ = test_compiler_dir
BlocklyRequestHandler.ServerCompilerSettings().launch_IDE_only = True
# Build expected string and run test
expected_command = test_compiler_dir + ' "' + test_sketch_path + '"'
BlocklyRequestHandler.load_sketch()
mock_os.system.assert_called_with(expected_command)
#
# Tests for checking browsing for paths and files
#
@mock.patch('BlocklyServerCompiler.BlocklyRequestHandler.tkFileDialog')
def test_browse_file(self, mock_file_select):
test_file = 'test_file'
mock_file_select.askopenfilename.return_value = test_file
new_file = BlocklyRequestHandler.browse_file()
self.assertEqual(new_file, test_file)
def test_browse_file_cancel(self):
canceled_file = ''
print('A file browser window will open, to successfully run this test '
'press cancel or close the window!!!\n')
#raw_input('Press "Enter" to continue...')
function_file = BlocklyRequestHandler.browse_file()
self.assertEqual(canceled_file, function_file)
@mock.patch('BlocklyServerCompiler.BlocklyRequestHandler.tkFileDialog')
def test_browse_path(self, mock_path_select):
test_path = 'test_path'
mock_path_select.askopenfilename.return_value = test_path
new_path = BlocklyRequestHandler.browse_file()
self.assertEqual(new_path, test_path)
def test_browse_path_cancel(self):
canceled_path = ''
print('A path browser window will open, to successfully run this test '
'press cancel or close the window!!!\n')
#raw_input('Press "Enter" to continue...')
function_path = BlocklyRequestHandler.browse_dir()
self.assertEqual(canceled_path, function_path)
if __name__ == '__main__':
unittest.main()
"""
This is a very simple module to import the parent directory to the system path
so that all the test files are able to correctly import the required module
under test
"""
from __future__ import unicode_literals, absolute_import
import os
import sys
# Import the parent directory into the system path
#sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
sys.path.append(os.path.dirname(os.path.dirname(os.getcwd())))
\ No newline at end of file
[Arduino_IDE]
arduino_exec_path = C:\IDEs\arduino-1.5.6-r2\arduino.exe
arduino_board = Uno
arduino_com_port = COM1
[Arduino_Sketch]
sketch_name = BlocklyDuinoSketch
sketch_directory = C:\Users\nggmcao\Desktop\volunteering\BlocklyDuino-BtW\BlocklyServerCompiler\tests
from __future__ import unicode_literals, absolute_import
import os
import unittest
import mock
import ParentDirToSysPath
from BlocklyServerCompiler.ServerCompilerSettings import ServerCompilerSettings
class ServerCompilerSettingsTestCase(unittest.TestCase):
"""
Tests for ServerCompilerSettings
"""
#
# Testing the class singlentoness
#
def test_singleton(self):
# Testing if singleton is working
instance_1 = ServerCompilerSettings()
instance_2 = ServerCompilerSettings()
self.assertEqual(id(instance_1), id(instance_2))
def test_destructor(self):
ServerCompilerSettings()
instance_1 = ServerCompilerSettings()
instance_1._drop()
self.assertEqual(instance_1.__singleton_instance__, None)
#
# Testing the compiler_dir getter and setter
#
def test_read_compiler_dir(self):
self.assertEqual(ServerCompilerSettings().compiler_dir, ServerCompilerSettings().__compiler_dir__)
@mock.patch('BlocklyServerCompiler.ServerCompilerSettings.os.path.exists')
def test_write_compiler_dir_invalid(self, mock_os_path_exists):
"""
Tests path doesn't get save if:
A file that does not exists
Just a folder
A non executable file
"""
# TODO: a file that 'exists but does not execute' is not done
# Random file
mock_os_path_exists.return_value = False
original_dir = ServerCompilerSettings().compiler_dir
new_dir = os.path.join(os.getcwd(), 'random.exe')
ServerCompilerSettings().compiler_dir = new_dir
self.assertNotEqual(new_dir, ServerCompilerSettings().compiler_dir)
self.assertEqual(original_dir, ServerCompilerSettings().compiler_dir)
# Just a folder
mock_os_path_exists.return_value = True
new_dir = os.getcwd()
ServerCompilerSettings().compiler_dir = new_dir
self.assertNotEqual(new_dir, ServerCompilerSettings().compiler_dir)
self.assertEqual(original_dir, ServerCompilerSettings().compiler_dir)
# Non .exe file
mock_os_path_exists.return_value = True
new_dir = os.path.join(os.getcwd(), 'arduino.txt')
ServerCompilerSettings().compiler_dir = new_dir
self.assertNotEqual(new_dir, ServerCompilerSettings().compiler_dir)
self.assertEqual(original_dir, ServerCompilerSettings().compiler_dir)
@mock.patch('BlocklyServerCompiler.ServerCompilerSettings.os.path.exists')
def test_write_compiler_dir_valid(self, mock_os_path_exists):
mock_os_path_exists.return_value = True
new_dir = os.path.join(os.getcwd(), 'arduino.exe')
ServerCompilerSettings().compiler_dir = new_dir
self.assertEqual(new_dir, ServerCompilerSettings().compiler_dir)
#
# Testing the settings file
#
def test_settings_file_creation(self):
""" Need to find a way to test this one """
ServerCompilerSettings().save_settings()
self.assertEqual(0,0)
def test_settings_file_read(self):
ServerCompilerSettings()
ServerCompilerSettings().set_default_settings()
ServerCompilerSettings().read_settings_file()
ServerCompilerSettings().save_settings()
if __name__ == '__main__':
unittest.main()
from __future__ import unicode_literals, absolute_import
import os
import unittest
import ParentDirToSysPath
from BlocklyServerCompiler.SketchCreator import SketchCreator
from BlocklyServerCompiler.ServerCompilerSettings import ServerCompilerSettings
class SketchCreatorTestCase(unittest.TestCase):
"""
Tests for SketchCreator class
"""
#
# File creation
#
def test_create_directory(self):
""" Tests to see if an Arduino Sketch is created in a new location """
test_sketch_name = 'TestTemp_Sketch'
ServerCompilerSettings().sketch_dir = os.getcwd()
ServerCompilerSettings().sketch_name = test_sketch_name
test_path = os.path.join(os.getcwd(),
test_sketch_name,
test_sketch_name + '.ino')
# It should be save to create and delete in test folder
if os.path.exists(test_path):
os.remove(test_path)
print('\ntest_createDirectory() message:')
print("Check location: " + test_path)
instance_1 = SketchCreator()
created_sketch_path = instance_1.create_sketch()
self.assertEqual(test_path, created_sketch_path)
#
# File creation with code
#
def test_create_sketch_with_code(self):
sketch_code_write = 'This is a test'
instance_1 = SketchCreator()
sketch_location = instance_1.create_sketch(sketch_code_write)
print('\ntest_create_sketch_with_code() message:')
print("Check location: " + sketch_location)
arduino_sketch = open(sketch_location, 'r')
sketch_code_read = arduino_sketch.read()
arduino_sketch.close()
self.assertEqual(sketch_code_write, sketch_code_read)
if __name__ == '__main__':
unittest.main()
from __future__ import unicode_literals, absolute_import
import unittest
import ParentDirToSysPath
from BlocklyServerCompiler.XXXX import XXXX
class XXXXTestCase(unittest.TestCase):
"""
Tests for XXXX
"""
#
# Test category
#
def test_XXX(self):
""" Tests description if not obvious """
self.assertEqual(a, b)
self.assertNotEqual(a, b)
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python2
# ##############################################################################
# The comment above works if the Python Launcher for Windows path included
# in Python>3.3 does not conflict with the py.exe file added to "C:\Windows"
# Currently this application should work in Python >2.6 and 3.x, although
# python 2 is prefered, as it is the main development platform.
###############################################################################
from __future__ import unicode_literals, absolute_import
import os
import platform
import threading
import webbrowser
import ArduinoServerCompiler.ServerCompilerSettings
import ArduinoServerCompiler.BlocklyHTTPServer
def open_browser(filetoload):
""" Start a browser after waiting for half a second. """
def _open_browser():
webbrowser.open('http://%s:%s/%s' %
(ArduinoServerCompiler.BlocklyHTTPServer.ADDRESS,
ArduinoServerCompiler.BlocklyHTTPServer.PORT,
filetoload))
thread = threading.Timer(0.5, _open_browser)
thread.start()
def main():
"""
Initialises the Settings singleton and starts the HTTP Server
"""
print('Running Python version ' + platform.python_version())
print("\n======= Loading Settings =======")
ArduinoServerCompiler.ServerCompilerSettings.ServerCompilerSettings()
current_dir = os.getcwd()
app_index = os.path.basename(os.path.normpath(current_dir))
app_index = os.path.join(app_index, 'apps', 'arduino')
open_browser(app_index)
print("\n======= Starting Server =======")
#parent directory as working directory due to closure requirements
parent_dir = os.path.dirname(os.getcwd())
ArduinoServerCompiler.BlocklyHTTPServer.start_server(parent_dir)
if __name__ == "__main__":
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment