granola.breakfast_cereal module
This module provides the core of GRANOLA with the Cereal class (affectionately called Breakfast Cereal).
Breakfast Cereal is the core Mock Serial object that handles mocking pyserial’s Serial class. It does
this with a number of Command Readers (Guide)
that each are responsible for processing a number of serial commands in different ways. This allows
very extensible functionality with different types of serial commands, and user specific customization
by defining your own Command Readers.
You can further customize the behavior of Command Readers by adding in Hook
that run a preditermined spots. Also see Configuration Overview.
- class granola.breakfast_cereal.Cereal(command_readers=None, hooks=None, data_path_root=None, unsupported_response='Unsupported\r>', write_terminator='\r', encoding='ascii')
Bases:
SerialMock Pyserial’s
Serialclass that is capable of mocking serial commands a responses to for both simple and sophisticated neeeds. Breakfast Cereal allows you to defined simplecommand_readersCannedQueriesthat are are predefined serial commands and responses for various commands. As well as more complicatedGettersAndSetterswhich allow on the fly changing the state of stored attributes. It also allows customizing the behavior of how individual or any set of commands are processed throughhooks.Mock serials initialization follows a 2 step process. The first is with the normal
__init__method where you pass arguments to Cereal, you can do this before your project is going, as part of the setup. At this point, Cereal will be able to mock but it won’t have many pyserial Serial attributes defined such as port, baudrate. Cereal is then able to be “initialized” a second time, following the signature of pyserial Serial class. This allows you to setup Cereal with its configuration before and then not have to change your code that initializes Pyserial much or at all through means of depencency injection or patching.The CSVs must have the columns cmd and response in them, it can have other columns as well.
- Parameters
command_readers (dict[BaseCommandReaders|str] | list[BaseCommandReaders|str], optional) – Dictionary or list of
Command Readers. Command Readers can be passed in as a string representation, or you may pass in the class itself. Passing in a dictionary allows specifying your initialization options as well. Using a list allows you to pass in already initialized Command Reader, or a not initialized Command Reader that will be initialized to default values. You can pass in custom Command Readers as well. See Custom Command Readers and Hooks Configuration. defaults to(GettersAndSetters(), CannedQueries())hooks (dict[BaseHook|str] | list[BaseHook|str], optional) – Dictionary or list of
Hooks. Hooks can be passed in as a string representation, or you may pass in the class itself. Passing in a dictionary allows specifying your initialization options as well. Using a list allows you to pass in already initialized Hook, or a not initialized Hook that will be initialized to default values. You can pass in custom Hooks as well. See Custom Command Readers and Hooks Configuration. defaults to[]data_path_root (str | Path, optional) –
Path to the where all data file paths (for Command Readers or Custom Hooks) will be referenced from.
Todo
probably just make this a thing inside the needed command readers
unsupported_response (str, optional) – The response returned for any command that does not have a defined response. Defaults to “Unsupportedr>”
encoding (str, optional) – The encoding scheme used to encode the serial commands and responses Defaults to “ascii”
See also
mock_from_json(): Constructor from external configuration fileBaseCommandReaders.get_reading(): Base Command Reader that defines the interface for other Command ReadersConfiguration Overview : Configuration Instructions and
Basic Overview of Mock Cereal and API : Intro Tutorial
Examples
Initialize Breakfast Cereal with CannedQueries and GettersAndSetters Command Readers
>>> command_readers = { ... "CannedQueries": {"data": [{"1\r": "1", "2\r": ["2a", "2b"]}]}, ... "GettersAndSetters": { ... "default_values": {"sn": "42"}, # We first initialize a default ... "getters": [{"cmd": "get sn\r", "response": "{{ sn }}\r>"}], # we define default ... "setters": [{"cmd": "set sn {{ sn }}\r", "response": "OK\r>"}], # and we define a setter ... }, ... } >>> cereal = Cereal(command_readers=command_readers)
CannedQueries returns the correct response and by default it loops when it reaches the end of the defined responses
>>> cereal.write(b"2\r") 2 >>> cereal.read(cereal.in_waiting) b'2a' >>> cereal.write(b"2\r") 2 >>> cereal.read(cereal.in_waiting) b'2b' >>> cereal.write(b"2\r") 2 >>> cereal.read(cereal.in_waiting) b'2a'
Our GettersAndSetters initializes values to the defaults we specified, and then we can set them to new values
>>> cereal.write(b"get sn\r") 7 >>> cereal.read(cereal.in_waiting) b'42\r>' >>> cereal.write(b"set sn our fancy new sn\r") # if we don't read in between commands, it clears the buffer 24 >>> cereal.write(b"get sn\r") 7 >>> cereal.read(cereal.in_waiting) b'our fancy new sn\r>'
If we send a command that is not defined, it sends back an Unsupported response, which is configurable
>>> cereal.write(b"nonesense\r") 10 >>> cereal.read(cereal.in_waiting) b'Unsupported\r>'
We can change the behavior of certain commands by adding
Hooks. TheHookscauses the last defined response to not loop, but to stick on it and just repeat.>>> from granola import StickCannedQueries >>> hooks = {"StickCannedQueries": {"attributes": ["2\r"], "include_or_exclude": "include"}} >>> cereal = Cereal(command_readers=command_readers, hooks=hooks) >>> cereal.write(b"2\r") 2 >>> cereal.read(cereal.in_waiting) b'2a' >>> cereal.write(b"2\r") 2 >>> cereal.read(cereal.in_waiting) b'2b' >>> cereal.write(b"2\r") # You'll notice here that it doesn't loop 2 >>> cereal.read(cereal.in_waiting) # It just repeats the last response (it sticks) b'2b' >>> cereal.write(b"2\r") 2 >>> cereal.read(cereal.in_waiting) b'2b'
- __call__(*args, **kwargs)
Method to initialize pyserial Serial object. Serperating out the initialization of Breakfast Cereal into its own
__init__method and then initializing pyserial in its __call__ method allows you to setup up Breakfast Cereal configuration outside of your code, and then have your code initialize it again to initialize the pyserial part of Breakfast Cereal. That way you can modify your own code as minimally as possible.- Parameters
args – args to pass to pyserial
kwargs – keyword arguments to pass to pyserial
- Raises
TypeError if you use __call__ more than once (and therefore pyserial is already –
initialized). –
- close()
Close port
- property in_waiting
mocking pyserial’s in_waiting
- classmethod mock_from_json(config_key, config_path='config.json', **kwargs)
Load configuration dictionary based on config_key and config_path and return a Breakfast Cereal class.
- If you don’t pass in where config_path is, it will default to “config.json” in the
root directory of your python path.
- open()
Open port with current settings. This may throw a SerialException if the port cannot be opened.
- property out_waiting
mocking pyserial’s out_waiting
- read(size=1)
Mock
serial.Serial.read(). Return number of bytes in self._next_read based on size
- reset_input_buffer()
A wrapper for serial.reset_input_buffer that also clears the current read buffer. Should only be used with pyserial versions >= 3.0
- reset_output_buffer()
A wrapper for serial.reset_input_buffer that also clears the current write buffer. Should only be used with pyserial versions >= 3.0
- write(data)
Mock
serial.Serial.write()by seeding a serial command generator- Parameters
data (byte_str) – serial command