Skip to content

Commit

Permalink
Release 0.5
Browse files Browse the repository at this point in the history
  • Loading branch information
StoneLabs committed Aug 14, 2020
2 parents 071c924 + 16ed983 commit 287182c
Show file tree
Hide file tree
Showing 9 changed files with 503 additions and 149 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The following will build the demo website included in this repository.
$ git clone https://github.com/StoneLabs/webduino-generator
$ cd webduino-generator/
$ wgen generate input
$ arduino main/main.ino
$ wgen open
Or use arduino-cli to compile and upload directly from the shell (linux only)
$ ./uploader.sh
Expand All @@ -29,7 +29,7 @@ Aside from build a random folder you can create a project. By default a simple h
```
$ wgen init
$ wgen build
$ arduino output/main/main.ino
$ wgen open
```

### Note
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

setup(
name='webduino-generator',
version='0.4',
version='0.5',
license='UNLICENSE',
url='https://github.com/StoneLabs/webduino-generator',
author='Levy Ehrstein',
Expand All @@ -17,5 +17,5 @@
'console_scripts': ['webduino-generator=webduino_generator.entrypoint:main',
'wgen=webduino_generator.entrypoint:main'],
},
install_requires=["jinja2", "rich"],
install_requires=["jinja2", "rich", "simple-term-menu"],
)
3 changes: 2 additions & 1 deletion webduino_generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

__author__ = 'Levy Ehrstein'
__email__ = 'levyehrstein@googlemail.com'
__version__ = '0.4'
__website__ = 'https://github.com/StoneLabs/webduino-generator'
__version__ = '0.5'
159 changes: 159 additions & 0 deletions webduino_generator/arduino.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import subprocess
import json

from simple_term_menu import TerminalMenu
from .helper import get_tool


def get_ide_path(userio):
# Get arduino IDE location
ide_path = get_tool("arduino")
if ide_path is None:
userio.error("Could not locate 'arduino' command. Is the arduino IDE istalled?")
userio.print("IDE located: " + ide_path, verbose=True)

return ide_path


def get_cli_path(userio):
# Get arduino IDE location
cli_path = get_tool("arduino-cli")
if cli_path is None:
userio.error("Could not locate 'arduino-cli' command. Is arduino-cli istalled?")
userio.print("CLI located: " + cli_path, verbose=True)

return cli_path


def get_boards_json(userio, list_all=False):
cli_path = get_cli_path(userio)

if list_all:
result = subprocess.run([cli_path, "board", "listall", "--format=json"], stdout=subprocess.PIPE)
else:
result = subprocess.run([cli_path, "board", "list", "--format=json"], stdout=subprocess.PIPE)

userio.print("Called arduino-cli with:", verbose=True)
userio.print(result.args, verbose=True)
userio.print("Dumping arduino-cli response:", verbose=True)
userio.print(result.stdout.decode('utf-8'), verbose=True)

if not result.returncode == 0:
userio.error("arduino-cli exited with code " + str(result.returncode))

try:
boards = json.loads(result.stdout.decode('utf-8'))
except Exception:
userio.error("arduino-cli returned invalid JSON")

return boards


def get_boards(userio):
boards = get_boards_json(userio, True)

# arduino-cli board listall packes the result in a dict
if "boards" not in boards:
userio.error("Could not parse arduino-cli output")
boards = boards["boards"]

# Filter out invalid entries (or unwanted)
boards = [board for board in boards if "name" in board]
boards = [board for board in boards if "FQBN" in board]

# Sort boards for the user
boards = sorted(boards, key=lambda board: board["name"])

userio.print("Dumping processed arduino-cli response:", verbose=True)
userio.print(boards, verbose=True)

return boards


def get_boards_connected(userio):
boards = get_boards_json(userio, False)

processed = []
for board in boards:
if "boards" not in board:
continue
if "address" not in board:
continue
if len(board["boards"]) != 1:
continue
if "FQBN" not in board["boards"][0]:
continue
if "name" not in board["boards"][0]:
continue
processed += [{
"name": board["boards"][0]["name"],
"FQBN": board["boards"][0]["FQBN"],
"address": board["address"]
}]

