Skip to content

Commit 6878a90

Browse files
Added python-matter-server and thread
1 parent 40f1f47 commit 6878a90

File tree

8 files changed

+1526
-0
lines changed

8 files changed

+1526
-0
lines changed

.templates/otbr/build.py

Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
#!/usr/bin/env python3
2+
3+
issues = {} # Returned issues dict
4+
buildHooks = {} # Options, and others hooks
5+
haltOnErrors = True
6+
7+
# Main wrapper function. Required to make local vars work correctly
8+
def main():
9+
import os
10+
import time
11+
import ruamel.yaml
12+
import signal
13+
import sys
14+
from blessed import Terminal
15+
16+
from deps.chars import specialChars, commonTopBorder, commonBottomBorder, commonEmptyLine, padText
17+
from deps.consts import servicesDirectory, templatesDirectory, buildSettingsFileName, buildCache, servicesFileName
18+
from deps.common_functions import getExternalPorts, getInternalPorts, checkPortConflicts, enterPortNumberWithWhiptail
19+
20+
yaml = ruamel.yaml.YAML()
21+
yaml.preserve_quotes = True
22+
23+
global dockerComposeServicesYaml # The loaded memory YAML of all checked services
24+
global toRun # Switch for which function to run when executed
25+
global buildHooks # Where to place the options menu result
26+
global currentServiceName # Name of the current service
27+
global issues # Returned issues dict
28+
global haltOnErrors # Turn on to allow erroring
29+
global hideHelpText # Showing and hiding the help controls text
30+
global serviceService
31+
global hasRebuiltHardwareSelection
32+
33+
serviceService = servicesDirectory + currentServiceName
34+
serviceTemplate = templatesDirectory + currentServiceName
35+
buildSettings = serviceService + buildSettingsFileName
36+
37+
hasRebuiltHardwareSelection = False
38+
39+
try: # If not already set, then set it.
40+
hideHelpText = hideHelpText
41+
except:
42+
hideHelpText = False
43+
44+
documentationHint = 'https://openthread.io/guides/border-router/docker'
45+
46+
# runtime vars
47+
portConflicts = []
48+
49+
# This lets the menu know whether to put " >> Options " or not
50+
# This function is REQUIRED.
51+
def checkForOptionsHook():
52+
try:
53+
buildHooks["options"] = callable(runOptionsMenu)
54+
except:
55+
buildHooks["options"] = False
56+
return buildHooks
57+
return buildHooks
58+
59+
# This function is REQUIRED.
60+
def checkForPreBuildHook():
61+
try:
62+
buildHooks["preBuildHook"] = callable(preBuild)
63+
except:
64+
buildHooks["preBuildHook"] = False
65+
return buildHooks
66+
return buildHooks
67+
68+
# This function is REQUIRED.
69+
def checkForPostBuildHook():
70+
try:
71+
buildHooks["postBuildHook"] = callable(postBuild)
72+
except:
73+
buildHooks["postBuildHook"] = False
74+
return buildHooks
75+
return buildHooks
76+
77+
# This function is REQUIRED.
78+
def checkForRunChecksHook():
79+
try:
80+
buildHooks["runChecksHook"] = callable(runChecks)
81+
except:
82+
buildHooks["runChecksHook"] = False
83+
return buildHooks
84+
return buildHooks
85+
86+
# This service will not check anything unless this is set
87+
# This function is optional, and will run each time the menu is rendered
88+
def runChecks():
89+
checkForIssues()
90+
return []
91+
92+
# This function is optional, and will run after the docker-compose.yml file is written to disk.
93+
def postBuild():
94+
return True
95+
96+
# This function is optional, and will run just before the build docker-compose.yml code.
97+
def preBuild():
98+
global dockerComposeServicesYaml
99+
global currentServiceName
100+
with open("{serviceDir}{buildSettings}".format(serviceDir=serviceService, buildSettings=buildSettingsFileName)) as objHardwareListFile:
101+
otbrYamlBuildOptions = yaml.load(objHardwareListFile)
102+
103+
with open((r'%s/' % serviceTemplate) + servicesFileName) as objServiceFile:
104+
serviceYamlTemplate = yaml.load(objServiceFile)
105+
106+
oldBuildCache = {}
107+
try:
108+
with open(r'%s' % buildCache) as objBuildCache:
109+
oldBuildCache = yaml.load(objBuildCache)
110+
except:
111+
pass
112+
113+
114+
buildCacheServices = {}
115+
if "services" in oldBuildCache:
116+
buildCacheServices = oldBuildCache["services"]
117+
118+
if not os.path.exists(serviceService):
119+
os.makedirs(serviceService, exist_ok=True)
120+
121+
try:
122+
if currentServiceName in dockerComposeServicesYaml:
123+
if otbrYamlBuildOptions["hardware"] and len(otbrYamlBuildOptions["hardware"]) == 1:
124+
newCommand = "--radio-url spinel+hdlc+uart://" + otbrYamlBuildOptions["hardware"][0]
125+
dockerComposeServicesYaml[currentServiceName]["command"] = newCommand
126+
dockerComposeServicesYaml[currentServiceName]["volumes"].append(otbrYamlBuildOptions["hardware"][0] + ":" + otbrYamlBuildOptions["hardware"][0])
127+
128+
except Exception as err:
129+
print("Error setting otbr hardware: ", err)
130+
time.sleep(10)
131+
return False
132+
133+
134+
135+
136+
return True
137+
138+
# #####################################
139+
# Supporting functions below
140+
# #####################################
141+
142+
def checkForIssues():
143+
with open("{serviceDir}{buildSettings}".format(serviceDir=serviceService, buildSettings=buildSettingsFileName)) as objHardwareListFile:
144+
otbrYamlBuildOptions = yaml.load(objHardwareListFile)
145+
for (index, serviceName) in enumerate(dockerComposeServicesYaml):
146+
if not currentServiceName == serviceName: # Skip self
147+
currentServicePorts = getExternalPorts(currentServiceName, dockerComposeServicesYaml)
148+
portConflicts = checkPortConflicts(serviceName, currentServicePorts, dockerComposeServicesYaml)
149+
if (len(portConflicts) > 0):
150+
issues["portConflicts"] = portConflicts
151+
152+
print(otbrYamlBuildOptions["hardware"])
153+
if not otbrYamlBuildOptions["hardware"] or len(otbrYamlBuildOptions["hardware"]) < 1:
154+
issues["hardware"] = "No Thread radio selected."
155+
if otbrYamlBuildOptions["hardware"] and len(otbrYamlBuildOptions["hardware"]) > 1:
156+
issues["hardware"] = "Two or more thread radios selected. The first listed one will be used"
157+
158+
159+
160+
161+
# #####################################
162+
# End Supporting functions
163+
# #####################################
164+
165+
166+
############################
167+
# Menu Logic
168+
############################
169+
170+
global currentMenuItemIndex
171+
global selectionInProgress
172+
global menuNavigateDirection
173+
global needsRender
174+
175+
selectionInProgress = True
176+
currentMenuItemIndex = 0
177+
menuNavigateDirection = 0
178+
needsRender = 1
179+
term = Terminal()
180+
hotzoneLocation = [((term.height // 16) + 6), 0]
181+
182+
def goBack():
183+
global selectionInProgress
184+
global needsRender
185+
selectionInProgress = False
186+
needsRender = 1
187+
return True
188+
189+
def selectMatterHardware():
190+
global needsRender
191+
global hasRebuiltHardwareSelection
192+
threadSelectHardwareFilePath = "./.templates/otbr/select_hardware.py"
193+
with open(threadSelectHardwareFilePath, "rb") as pythonDynamicImportFile:
194+
code = compile(pythonDynamicImportFile.read(), threadSelectHardwareFilePath, "exec")
195+
# execGlobals = globals()
196+
# execLocals = locals()
197+
execGlobals = {
198+
"currentServiceName": currentServiceName,
199+
"renderMode": renderMode
200+
}
201+
execLocals = {}
202+
screenActive = False
203+
exec(code, execGlobals, execLocals)
204+
signal.signal(signal.SIGWINCH, onResize)
205+
try:
206+
hasRebuiltHardwareSelection = execGlobals["hasRebuiltHardwareSelection"]
207+
except:
208+
hasRebuiltHardwareSelection = False
209+
screenActive = True
210+
needsRender = 1
211+
212+
def onResize(sig, action):
213+
global threadBuildOptions
214+
global currentMenuItemIndex
215+
mainRender(1, threadBuildOptions, currentMenuItemIndex)
216+
217+
threadBuildOptions = []
218+
219+
def createMenu():
220+
global threadBuildOptions
221+
global serviceService
222+
threadBuildOptions = []
223+
224+
225+
if os.path.exists("{buildSettings}".format(buildSettings=buildSettings)):
226+
threadBuildOptions.insert(0, ["Change selected hardware", selectMatterHardware])
227+
else:
228+
threadBuildOptions.insert(0, ["Select hardware", selectMatterHardware])
229+
230+
threadBuildOptions.append(["Go back", goBack])
231+
232+
def runOptionsMenu():
233+
createMenu()
234+
menuEntryPoint()
235+
return True
236+
237+
def renderHotZone(term, menu, selection, hotzoneLocation):
238+
lineLengthAtTextStart = 71
239+
print(term.move(hotzoneLocation[0], hotzoneLocation[1]))
240+
for (index, menuItem) in enumerate(menu):
241+
toPrint = ""
242+
if index == selection:
243+
toPrint += ('{bv} -> {t.blue_on_green} {title} {t.normal} <-'.format(t=term, title=menuItem[0], bv=specialChars[renderMode]["borderVertical"]))
244+
else:
245+
toPrint += ('{bv} {t.normal} {title} '.format(t=term, title=menuItem[0], bv=specialChars[renderMode]["borderVertical"]))
246+
247+
for i in range(lineLengthAtTextStart - len(menuItem[0])):
248+
toPrint += " "
249+
250+
toPrint += "{bv}".format(bv=specialChars[renderMode]["borderVertical"])
251+
252+
toPrint = term.center(toPrint)
253+
254+
print(toPrint)
255+
256+
def mainRender(needsRender, menu, selection):
257+
global hasRebuiltHardwareSelection
258+
term = Terminal()
259+
260+
if needsRender == 1:
261+
print(term.clear())
262+
print(term.move_y(term.height // 16))
263+
print(term.black_on_cornsilk4(term.center('IOTstack Thread Options')))
264+
print("")
265+
print(term.center(commonTopBorder(renderMode)))
266+
print(term.center(commonEmptyLine(renderMode)))
267+
print(term.center("{bv} Select Option to configure {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
268+
print(term.center(commonEmptyLine(renderMode)))
269+
270+
if needsRender >= 1:
271+
renderHotZone(term, menu, selection, hotzoneLocation)
272+
273+
if needsRender == 1:
274+
if os.path.exists("{buildSettings}".format(buildSettings=buildSettings)):
275+
if hasRebuiltHardwareSelection:
276+
print(term.center(commonEmptyLine(renderMode)))
277+
print(term.center('{bv} {t.grey_on_blue4} {text} {t.normal}{t.white_on_black}{t.normal} {bv}'.format(t=term, text="Hardware list has been rebuilt: build_settings.yml", bv=specialChars[renderMode]["borderVertical"])))
278+
else:
279+
print(term.center(commonEmptyLine(renderMode)))
280+
print(term.center('{bv} {t.grey_on_blue4} {text} {t.normal}{t.white_on_black}{t.normal} {bv}'.format(t=term, text="Using existing build_settings.yml for hardware installation", bv=specialChars[renderMode]["borderVertical"])))
281+
else:
282+
print(term.center(commonEmptyLine(renderMode)))
283+
print(term.center(commonEmptyLine(renderMode)))
284+
if not hideHelpText:
285+
print(term.center(commonEmptyLine(renderMode)))
286+
print(term.center("{bv} Controls: {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
287+
print(term.center("{bv} [Up] and [Down] to move selection cursor {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
288+
print(term.center("{bv} [H] Show/hide this text {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
289+
print(term.center("{bv} [Enter] to run command or save input {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
290+
print(term.center("{bv} [Escape] to go back to build stack menu {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
291+
print(term.center(commonEmptyLine(renderMode)))
292+
print(term.center("{bv} There are extra steps you need to do before Thread will work. Be sure {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
293+
print(term.center("{bv} to read the documentation. IPv6 and flashing custon firmware on {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
294+
print(term.center("{bv} a Thread ready USB radio. {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
295+
print(term.center(commonEmptyLine(renderMode)))
296+
if len(documentationHint) > 1:
297+
if len(documentationHint) > 56:
298+
documentationAndPadding = padText(documentationHint, 71)
299+
print(term.center("{bv} Documentation: {bv}".format(bv=specialChars[renderMode]["borderVertical"])))
300+
print(term.center("{bv} {dap} {bv}".format(bv=specialChars[renderMode]["borderVertical"], dap=documentationAndPadding)))
301+
else:
302+
documentationAndPadding = padText(documentationHint, 56)
303+
print(term.center("{bv} Documentation: {dap} {bv}".format(bv=specialChars[renderMode]["borderVertical"], dap=documentationAndPadding)))
304+
print(term.center(commonEmptyLine(renderMode)))
305+
print(term.center(commonEmptyLine(renderMode)))
306+
print(term.center(commonBottomBorder(renderMode)))
307+
308+
def runSelection(selection):
309+
import types
310+
global threadBuildOptions
311+
if len(threadBuildOptions[selection]) > 1 and isinstance(threadBuildOptions[selection][1], types.FunctionType):
312+
threadBuildOptions[selection][1]()
313+
else:
314+
print(term.green_reverse('IOTstack Error: No function assigned to menu item: "{}"'.format(nodeRedBuildOptions[selection][0])))
315+
316+
def isMenuItemSelectable(menu, index):
317+
if len(menu) > index:
318+
if len(menu[index]) > 2:
319+
if menu[index][2]["skip"] == True:
320+
return False
321+
return True
322+
323+
def menuEntryPoint():
324+
# These need to be reglobalised due to eval()
325+
global currentMenuItemIndex
326+
global selectionInProgress
327+
global menuNavigateDirection
328+
global needsRender
329+
global hideHelpText
330+
global threadBuildOptions
331+
term = Terminal()
332+
with term.fullscreen():
333+
menuNavigateDirection = 0
334+
mainRender(needsRender, threadBuildOptions, currentMenuItemIndex)
335+
selectionInProgress = True
336+
with term.cbreak():
337+
while selectionInProgress:
338+
menuNavigateDirection = 0
339+
340+
if needsRender: # Only rerender when changed to prevent flickering
341+
mainRender(needsRender, threadBuildOptions, currentMenuItemIndex)
342+
needsRender = 0
343+
344+
key = term.inkey(esc_delay=0.05)
345+
if key.is_sequence:
346+
if key.name == 'KEY_TAB':
347+
menuNavigateDirection += 1
348+
if key.name == 'KEY_DOWN':
349+
menuNavigateDirection += 1
350+
if key.name == 'KEY_UP':
351+
menuNavigateDirection -= 1
352+
if key.name == 'KEY_LEFT':
353+
goBack()
354+
if key.name == 'KEY_ENTER':
355+
runSelection(currentMenuItemIndex)
356+
if key.name == 'KEY_ESCAPE':
357+
return True
358+
elif key:
359+
if key == 'h': # H pressed
360+
if hideHelpText:
361+
hideHelpText = False
362+
else:
363+
hideHelpText = True
364+
mainRender(1, threadBuildOptions, currentMenuItemIndex)
365+
366+
if menuNavigateDirection != 0: # If a direction was pressed, find next selectable item
367+
currentMenuItemIndex += menuNavigateDirection
368+
currentMenuItemIndex = currentMenuItemIndex % len(threadBuildOptions)
369+
needsRender = 2
370+
371+
while not isMenuItemSelectable(threadBuildOptions, currentMenuItemIndex):
372+
currentMenuItemIndex += menuNavigateDirection
373+
currentMenuItemIndex = currentMenuItemIndex % len(threadBuildOptions)
374+
return True
375+
376+
377+
if haltOnErrors:
378+
eval(toRun)()
379+
else:
380+
try:
381+
eval(toRun)()
382+
except:
383+
pass
384+
385+
# This check isn't required, but placed here for debugging purposes
386+
global currentServiceName # Name of the current service
387+
if currentServiceName == 'otbr':
388+
main()
389+
else:
390+
print("Error. '{}' Tried to run 'otbr' config".format(currentServiceName))

0 commit comments

Comments
 (0)