Skip to content

Commit 287182c

Browse files
committed
Release 0.5
2 parents 071c924 + 16ed983 commit 287182c

File tree

9 files changed

+503
-149
lines changed

9 files changed

+503
-149
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The following will build the demo website included in this repository.
1818
$ git clone https://github.com/StoneLabs/webduino-generator
1919
$ cd webduino-generator/
2020
$ wgen generate input
21-
$ arduino main/main.ino
21+
$ wgen open
2222
2323
Or use arduino-cli to compile and upload directly from the shell (linux only)
2424
$ ./uploader.sh
@@ -29,7 +29,7 @@ Aside from build a random folder you can create a project. By default a simple h
2929
```
3030
$ wgen init
3131
$ wgen build
32-
$ arduino output/main/main.ino
32+
$ wgen open
3333
```
3434

3535
### Note

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
setup(
55
name='webduino-generator',
6-
version='0.4',
6+
version='0.5',
77
license='UNLICENSE',
88
url='https://github.com/StoneLabs/webduino-generator',
99
author='Levy Ehrstein',
@@ -17,5 +17,5 @@
1717
'console_scripts': ['webduino-generator=webduino_generator.entrypoint:main',
1818
'wgen=webduino_generator.entrypoint:main'],
1919
},
20-
install_requires=["jinja2", "rich"],
20+
install_requires=["jinja2", "rich", "simple-term-menu"],
2121
)

webduino_generator/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
__author__ = 'Levy Ehrstein'
44
__email__ = '[email protected]'
5-
__version__ = '0.4'
5+
__website__ = 'https://github.com/StoneLabs/webduino-generator'
6+
__version__ = '0.5'

webduino_generator/arduino.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import subprocess
2+
import json
3+
4+
from simple_term_menu import TerminalMenu
5+
from .helper import get_tool
6+
7+
8+
def get_ide_path(userio):
9+
# Get arduino IDE location
10+
ide_path = get_tool("arduino")
11+
if ide_path is None:
12+
userio.error("Could not locate 'arduino' command. Is the arduino IDE istalled?")
13+
userio.print("IDE located: " + ide_path, verbose=True)
14+
15+
return ide_path
16+
17+
18+
def get_cli_path(userio):
19+
# Get arduino IDE location
20+
cli_path = get_tool("arduino-cli")
21+
if cli_path is None:
22+
userio.error("Could not locate 'arduino-cli' command. Is arduino-cli istalled?")
23+
userio.print("CLI located: " + cli_path, verbose=True)
24+
25+
return cli_path
26+
27+
28+
def get_boards_json(userio, list_all=False):
29+
cli_path = get_cli_path(userio)
30+
31+
if list_all:
32+
result = subprocess.run([cli_path, "board", "listall", "--format=json"], stdout=subprocess.PIPE)
33+
else:
34+
result = subprocess.run([cli_path, "board", "list", "--format=json"], stdout=subprocess.PIPE)
35+
36+
userio.print("Called arduino-cli with:", verbose=True)
37+
userio.print(result.args, verbose=True)
38+
userio.print("Dumping arduino-cli response:", verbose=True)
39+
userio.print(result.stdout.decode('utf-8'), verbose=True)
40+
41+
if not result.returncode == 0:
42+
userio.error("arduino-cli exited with code " + str(result.returncode))
43+
44+
try:
45+
boards = json.loads(result.stdout.decode('utf-8'))
46+
except Exception:
47+
userio.error("arduino-cli returned invalid JSON")
48+
49+
return boards
50+
51+
52+
def get_boards(userio):
53+
boards = get_boards_json(userio, True)
54+
55+
# arduino-cli board listall packes the result in a dict
56+
if "boards" not in boards:
57+
userio.error("Could not parse arduino-cli output")
58+
boards = boards["boards"]
59+
60+
# Filter out invalid entries (or unwanted)
61+
boards = [board for board in boards if "name" in board]
62+
boards = [board for board in boards if "FQBN" in board]
63+
64+
# Sort boards for the user
65+
boards = sorted(boards, key=lambda board: board["name"])
66+
67+
userio.print("Dumping processed arduino-cli response:", verbose=True)
68+
userio.print(boards, verbose=True)
69+
70+
return boards
71+
72+
73+
def get_boards_connected(userio):
74+
boards = get_boards_json(userio, False)
75+
76+
processed = []
77+
for board in boards:
78+
if "boards" not in board:
79+
continue
80+
if "address" not in board:
81+
continue
82+
if len(board["boards"]) != 1:
83+
continue
84+
if "FQBN" not in board["boards"][0]:
85+
continue
86+
if "name" not in board["boards"][0]:
87+
continue
88+
processed += [{
89+
"name": board["boards"][0]["name"],
90+
"FQBN": board["boards"][0]["FQBN"],
91+
"address": board["address"]
92+
}]
93+
94+
# Sort boards for the user
95+
processed = sorted(processed, key=lambda board: board["name"])
96+
97+
userio.print("Dumping processed arduino-cli response:", verbose=True)
98+
userio.print(processed, verbose=True)
99+
100+
return processed
101+
102+
103+
def get_board(userio):
104+
boards = get_boards(userio)
105+
106+
if len(boards) == 0:
107+
userio.error("No boards found!")
108+
109+
# Query user to select a board
110+
userio.print("Please select target board:")
111+
terminal_menu = TerminalMenu([board["name"] for board in boards], menu_highlight_style=None)
112+
113+
selection = terminal_menu.show()
114+
115+
# Menu cancled by user
116+
if selection is None:
117+
exit(0)
118+
board = boards[selection]
119+
120+
userio.print("Selected board: ", verbose=True)
121+
userio.print(board, verbose=True)
122+
123+
return board["name"], board["FQBN"]
124+
125+
126+
def get_board_connected(userio):
127+
boards = get_boards_connected(userio)
128+
129+
if len(boards) == 0:
130+
userio.error("No boards found!")
131+
132+
# Query user to select a board
133+
userio.print("Please select target board:")
134+
terminal_menu = TerminalMenu([board["address"] + ": " + board["name"]
135+
for board in boards], menu_highlight_style=None)
136+
137+
selection = terminal_menu.show()
138+
139+
# Menu cancled by user
140+
if selection is None:
141+
exit(0)
142+
board = boards[selection]
143+
144+
userio.print("Selected board: ", verbose=True)
145+
userio.print(board, verbose=True)
146+
147+
return board["name"], board["FQBN"], board["address"]
148+
149+
150+
def sketch_compile(userio, sketch_path, fqbn):
151+
# Compile sketch
152+
cli_path = get_cli_path(userio)
153+
subprocess.run([cli_path, "compile", "--fqbn", fqbn, sketch_path])
154+
155+
156+
def sketch_upload(userio, sketch_path, fqbn, address):
157+
# Upload sketch
158+
cli_path = get_cli_path(userio)
159+
subprocess.run([cli_path, "upload", "-p", address, "--fqbn", fqbn, sketch_path])

