Advanced Getters and Setters

from granola.utils import check_min_package_version

Convenience function for pre pyserial 3 and current pyserial compatibility

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. Getters and Setters Configuration Overview

A Getters and Setters configuration set up by passing into command_readers a nested dictionary of Getters and Setters configuration options. This nested dictionaries key should either be the string “GettersAndSetters” or the class GettersAndSetters.

They are configured with a number of attributes with their states being saved for you to set or retrieve from. There attributes must first be defined with "default_values". This would look like this in your nested dictionary:

"default_values": {"sn": "42", "temp": "20.0"}

You can call the attributes whatever you want. They don’t have to be related to the commands they are called in. It is just for nice bookkeeping.

You can then define getters or setters to modify those attributes. Getters and setters are both defined with a "cmd" and "response" key. The getter key needs to be a static string, but the response can include jinja2 formatting, see 2. Adding Advanced Jinja2 Formatting.

from granola import GettersAndSetters

command_readers = {
    "GettersAndSetters": {
        "default_values": {"sn": "42"},
        "getters": [{"cmd": "get -sn\r", "response": "{{ sn }}\r>"}],
        "setters": [{"cmd": "set -sn {{ sn }}\r", "response": "OK\r>"}],
    }
}
command_readers = {
    GettersAndSetters: {
        "default_values": {"sn": "42"},
        "getters": [{"cmd": "get -sn\r", "response": "{{ sn }}\r>"}],
        "setters": [{"cmd": "set -sn {{ sn }}\r", "response": "OK\r>"}],
    }
}

2. Adding Advanced Jinja2 Formatting

For full configurability with getters and setters, we use jinja2 for our formatting. This allows you to not only specify which attribute to return or set, but also to include equations in your responses (setter commands that use jinja2 formatting currently are just to specify what attribute to use and can’t use anything like an equation. Equations are limited to getter and setter responses)

from granola import Cereal

command_readers = {
    "GettersAndSetters": {
        "default_values": {"devtype": "Cereal", "sn": "42", "fw_ver": "0.0.0", "temp": "20.0", "volts": "2000.0"},
        "getters": [
            {"cmd": "get sn\r", "response": "sn: {{ sn }}\r>"},
            {"cmd": "get ver\r", "response": "{{ fw_ver }}\r>"},
            {"cmd": "show\r", "response": "{{ devtype }} - {{ fw_ver }} - {{ sn }}\r>"},
            {"cmd": "get temp\r", "response": "{{ temp }}\r>"},
            # jinja attributes are substituted as strings, so to do math operations on them, we convert them first
            {"cmd": "get tempf\r", "response": "{{ temp|float * (9/5) + 32 }}\r>"},
            {"cmd": "get volts\r", "response": "{{ volts }}"},
            #
            {"cmd": "get volt_temp\r", "response": "{{ (temp|float + volts|float) / 2 }}"},
        ],
        "setters": [
            {"cmd": "set sn {{sn}}\r", "response": "OK\r>"},
            {"cmd": "set both {{ sn }} {{ fw_ver }}\r", "response": "OK\r>"},
            {"cmd": "set temp {{ temp }}\r", "response": "temp: {{ temp }}"},
            {"cmd": "set volts {{ volts }}\r", "response": "Volt Calculation {{ volts|float / 2 }}"},
        ],
    }
}
cereal = Cereal(command_readers)

Below we can see an example of two queries that return the temp attribute. The first ust returns it directly, while the second converts the string to a float, multiplies it by 9/5 and adds 32, converting it from celsius to fahrenheit.

cereal.write(b"get temp\r")
read_all(cereal)
cereal.write(b"get tempf\r")
read_all(cereal)
b'20.0\r>'
b'68.0\r>'

If we set the temperature, we get back a response of the temp: followed by the temperature we set.

And if we issue the two commands we did above, they work with the new temperature

cereal.write(b"set temp 100\r")
read_all(cereal)
cereal.write(b"get temp\r")
read_all(cereal)
cereal.write(b"get tempf\r")
read_all(cereal)
b'temp: 100'
b'100\r>'
b'212.0\r>'

Our getters and setters can also do calculations involving multiple attributes. get volt_temp does calculations on both volts and temp

We also se more examples of using calculations with set volts which returns a "Volt Calculation" which is just half of what you put in.

cereal.write(b"get volts\r")
read_all(cereal)
cereal.write(b"get volt_temp\r")
read_all(cereal)
cereal.write(b"set volts 100\r")
read_all(cereal)
cereal.write(b"get volts\r")
read_all(cereal)
cereal.write(b"get volt_temp\r")
read_all(cereal)
b'2000.0'
b'1050.0'
b'Volt Calculation 50.0'
b'100'
b'100.0'

Commands can also get multiple attributes and set multiple attributes at once, as in the example below.

Also notice above in the config that devtype only has a getter associated with it, and no setters. You are allowed to define read only properties.

cereal.write(b"show\r")
read_all(cereal)
cereal.write(b"set both abc 1.2.3\r")
read_all(cereal)
cereal.write(b"show\r")
read_all(cereal)
b'Cereal - 0.0.0 - 42\r>'
b'OK\r>'
b'Cereal - 1.2.3 - abc\r>'