Skip to content

Commit ab42db8

Browse files
authored
Automatically configure MOT boot settings on reboot (#30)
* add auto configure on reboot * move connection functions to their own module * move connection functions to their own module * add a pause before config * add a pause in sendline * send \r on blank line * try \n for blank line * fix telnet.sendline * have check promt allow unknown state for bootloop recovery * make check_prompt cope with boot loops
1 parent 9b4243c commit ab42db8

File tree

5 files changed

+146
-98
lines changed

5 files changed

+146
-98
lines changed

rtems-proxy.code-workspace

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
},
99
{
1010
"path": "../i04-services"
11-
},
12-
{
13-
"path": "../rf-services"
1411
}
1512
],
1613
"settings": {}

src/rtems_proxy/__main__.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
from . import __version__
1313
from .configure import Configure
14+
from .connect import ioc_connect, motboot_connect, report
1415
from .copy import copy_rtems
1516
from .globals import GLOBALS
16-
from .telnet import ioc_connect, motboot_connect, report
1717

1818
__all__ = ["main"]
1919

@@ -52,6 +52,9 @@ def start(
5252
reboot: bool = typer.Option(
5353
True, "--reboot/--no-reboot", help="reboot the IOC first"
5454
),
55+
configure: bool = typer.Option(
56+
True, "--configure/--no-configure", help="configure motBoot when rebooting"
57+
),
5558
raise_errors: bool = typer.Option(
5659
True, "--raise-errors/--no-raise-errors", help="raise errors instead of exiting"
5760
),
@@ -81,7 +84,11 @@ def start(
8184
if connect:
8285
assert GLOBALS.RTEMS_CONSOLE, "No RTEMS console defined"
8386
ioc_connect(
84-
GLOBALS.RTEMS_CONSOLE, reboot=reboot, attach=True, raise_errors=raise_errors
87+
GLOBALS.RTEMS_CONSOLE,
88+
reboot=reboot,
89+
attach=True,
90+
raise_errors=raise_errors,
91+
configure=configure,
8592
)
8693
else:
8794
report("IOC console connection disabled. ")
@@ -189,9 +196,8 @@ def configure(
189196
else:
190197
assert GLOBALS.RTEMS_CONSOLE, "No RTEMS console defined"
191198

192-
config = Configure(None, debug=debug)
193199
telnet = motboot_connect(GLOBALS.RTEMS_CONSOLE, use_console=use_console)
194-
config = Configure(telnet, debug=debug, dry_run=dry_run)
200+
config = Configure(telnet, debug=debug, dry_run=False)
195201
config.apply_settings()
196202
telnet.close()
197203
if attach:

src/rtems_proxy/configure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def apply_nvm(self, variable: str, value: str | None):
3535
else:
3636
self.telnet.sendline(f"gevE {variable}")
3737
self.telnet.expect(r"\(Blank line terminates input.\)")
38-
self.telnet.sendline(value or "")
38+
self.telnet.sendline(value)
3939
self.telnet.sendline("")
4040
self.telnet.expect(r"\?")
4141
self.telnet.sendline("Y")

src/rtems_proxy/connect.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from time import sleep
2+
3+
import pexpect
4+
5+
from .configure import Configure
6+
from .telnet import CannotConnectError, RtemsState, TelnetRTEMS
7+
from .utils import run_command
8+
9+
10+
def report(message):
11+
"""
12+
print a message that is noticeable amongst all the other output
13+
"""
14+
print(f"\n>>>> {message} <<<<\n")
15+
16+
17+
def ioc_connect(
18+
host_and_port: str,
19+
reboot: bool = False,
20+
configure: bool = True,
21+
attach: bool = True,
22+
raise_errors: bool = False,
23+
):
24+
"""
25+
Entrypoint to make a connection to an RTEMS IOC over telnet.
26+
Once connected, enters an interactive user session with the IOC.
27+
28+
args:
29+
host_and_port: 'hostname:port' of the IOC to connect to
30+
reboot: reboot the IOC to pick up new binaries/startup/epics db
31+
"""
32+
telnet = TelnetRTEMS(host_and_port, reboot)
33+
34+
try:
35+
telnet.connect()
36+
37+
# this will untangle a partially executed gevEdit command
38+
for _ in range(3):
39+
telnet.sendline("\r")
40+
41+
current = telnet.check_prompt(retries=5, timeout=1)
42+
match current:
43+
case RtemsState.MOT:
44+
report("At MOTBoot prompt")
45+
reboot = True
46+
case RtemsState.UNKNOWN:
47+
report("Current IOC state unknown, attempting reboot ...")
48+
reboot = True
49+
case RtemsState.IOC:
50+
report("At IOC shell prompt")
51+
52+
if reboot:
53+
if configure:
54+
report("Rebooting to configure motBoot settings")
55+
telnet.get_boot_prompt(retries=10)
56+
sleep(1)
57+
cfg = Configure(telnet)
58+
cfg.apply_settings()
59+
else:
60+
report("Rebooting into IOC shell")
61+
62+
telnet.get_epics_prompt(retries=10)
63+
else:
64+
report("Auto reboot disabled. Skipping reboot")
65+
66+
except (CannotConnectError, pexpect.exceptions.TIMEOUT):
67+
report("Connection failed, Exiting.")
68+
telnet.close()
69+
raise
70+
71+
except Exception as e:
72+
# flush any remaining buffered output to stdout
73+
telnet.flush_remaining_output()
74+
report(f"An error occurred: {e}")
75+
telnet.close()
76+
if raise_errors:
77+
raise
78+
79+
telnet.close()
80+
if attach:
81+
report("Connecting to IOC console, hit enter for a prompt")
82+
run_command(telnet.command)
83+
84+
85+
def motboot_connect(
86+
host_and_port: str, reboot: bool = False, use_console: bool = False
87+
) -> TelnetRTEMS:
88+
"""
89+
Connect to the MOTBoot bootloader prompt, rebooting if needed.
90+
91+
Returns a TelnetRTEMS object that is connected to the MOTBoot bootloader
92+
"""
93+
telnet = TelnetRTEMS(host_and_port, ioc_reboot=reboot, use_console=use_console)
94+
telnet.connect()
95+
96+
# this will untangle a partially executed gevEdit command
97+
for _ in range(3):
98+
telnet.sendline("\r")
99+
100+
telnet.get_boot_prompt()
101+
102+
return telnet

src/rtems_proxy/telnet.py

Lines changed: 33 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
import pexpect
77

8-
from .utils import run_command
9-
108

119
class CannotConnectError(Exception):
1210
pass
@@ -78,6 +76,7 @@ def connect(self):
7876
logfile=sys.stdout,
7977
echo=False,
8078
codec_errors="ignore",
79+
timeout=5,
8180
)
8281
try:
8382
# first check for connection refusal
@@ -89,7 +88,7 @@ def connect(self):
8988
report("Cannot connect to remote IOC, connection in use?")
9089
raise CannotConnectError
9190

92-
def check_prompt(self, retries) -> RtemsState:
91+
def check_prompt(self, retries, timeout=15) -> RtemsState:
9392
"""
9493
Determine if we are currently seeing an IOC shell prompt or
9594
bootloader. Because there is a possibility that we are in the middle
@@ -104,24 +103,33 @@ def check_prompt(self, retries) -> RtemsState:
104103
self._child.sendline(self.IOC_CHECK)
105104
self._child.expect(self.IOC_RESPONSE, timeout=1)
106105
except pexpect.exceptions.TIMEOUT:
107-
try:
108-
# see if we are in the bootloader
109-
self._child.sendline()
110-
self._child.expect(self.MOT_PROMPT, timeout=1)
111-
except pexpect.exceptions.TIMEOUT:
112-
# current state unknown. wait and retry
113-
sleep(15)
114-
else:
115-
report("Currently in bootloader")
116-
return RtemsState.MOT
106+
pass
117107
else:
118-
report("Currently in IOC shell")
119108
return RtemsState.IOC
120109

110+
try:
111+
# see if we are in the bootloader
112+
self._child.sendline()
113+
self._child.expect(self.MOT_PROMPT, timeout=1)
114+
except pexpect.exceptions.TIMEOUT:
115+
pass
116+
else:
117+
return RtemsState.MOT
118+
119+
try:
120+
# current state unknown. check for mot start prompt
121+
# in case we are in a boot loop
122+
self._child.expect(self.CONTINUE, timeout=timeout)
123+
except pexpect.exceptions.TIMEOUT:
124+
pass
125+
else:
126+
# send escape to get into the bootloader
127+
self._child.sendline(chr(27))
128+
return RtemsState.MOT
129+
121130
report(f"Retry {retry + 1} of get current status")
122131

123-
report("Current state UNKNOWN")
124-
raise CannotConnectError("Current state of remote IOC unknown")
132+
return RtemsState.UNKNOWN
125133

126134
def reboot(self, into: RtemsState):
127135
"""
@@ -145,6 +153,8 @@ def reboot(self, into: RtemsState):
145153
# send space to boot the IOC
146154
self._child.send(" ")
147155

156+
self.ioc_rebooted = True
157+
148158
def get_epics_prompt(self, retries=5):
149159
"""
150160
Get to the IOC shell prompt, if the IOC is not already running, reboot
@@ -153,27 +163,26 @@ def get_epics_prompt(self, retries=5):
153163
"""
154164
assert self._child, "must call connect before get_epics_prompt"
155165

156-
current = self.check_prompt(retries=10)
166+
current = self.check_prompt(retries=retries)
157167

158168
if current != RtemsState.IOC or (self._ioc_reboot and not self.ioc_rebooted):
159169
sleep(0.5)
160-
report("Rebooting the IOC")
161170

171+
report("Rebooting into IOC shell")
162172
self.reboot(RtemsState.IOC)
163-
self.ioc_rebooted = True
164173

165-
current = self.check_prompt(retries=10)
174+
current = self.check_prompt(retries=retries)
166175
if current != RtemsState.IOC:
167176
raise CannotConnectError("Failed to reboot into IOC shell")
168177

169-
def get_boot_prompt(self):
178+
def get_boot_prompt(self, retries=5):
170179
"""
171180
Get to the bootloader prompt, if the IOC shell is running then exit
172181
and send appropriate commands to get to the bootloader
173182
"""
174183
assert self._child, "must call connect before get_boot_prompt"
175184

176-
current = self.check_prompt(retries=10)
185+
current = self.check_prompt(retries=retries)
177186
if current != RtemsState.MOT:
178187
# get out of the IOC and return to MOT
179188
self.reboot(RtemsState.MOT)
@@ -185,8 +194,9 @@ def sendline(self, command: str) -> None:
185194
"""
186195
Send a command to the telnet session
187196
"""
197+
# always pause a little to allow the previous expect to complete
188198
assert self._child, "must call connect before send"
189-
self._child.sendline(command)
199+
self._child.sendline(command + "\r")
190200

191201
def expect(self, pattern, timeout=10) -> None:
192202
"""
@@ -221,70 +231,3 @@ def report(message):
221231
print a message that is noticeable amongst all the other output
222232
"""
223233
print(f"\n>>>> {message} <<<<\n")
224-
225-
226-
def ioc_connect(
227-
host_and_port: str,
228-
reboot: bool = False,
229-
attach: bool = True,
230-
raise_errors: bool = False,
231-
):
232-
"""
233-
Entrypoint to make a connection to an RTEMS IOC over telnet.
234-
Once connected, enters an interactive user session with the IOC.
235-
236-
args:
237-
host_and_port: 'hostname:port' of the IOC to connect to
238-
reboot: reboot the IOC to pick up new binaries/startup/epics db
239-
"""
240-
telnet = TelnetRTEMS(host_and_port, reboot)
241-
242-
try:
243-
telnet.connect()
244-
245-
# this will untangle a partially executed gevEdit command
246-
for _ in range(3):
247-
telnet.sendline("\r")
248-
249-
if reboot:
250-
telnet.get_epics_prompt(retries=10)
251-
else:
252-
report("Auto reboot disabled. Skipping reboot")
253-
254-
except (CannotConnectError, pexpect.exceptions.TIMEOUT):
255-
report("Connection failed, Exiting.")
256-
telnet.close()
257-
raise
258-
259-
except Exception as e:
260-
# flush any remaining buffered output to stdout
261-
telnet.flush_remaining_output()
262-
report(f"An error occurred: {e}")
263-
telnet.close()
264-
if raise_errors:
265-
raise
266-
267-
telnet.close()
268-
if attach:
269-
report("Connecting to IOC console, hit enter for a prompt")
270-
run_command(telnet.command)
271-
272-
273-
def motboot_connect(
274-
host_and_port: str, reboot: bool = False, use_console: bool = False
275-
) -> TelnetRTEMS:
276-
"""
277-
Connect to the MOTBoot bootloader prompt, rebooting if needed.
278-
279-
Returns a TelnetRTEMS object that is connected to the MOTBoot bootloader
280-
"""
281-
telnet = TelnetRTEMS(host_and_port, ioc_reboot=reboot, use_console=use_console)
282-
telnet.connect()
283-
284-
# this will untangle a partially executed gevEdit command
285-
for _ in range(3):
286-
telnet.sendline("\r")
287-
288-
telnet.get_boot_prompt()
289-
290-
return telnet

0 commit comments

Comments
 (0)