webduino_generator/entrypoint.py

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import argparse
2+
import subprocess
3+
import json
24

3-
from .__init__ import __version__
5+
from .__init__ import __version__, __website__
46
from .userio import UserIO, get_ssid_pass
5-
from .helper import cpp_str_esc, cpp_img_esc, get_files_rec, shorten
6-
from .project import project_make_new, project_generate
7+
from .helper import cpp_str_esc, cpp_img_esc, get_files_rec, shorten, get_tool
8+
from .project import Project
9+
from .arduino import get_ide_path
710
from .generator import *
811

912

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

1317

1418
def command_generate(userio, args):
@@ -43,12 +47,45 @@ def command_generate(userio, args):
4347

4448

4549
def command_init(userio, args):
46-
project_make_new(userio, args.target, args.force,
47-
args.mode, args.ssid, args.port)
50+
Project.create_project(userio, args.target, args.force,
51+
args.mode, args.ssid, args.port)
4852

4953

5054
def command_build(userio, args):
51-
project_generate(userio, args.target, args.quiet)
55+
project = Project(userio, args.target)
56+
project.generate(args.quiet)
57+
58+
59+
def command_open(userio, args):
60+
userio.section("Opening project output")
61+
62+
# Get project output location
63+
project = Project(userio, args.target)
64+
sketch_path = project.get_sketch_path()
65+
userio.print("Sketch located: " + sketch_path, verbose=True)
66+
67+
# Get arduino IDE location
68+
ide_path = get_ide_path(userio)
69+
70+
# Launch IDE
71+
if args.detach:
72+
userio.print("Opening IDE detached...")
73+
subprocess.Popen([ide_path, sketch_path],
74+
stdout=subprocess.DEVNULL,
75+
stderr=subprocess.DEVNULL)
76+
else:
77+
userio.print("Opening IDE...")
78+
subprocess.call([ide_path, sketch_path])
79+
80+
81+
def command_compile(userio, args):
82+
project = Project(userio, args.target)
83+
project.compile(save=args.save, force_select=args.select_device)
84+
85+
86+
def command_upload(userio, args):
87+
project = Project(userio, args.target)
88+
project.upload()
5289