# Sort boards for the user
processed = sorted(processed, key=lambda board: board["name"])

userio.print("Dumping processed arduino-cli response:", verbose=True)
userio.print(processed, verbose=True)

return processed


def get_board(userio):
boards = get_boards(userio)

if len(boards) == 0:
userio.error("No boards found!")

# Query user to select a board
userio.print("Please select target board:")
terminal_menu = TerminalMenu([board["name"] for board in boards], menu_highlight_style=None)

selection = terminal_menu.show()

# Menu cancled by user
if selection is None:
exit(0)
board = boards[selection]

userio.print("Selected board: ", verbose=True)
userio.print(board, verbose=True)

return board["name"], board["FQBN"]


def get_board_connected(userio):
boards = get_boards_connected(userio)

if len(boards) == 0:
userio.error("No boards found!")

# Query user to select a board
userio.print("Please select target board:")
terminal_menu = TerminalMenu([board["address"] + ": " + board["name"]
for board in boards], menu_highlight_style=None)

selection = terminal_menu.show()

# Menu cancled by user
if selection is None:
exit(0)
board = boards[selection]

userio.print("Selected board: ", verbose=True)
userio.print(board, verbose=True)

return board["name"], board["FQBN"], board["address"]


def sketch_compile(userio, sketch_path, fqbn):
# Compile sketch
cli_path = get_cli_path(userio)
subprocess.run([cli_path, "compile", "--fqbn", fqbn, sketch_path])


def sketch_upload(userio, sketch_path, fqbn, address):
# Upload sketch
cli_path = get_cli_path(userio)
subprocess.run([cli_path, "upload", "-p", address, "--fqbn", fqbn, sketch_path])
82 changes: 70 additions & 12 deletions webduino_generator/entrypoint.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import argparse
import subprocess
import json

from .__init__ import __version__
from .__init__ import __version__, __website__
from .userio import UserIO, get_ssid_pass
from .helper import cpp_str_esc, cpp_img_esc, get_files_rec, shorten
from .project import project_make_new, project_generate
from .helper import cpp_str_esc, cpp_img_esc, get_files_rec, shorten, get_tool
from .project import Project
from .arduino import get_ide_path
from .generator import *


def command_version(userio, args):
userio.print("Current version: " + __version__)
userio.print(__website__)


def command_generate(userio, args):
Expand Down Expand Up @@ -43,12 +47,45 @@ def command_generate(userio, args):


def command_init(userio, args):
project_make_new(userio, args.target, args.force,
args.mode, args.ssid, args.port)
Project.create_project(userio, args.target, args.force,
args.mode, args.ssid, args.port)


def command_build(userio, args):
project_generate(userio, args.target, args.quiet)
project = Project(userio, args.target)
project.generate(args.quiet)


def command_open(userio, args):
userio.section("Opening project output")

# Get project output location
project = Project(userio, args.target)
sketch_path = project.get_sketch_path()
userio.print("Sketch located: " + sketch_path, verbose=True)

# Get arduino IDE location
ide_path = get_ide_path(userio)

# Launch IDE
if args.detach:
userio.print("Opening IDE detached...")
subprocess.Popen([ide_path, sketch_path],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
else:
userio.print("Opening IDE...")
subprocess.call([ide_path, sketch_path])


def command_compile(userio, args):
project = Project(userio, args.target)
project.compile(save=args.save, force_select=args.select_device)


def command_upload(userio, args):
project = Project(userio, args.target)
project.upload()


def main():
Expand Down Expand Up @@ -100,19 +137,40 @@ def main():
help="Connection mode/library to be used")
parser_init.add_argument("-f", "--force",
action="store_true", dest='force',
help="Delete files that block project creation.")
help="Delete files that block project creation")

