Basic Overview of Mock Cereal and API
This tutorial will go over the basics of initializing and using the GRANOLA mock Cereal class (also known as Breakfast Cereal)
from granola.utils import check_min_package_version
def read_all(cereal):
"""convenience function since pyserial pre 3.0 doesn't have read_all"""
if check_min_package_version("pyserial", "3.0"):
print(repr(cereal.read_all()))
else:
print(repr(cereal.read(cereal.in_waiting)))
1. Overview of Breakfast Cereal Configuration
Mock Cereal takes a number Command Readers, Hooks (optional), and additional tweaking parameters.
a) Command Readers
Command Readers are the backbone of mock Cereal. Each Command Reader receives a serial command and then returns a response. Any given Command Reader is free to process that command in any way that is so chooses.
Canned Queries is a type of Command Reader that takes a command and loops through a list of responses for that command. It can loop through those responses in order or randomly. Canned Queries also by default loop back to the beginning of their list of responses when they reach the end.
Getters and Setters is a type of Command Reader that manages the state of a number of attributes which you can access with getters and set with setters.
b) Hooks
Hooks allow the modification of the processing of all or a subset of serial commands. Some examples are:
LoopCannedQueriesis a Hook that will loop Canned Queries responses when it has exhausted all responses. This hook is enabled by default on all Canned Queries.StickCannedQueriesis a Hook that will take specified Canned Queries commands, and once the command has exhausted all of its responses, all future queries of that command will only return the last response. A practical example of this might be a serial command of some measurement that is approaching a value, such as a temperature reading.ApproachHookis a Hook that works with Getters and Setters commands, and allows you to set a new value for a command, but instead of the value being instantly set, it is set over time, allowing you slowly approach that value. This can be practical for a simulating a device that is controlling the temperature of some chamber, but when you set it, it takes a while to warm up.
c) Other Parameters
To see a full list of configuration parameters, see Breakfast Cereal
2. Initializing Breakfast Cereal
from granola import Cereal
command_readers = {
# We will first set up queries that will just execute as written, allowing multiple responses per command.
# By default when a particular command reaches the end of its responses, it will loop back to the beginning,
# but this behavior can be changed on individual queries.
"CannedQueries": {
"data": [
{
"1\r": "1", # a command that only has a single response `1` defined
"2\r": {"response": "2"}, # equivalent to the above
"3\r": {"response": ["3a", "3b"]}, # a command that has 2 responses defined, `3a` and `3b`
"4\r": ["4a", "4b"], # equivalent to the above
}
],
},
# We will also define a getter and setter style query. This is a query that you can retrieve a value by
# calling the getter, but you can also set a new value by calling the setter.
"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)
3. Using Breakfast Cereal
Canned Queries will first find the command you entered, and the iterate through the responses defined for that command until they reach the end for that command. By default, they then loop back to the beginning of that sequence
cereal.write(b"3\r")
read_all(cereal)
cereal.write(b"3\r")
read_all(cereal)
cereal.write(b"3\r")
read_all(cereal)
b'3a'
b'3b'
b'3a'
Getters And Setters use jinja2 formatting to specify the location of where the setter attribute is in the setter command, and where it is in the getter response.
cereal.write(b"get sn\r")
read_all(cereal)
cereal.write(b"set sn something something\r")
read_all(cereal)
cereal.write(b"get sn\r")
read_all(cereal)
b'42\r>'
b'OK\r>'
b'something something\r>'
If we pass in an undefined command, then we get back an unsupported response.
cereal.write(b"fake command\r")
read_all(cereal)
b'Unsupported\r>'
We can configure what our unsupported response is in our initial initialization
cereal = Cereal(unsupported_response="ERROR!!!")
cereal.write(b"fake command\r")
read_all(cereal)
b'ERROR!!!'
Because in this case, we didn’t define any Command Readers, every command will be unsupported
cereal.write(b"get sn\r")
read_all(cereal)
b'ERROR!!!'