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: Serial

Mock Pyserial’s Serial class that is capable of mocking serial commands a responses to for both simple and sophisticated neeeds. Breakfast Cereal allows you to defined simple command_readersCannedQueries that are are predefined serial commands and responses for various commands. As well as more complicated GettersAndSetters which 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 through hooks.

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 file

BaseCommandReaders.get_reading() : Base Command Reader that defines the interface for other Command Readers

Configuration 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. The Hooks causes 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