parser_build = subparsers.add_parser("build", help="Generate Arduino code from current project")
parser_build.add_argument("target", metavar="target", type=str,
default=".", nargs="?",
help="Target folder where project will be created")
help="Root folder of target project")
parser_build.add_argument("-q", "--quiet",
action="store_true", dest='quiet',
help="Hides password warning")

parser_open = subparsers.add_parser("open", help="Open generated code in arduino ide")
parser_open.add_argument("target", metavar="target", type=str,
default=".", nargs="?",
help="Root folder of target project")
parser_open.add_argument("-d", "--detach",
action="store_true", dest='detach',
help="Spawns IDE in a new thread")

parser_compile = subparsers.add_parser("compile", help="Compile Arduino code from current project")
parser_compile.add_argument("target", metavar="target", type=str,
default=".", nargs="?",
help="Root folder of target project")
parser_compile.add_argument("--select-device",
action="store_true", dest='select_device',
help="Ignore saved target device and select another one")
parser_compile.add_argument("--save",
action="store_true", dest='save',
help="Safe selected target to project files")

parser_upload = subparsers.add_parser("upload", help="Upload Arduino code from current project")
parser_open = subparsers.add_parser("open", help="Open generated code in arduino ide")
parser_upload.add_argument("target", metavar="target", type=str,
default=".", nargs="?",
help="Root folder of target project")

parser_version = subparsers.add_parser("version", help="Display current version")

# Global arguments
Expand Down Expand Up @@ -147,11 +205,11 @@ def handle():
elif args.command == "build":
command_build(userio, args)
elif args.command == "compile":
raise NotImplementedError
command_compile(userio, args)
elif args.command == "upload":
raise NotImplementedError
command_upload(userio, args)
elif args.command == "open":
raise NotImplementedError
command_open(userio, args)
elif args.command == "generate":
command_generate(userio, args)
else:
Expand Down
15 changes: 15 additions & 0 deletions webduino_generator/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ def get_input_data(userio, input_path):
# Get list of all files
files = get_files_rec(input_path)
userio.print("Processing " + str(len(files)) + " files...", verbose=True)

# Sort files for deterministic output
files = sorted(files)

userio.quick_table("", ["Input Files"],
[[_file] for _file in files], verbose=True)

Expand Down Expand Up @@ -107,6 +111,17 @@ def inner(mime, mime_hash):
# Try to handle file non-binary UTF-8 file.
with open(os.path.join(input_path, file_name), 'r', encoding="UTF-8") as file:
if (file_name.endswith(".cpp")):
# Warn user when using includes
line_num = 0
for line in file.readlines():
line_num += 1
if line.startswith("#include "):
userio.print("")
userio.warn(file_name + " line " + str(line_num) + ":")
userio.print("| Putting includes in input files is not recommended!")
userio.print("| This might cause conflicts or large sketch sizes!")
userio.print("| Please put them int the template files instead.")

# Handle dynamic content (cpp files)
file_content = file.read().replace("\n", "\n\t")
file_type = 2 # Dynamic content
Expand Down
13 changes: 11 additions & 2 deletions webduino_generator/helper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os

from shutil import which


def cpp_str_esc(s, encoding='ascii'):
if isinstance(s, bytes):
Expand All @@ -24,7 +26,7 @@ def get_files_rec(parent):
rel_dir = os.path.relpath(dir_, parent)
rel_file = os.path.join(rel_dir, file_name)

rel_file = rel_file.replace("\\","/")
rel_file = rel_file.replace("\\", "/")
if rel_file.startswith("./"):
rel_file = rel_file[2:]

Expand All @@ -33,4 +35,11 @@ def get_files_rec(parent):


def shorten(text, maxLength):
return str(text)[:maxLength] + ("..." if maxLength < len(str(text)) else "")
return str(text)[:maxLength] + ("..." if maxLength < len(str(text)) else "")


def get_tool(name):
"""Returns absolute path of command.
Returns None if command it not found."""

return which(name)
Loading

0 comments on commit 287182c

Please sign in to comment.