5390

5491
def main():
@@ -100,19 +137,40 @@ def main():
100137
help="Connection mode/library to be used")
101138
parser_init.add_argument("-f", "--force",
102139
action="store_true", dest='force',
103-
help="Delete files that block project creation.")
140+
help="Delete files that block project creation")
104141

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

150+
parser_open = subparsers.add_parser("open", help="Open generated code in arduino ide")
151+
parser_open.add_argument("target", metavar="target", type=str,
152+
default=".", nargs="?",
153+
help="Root folder of target project")
154+
parser_open.add_argument("-d", "--detach",
155+
action="store_true", dest='detach',
156+
help="Spawns IDE in a new thread")
157+
113158
parser_compile = subparsers.add_parser("compile", help="Compile Arduino code from current project")
159+
parser_compile.add_argument("target", metavar="target", type=str,
160+
default=".", nargs="?",
161+
help="Root folder of target project")
162+
parser_compile.add_argument("--select-device",
163+
action="store_true", dest='select_device',
164+
help="Ignore saved target device and select another one")
165+
parser_compile.add_argument("--save",
166+
action="store_true", dest='save',
167+
help="Safe selected target to project files")
168+
114169
parser_upload = subparsers.add_parser("upload", help="Upload Arduino code from current project")
115-
parser_open = subparsers.add_parser("open", help="Open generated code in arduino ide")
170+
parser_upload.add_argument("target", metavar="target", type=str,
171+
default=".", nargs="?",
172+
help="Root folder of target project")
173+
116174
parser_version = subparsers.add_parser("version", help="Display current version")
117175

118176
# Global arguments
@@ -147,11 +205,11 @@ def handle():
147205
elif args.command == "build":
148206
command_build(userio, args)
149207
elif args.command == "compile":
150-
raise NotImplementedError
208+
command_compile(userio, args)
151209
elif args.command == "upload":
152-
raise NotImplementedError
210+
command_upload(userio, args)
153211
elif args.command == "open":
154-
raise NotImplementedError
212+
command_open(userio, args)
155213
elif args.command == "generate":
156214
command_generate(userio, args)
157215
else:

webduino_generator/generator.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ def get_input_data(userio, input_path):
4040
# Get list of all files
4141
files = get_files_rec(input_path)
4242
userio.print("Processing " + str(len(files)) + " files...", verbose=True)
43+
44+
# Sort files for deterministic output
45+
files = sorted(files)
46+
4347
userio.quick_table("", ["Input Files"],
4448
[[_file] for _file in files], verbose=True)
4549

@@ -107,6 +111,17 @@ def inner(mime, mime_hash):
107111
# Try to handle file non-binary UTF-8 file.
108112
with open(os.path.join(input_path, file_name), 'r', encoding="UTF-8") as file:
109113
if (file_name.endswith(".cpp")):
114+
# Warn user when using includes
115+
line_num = 0
116+
for line in file.readlines():
117+
line_num += 1
118+
if line.startswith("#include "):
119+
userio.print("")
120+
userio.warn(file_name + " line " + str(line_num) + ":")
121+
userio.print("| Putting includes in input files is not recommended!")
122+
userio.print("| This might cause conflicts or large sketch sizes!")
123+
userio.print("| Please put them int the template files instead.")
124+
110125
# Handle dynamic content (cpp files)
111126
file_content = file.read().replace("\n", "\n\t")
112127
file_type = 2 # Dynamic content

webduino_generator/helper.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
22

3+
from shutil import which
4+
35

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

27-
rel_file = rel_file.replace("\\","/")
29+
rel_file = rel_file.replace("\\", "/")
2830
if rel_file.startswith("./"):
2931
rel_file = rel_file[2:]
3032

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

3436

3537
def shorten(text, maxLength):
36-
return str(text)[:maxLength] + ("..." if maxLength < len(str(text)) else "")
38+
return str(text)[:maxLength] + ("..." if maxLength < len(str(text)) else "")
39+
40+
41+
def get_tool(name):
42+
"""Returns absolute path of command.
43+
Returns None if command it not found."""
44+
45+
return which(name)

0 commit comments

Comments
 (0)