cmdif - Command Interface Processor.
This module implements a command interface for receiving commands from a
console, processing, the command, and printing results. Commands are
implemented in a standardized way (see CommandTemplate)
to make it "easy" to add additional commands, sort of like a plugin. Any
command implementation can schedule itself to run once, repeatedly, or with
periodic timing.
Most commands are added from their own modules, but there are some built-in
commands here that are used to support all commands:
- CmdHelp - provide a basic help function
- CmdConfig - provide a way to pass configuration to any command module
- CmdAdd - provide a way to add new command module to the list of commands
This module relies on the presence of the console_std
module which provides an abstraction of read and write functions for a console.
This should allow this module to be used with different mechanisms of input and
output.
CmdAdd
Bases: CommandTemplate
Provide a command that allows adding new commands.
Any command classes that are implemented and listed in
cmdclasses.py
can be added to the command list.
This allows for the existence of many kinds of commands (mostly LED
patterns) when only some will be used for a particular installation. Common
firmware can be installed on multiple controllers, and the add
command in
combination with the config
command allows a particular controller to be
configured at run time from the console command line.
Source code in ledstrip/cmdif.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158 | class CmdAdd(CommandTemplate):
"""Provide a command that allows adding new commands.
Any command classes that are implemented and listed in
[`cmdclasses.py`][ledstrip.cmdclasses] can be added to the command list.
This allows for the existence of many kinds of commands (mostly LED
patterns) when only some will be used for a particular installation. Common
firmware can be installed on multiple controllers, and the `add` command in
combination with the `config` command allows a particular controller to be
configured at run time from the console command line.
"""
helpstr = "Add new command (add,newname,ClassName)"
# provide the cmdinterface so it can add commands
def __init__(self, cmdinterface):
super().__init__()
self._ci = cmdinterface
# TODO add error handling to below
# this is called when parm[0]=='add'
# parm[1] should be the name of the command (how it will be invoked)
# parm[2] is the Class name that implements the command (from cmdclasses.py)
def render(self, parmlist, framebuf):
if len(parmlist) == 3:
cmdname = parmlist[1]
clsname = parmlist[2]
cmdobj = globals()[clsname]()
self._ci.add_cmd(cmdname, cmdobj)
return None
|
CmdConfig
Bases: CommandTemplate
Provide a configuration command, to configure other commands.
Some commands have configuration options. This command class provides a
standard way to set configuration options for a command.
It is invoked as config,<cmdname>,parm1,parm2,...
The parameters are passed through to the specified command's config handler
if it has one. No checking is done on the actual parameters.
Source code in ledstrip/cmdif.py
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 | class CmdConfig(CommandTemplate):
"""Provide a configuration command, to configure other commands.
Some commands have configuration options. This command class provides a
standard way to set configuration options for a command.
It is invoked as `config,<cmdname>,parm1,parm2,...`
The parameters are passed through to the specified command's config handler
if it has one. No checking is done on the actual parameters.
"""
helpstr = "config,<cmdname>,parm1,parm2,..."
def __init__(self, cmddict):
super().__init__()
self._dict = cmddict
# this is called when parm[0]=='config'
# parm[1] should be the command to be conigured
# parm[2] and greater are configuration parameters, which vary depending
# on the command that is being configured
def render(self, parmlist, framebuf):
# check that there at least one parameter, and that the specified
# command exists, and then pass to the command's config handler.
if len(parmlist) >= 3:
cmdname = parmlist[1]
if cmdname in self._dict:
cmdobj = self._dict[cmdname]
cmdobj.config(parmlist)
return None
|
CmdHelp
Bases: CommandTemplate
Provide a basic help command.
This command class provides two commands to show help to the user. It uses
strings that are part of the CommandTemplate as the help message.
The two commands are:
help
- show simple help message for every installed command
help,config
- show configuration options for a command, if any
The help is automatic for each command as long as the implementation
provides a help string.
Source code in ledstrip/cmdif.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 | class CmdHelp(CommandTemplate):
"""Provide a basic help command.
This command class provides two commands to show help to the user. It uses
strings that are part of the CommandTemplate as the help message.
The two commands are:
* `help` - show simple help message for every installed command
* `help,config` - show configuration options for a command, if any
The help is automatic for each command as long as the implementation
provides a help string.
"""
helpstr = "show list of commands"
def __init__(self, cmddict):
super().__init__()
self._dict = cmddict
def cmdhelp(self):
"""Show basic help messages to the user."""
console_writeln("\nCommands")
console_writeln("--------")
for cmdname, cmdobj in self._dict.items():
cmdhelp = cmdobj.helpstr
console_writeln(f"{cmdname:<8} : {cmdhelp}")
console_writeln("")
def cfghelp(self):
"""Show configuration help to the user."""
console_writeln("\nConfigs")
console_writeln("-------")
for cmdname, cmdobj in self._dict.items():
cfghelp = cmdobj.cfgstr
console_writeln(f"{cmdname:<8} : {cfghelp}")
console_writeln("")
# this is called whenever parm[0]=='help'
def render(self, parmlist, framebuf):
# decide if this is a regular help, or a config help, and then
# call the appropriate method to show the help to the user
if len(parmlist) == 2 and parmlist[1] == "config":
self.cfghelp()
else:
self.cmdhelp()
return None
|
cfghelp()
Show configuration help to the user.
Source code in ledstrip/cmdif.py
| def cfghelp(self):
"""Show configuration help to the user."""
console_writeln("\nConfigs")
console_writeln("-------")
for cmdname, cmdobj in self._dict.items():
cfghelp = cmdobj.cfgstr
console_writeln(f"{cmdname:<8} : {cfghelp}")
console_writeln("")
|
cmdhelp()
Show basic help messages to the user.
Source code in ledstrip/cmdif.py
| def cmdhelp(self):
"""Show basic help messages to the user."""
console_writeln("\nCommands")
console_writeln("--------")
for cmdname, cmdobj in self._dict.items():
cmdhelp = cmdobj.helpstr
console_writeln(f"{cmdname:<8} : {cmdhelp}")
console_writeln("")
|
CmdInterface
Provides methods for processing command line input.
Source code in ledstrip/cmdif.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281 | class CmdInterface():
"""Provides methods for processing command line input."""
def __init__(self, framebuf=None):
self._cmds = OrderedDict()
# dictionary format:
# key - command name as string
# value - CommandPattern object
self._cmds["help"] = CmdHelp(self._cmds)
self._cmds["config"] = CmdConfig(self._cmds)
self._cmds["add"] = CmdAdd(self)
# temporary additional commands
self._cmds["range"] = LedRange()
self._cmds["meter"] = LedMeter()
self._cmds["randomog"] = LedRandomOG()
self._cmds["random"] = LedRandom()
self._cmds["stop"] = LedStop()
#
self._cp = cmdparser.CmdParser()
self._framebuf = framebuf
self._cmdobj = None
self._cmdparms = None
self._sched = 0
console_init()
def add_cmd(self, cmdname: str, cmdobj: CommandTemplate) -> None:
"""Adds a new command of the specified class to the command list.
:param cmdname: name of the new command, must be unique from other
command names
:param cmdobj: a [CommandTemplate][ledstrip.cmdtemplate] subclass
implementing the new command
"""
self._cmds[cmdname] = cmdobj
def setup(self, param_list: list[str]) -> None:
"""Setup to start running a new command.
This is called by the command loop whenever a complete command line is
received. It uses the input parameter list to determine if the named
command exists. If so it sets up for executing in the exec loop.
It send `$OK` or `$ERR` to the serial console as a reply. Any currently
running command will be canceled, even if the new command is invalid.
:param param_list: string list of all the command line parameters,
including the command name which is the first item.
"""
if param_list[0] in self._cmds:
# if new command is valid, schedule it to run immediately
self._cmdobj = self._cmds[param_list[0]]
self._cmdparms = param_list
self._sched = time.ticks_us()
console_writeln("$OK")
else:
# if command is not valid, cancel anything in progress and
# indicate error to console
self._cmdobj = None
console_writeln("$ERR")
def exec(self) -> bool:
"""Execute currently scheduled command.
This method is called repeatedly from the run loop. It checks to see
if any command is scheduled to run, and if so calls the `render()`
method for that command. The return value determines how the command
is rescheduled. The return value is:
* None - do not run again
* int(0) - run again immediately
* int(N) > 0 - run after N ticks have elapsed, in microseconds
The return value is `True` if the command was run, or `False` if not.
**NOTES:** if render() was called, the `True` return will cause the
LED strip to be repainted, even if the frame buffer was not updated.
Some commands do not even affect the LED status. In the future consider
a way for render() itself to indicate if a repaint is needed.
"""
# check for scheduled command
if self._cmdobj:
# get the current time and compare to scheduled time
now = time.ticks_us()
if time.ticks_diff(now, self._sched) >= 0:
# time has elapsed so run the command
delay = self._cmdobj.render(self._cmdparms, self._framebuf)
# process the return from the command ...
if delay is None:
self._cmdobj = None # doesnt need to run again
elif delay == 0:
self._sched = now # run again immediately
else:
# schedule next run time
self._sched = time.ticks_add(now, delay)
return True # need to repaint
# frame buffer was not updated so no need to repaint
return False
def run(self) -> bool:
"""Command line processing and run loop.
This method is called repeatedly from the top level run loop. It
processes incoming data from the command line, calls the parser, and
dispatches commands when a complete command line is received.
It returns whatever `exec()` returns, which is used as a repaint signal
at the top level.
"""
# process any new incoming characters
incoming = console_read()
if incoming:
console_write(incoming) # echo to console
cmdargs = self._cp.process_input(incoming)
# if there is a complete new command line, then setup new command
if cmdargs:
self.setup(cmdargs)
# run existing scheduled command
return self.exec()
|
add_cmd(cmdname: str, cmdobj: CommandTemplate) -> None
Adds a new command of the specified class to the command list.
Parameters:
Name |
Type |
Description |
Default |
cmdname
|
str
|
name of the new command, must be unique from other command names
|
required
|
cmdobj
|
CommandTemplate
|
|
required
|
Source code in ledstrip/cmdif.py
186
187
188
189
190
191
192
193
194 | def add_cmd(self, cmdname: str, cmdobj: CommandTemplate) -> None:
"""Adds a new command of the specified class to the command list.
:param cmdname: name of the new command, must be unique from other
command names
:param cmdobj: a [CommandTemplate][ledstrip.cmdtemplate] subclass
implementing the new command
"""
self._cmds[cmdname] = cmdobj
|
exec() -> bool
Execute currently scheduled command.
This method is called repeatedly from the run loop. It checks to see
if any command is scheduled to run, and if so calls the render()
method for that command. The return value determines how the command
is rescheduled. The return value is:
- None - do not run again
- int(0) - run again immediately
- int(N) > 0 - run after N ticks have elapsed, in microseconds
The return value is True
if the command was run, or False
if not.
NOTES: if render() was called, the True
return will cause the
LED strip to be repainted, even if the frame buffer was not updated.
Some commands do not even affect the LED status. In the future consider
a way for render() itself to indicate if a repaint is needed.
Source code in ledstrip/cmdif.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258 | def exec(self) -> bool:
"""Execute currently scheduled command.
This method is called repeatedly from the run loop. It checks to see
if any command is scheduled to run, and if so calls the `render()`
method for that command. The return value determines how the command
is rescheduled. The return value is:
* None - do not run again
* int(0) - run again immediately
* int(N) > 0 - run after N ticks have elapsed, in microseconds
The return value is `True` if the command was run, or `False` if not.
**NOTES:** if render() was called, the `True` return will cause the
LED strip to be repainted, even if the frame buffer was not updated.
Some commands do not even affect the LED status. In the future consider
a way for render() itself to indicate if a repaint is needed.
"""
# check for scheduled command
if self._cmdobj:
# get the current time and compare to scheduled time
now = time.ticks_us()
if time.ticks_diff(now, self._sched) >= 0:
# time has elapsed so run the command
delay = self._cmdobj.render(self._cmdparms, self._framebuf)
# process the return from the command ...
if delay is None:
self._cmdobj = None # doesnt need to run again
elif delay == 0:
self._sched = now # run again immediately
else:
# schedule next run time
self._sched = time.ticks_add(now, delay)
return True # need to repaint
# frame buffer was not updated so no need to repaint
return False
|
run() -> bool
Command line processing and run loop.
This method is called repeatedly from the top level run loop. It
processes incoming data from the command line, calls the parser, and
dispatches commands when a complete command line is received.
It returns whatever exec()
returns, which is used as a repaint signal
at the top level.
Source code in ledstrip/cmdif.py
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281 | def run(self) -> bool:
"""Command line processing and run loop.
This method is called repeatedly from the top level run loop. It
processes incoming data from the command line, calls the parser, and
dispatches commands when a complete command line is received.
It returns whatever `exec()` returns, which is used as a repaint signal
at the top level.
"""
# process any new incoming characters
incoming = console_read()
if incoming:
console_write(incoming) # echo to console
cmdargs = self._cp.process_input(incoming)
# if there is a complete new command line, then setup new command
if cmdargs:
self.setup(cmdargs)
# run existing scheduled command
return self.exec()
|
setup(param_list: list[str]) -> None
Setup to start running a new command.
This is called by the command loop whenever a complete command line is
received. It uses the input parameter list to determine if the named
command exists. If so it sets up for executing in the exec loop.
It send $OK
or $ERR
to the serial console as a reply. Any currently
running command will be canceled, even if the new command is invalid.
Parameters:
Name |
Type |
Description |
Default |
param_list
|
list[str]
|
string list of all the command line parameters, including the command name which is the first item.
|
required
|
Source code in ledstrip/cmdif.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219 | def setup(self, param_list: list[str]) -> None:
"""Setup to start running a new command.
This is called by the command loop whenever a complete command line is
received. It uses the input parameter list to determine if the named
command exists. If so it sets up for executing in the exec loop.
It send `$OK` or `$ERR` to the serial console as a reply. Any currently
running command will be canceled, even if the new command is invalid.
:param param_list: string list of all the command line parameters,
including the command name which is the first item.
"""
if param_list[0] in self._cmds:
# if new command is valid, schedule it to run immediately
self._cmdobj = self._cmds[param_list[0]]
self._cmdparms = param_list
self._sched = time.ticks_us()
console_writeln("$OK")
else:
# if command is not valid, cancel anything in progress and
# indicate error to console
self._cmdobj = None
console_writeln("$ERR")
|