5. Developing fox CLI
The fox CLI consists of three parts:
cli/*: the actualfox CLIimplementationa two layer wrapper:
fox.ps1/fox.sh: a wrapper to support different shellsfox.py: a Python wrapper as entrance point when thefox CLIis used in the repository (called by the shell wrappers).
The fox CLI package only contains part of the fox CLI implementation and
provides fox-cli as an entry point, which can be used similarly to the
fox CLI wrappers.
5.4. fox CLI Fundamentals
The foxBMS 2 repository is large and offers a lot of different things to do.
These things shall be explorable and usable.
One thing to enable this is documentation, but a simple interface directly
embedded into the repository simplifies this furthermore and enables a good
interaction with the project.
That interface is the fox CLI.
The fox CLI shall e.g., enable
building embedded binaries
updating the BMS software
analyzing and plotting BMS data
and many more things. All this
shall work in a terminal,
shall be accessible in a GUI,
shall be cross platform (Windows and Linux),
shall not rely on environment variables,
shall not require any setup of a terminal.
Getting all these different requirements to work, required some implementations that at first glance are not immediately clear and some of the reasoning shall be explained in the following, so that - in combination with the source code - the implementation is understandable and comprehensible.
This is a lose list, that makes most sense to read and understand when also
looking at the fox CLI implementation.
5.4.1. Standard Streams
PowerShell and Bash handle standard streams differently.
Depending on the specific command behavior, fox.ps1 handles stdin
differently to Bash (i.e., this PowerShell and Windows only).
This affects the base commands bms and install.
5.4.2. GUI
Note
The GUI is currently not supported on Linux.
The GUI can be started through fox.ps1 and fox.sh.
On Windows, there is pythonw to start Python GUI applications.
pythonw does not support standard streams and this behavior needs to be
reflected in some implementations, e.g., the GUI.
The standard way of using the GUI shall be to not block the terminal.
For that reason the default is to start the GUI using pythonw, as this just
starts Python and exits.
However, when debugging, the standard streams must be available.
The correct interpreter is determined based on the provided options
after the base command gui
(these are -h, --help and --debug-gui) by the terminal wrappers.
Some commands that can be run in the GUI require stdout/stderr to be
available, which of course would contradict the usage of pythonw.
To overcome this issue for such commands, the GUI uses subprocesses and
redirects stdout/stderr to file streams that are then read back to the
GUI output.
To make this consistently work, all stdout/stderr output must be
written through echo, recho, and secho provided by
cli/helpers/click_helpers.py.
Only cli/helpers/python_setup.py shall use print.
There is a wrapper provided for starting the GUI on Windows (gui.lnk),
which is a shortcut to run fox.ps1 gui.
5.4.3. Dependencies
In case Python or the Python development environment are not available, the shell wrappers should guide the user on what to do, in order to get a working setup.
This includes:
Check whether the activate script for the virtual environment exists
If so, activate it, ensure that it worked, and run the provided command
If not, check which part is missing
Generally Python
Specifically Python 3.12
Pinned Python 3.12 development environment
This check is always run when using the wrappers
fox.ps1andfox.sh. When only the pinned Python development environment is missing, the user can directly install it through the wrapper in an interactive mode. On Windows, this check and interactive installation is also implemented for the GUI, i.e., when runninggui.lnk, but if the aforementioned dependencies are missing graphical error messages or installation confirmations are provided.
For a deeper understanding see fox.ps1, fox.sh, and
cli/helpers/python_setup.py, as well as
Fig. 5.16.
5.4.4. fox CLI package
This sections only covers information for developers. More general information on the package can be found in the following sections:
5.4.4.1. Configuration
The fox CLI package is built using
hatchling.
All configuration is managed via the pyproject.toml and hatch_build.py
files.
Some files required to run commands are not part of the fox CLI.
These files are included in the package_data directory.
The file cli/foxbms_version.py typically reads the wscript located at
the repository root to determine the foxBMS 2 version.
However, since hatchling requires a strictly numerical version number, which
the foxBMS 2 version does not always provide, the file version.py is
created at the start of the package build.
This file only contains the numerical version number.
When using the fox CLI package, foxbms_version.py reads the version
information from this generated version.py file.
5.4.4.2. Package Structure
The fox CLI package is structured as shown in Table 5.66 and
the files shown in Table 5.67 are essential for the
fox CLI to function properly within the package.
Directory Name |
Long Name |
Content Description |
|---|---|---|
|
Command_* |
Actual implementation of the specific CLI command |
|
Communication |
Actual implementation of the communication functionalities |
|
Commands |
Implementation of the command line interface of each tool |
|
Database |
Implementation for |
|
Helpers |
Helper functions that are used by several parts of the CLI tool |
|
Project Data |
Files that are necessary for |
File Name |
Content Description |
|---|---|
|
Registers all commands. |
|
Reads the foxBMS 2 version information from the |
|
Contains the numeric version of the foxBMS 2. |
|
Defines helper functions and constants for the |
5.4.4.3. How to use the fox CLI package?
The fox CLI package can be used both from the command line, with the command
fox-cli, and directly in Python code, where the import-name is fox_cli.
An example for using the fox CLI package in Python code is given in
Listing 5.12, where the class ComInterface is used.
from fox_cli.com.com_interface import ComInterface, ProcessInterface
class NewCom(ComInterface):
"""New communication interface using 'ComInterface' from the package."""
def __init__(self, name) -> None:
super().__init__(name)
# ...