Gus
Additional Python Modules
mqtt_code
- class growbuddies.mqtt_code.MQTTService(callbacks_dict=None)
GrowBuddies code that subscribes to topics and runs as a systemd service will need to get to the MQTT client through MQTTService(). This is necessary because a systemd service requires a blocking call, such as loop_forever(), in order to run continuously in the background. However, the loop_forever() method prevents the thread from continuing. By using a separate thread for the MQTTClient, the service can run in the background without blocking the main thread.
- Attributes:
callbacks_dict (dict): A dictionary mapping MQTT topics to callback functions. The callbacks_dict is created by calls to the
growbuddies.settings_code.Settings
class.- Methods:
start(): Starts the MQTT client in a separate thread.
stop(): Stops the MQTT client and waits for the thread to terminate.
Note
The default mqtt broker is Gus
- __init__(callbacks_dict=None)
- class growbuddies.mqtt_code.MQTTClient(callbacks_dict=None)
A more optimized way to publish and subscribe to the Gus MQTT broker.
If the calling code logic is subscribing to MQTT topics, then access to the MQTTClient() must go through MQTTService().
If the calling code logic is strictly publishing MQTT messages, then access to publishing mqtt messages can all be done through MQTTClient().
It also provides methods for handling incoming messages and performing cleanup when the client is stopped.
- Attributes:
host (str): The hostname or IP address of the MQTT broker.
callbacks_dict (dict): A dictionary mapping MQTT topics to callback functions.
- __init__(callbacks_dict=None)
- on_message(client, userdata, msg) None
Callback function that is called when the client receives a message from the MQTT broker.
- Args:
msg (str): An MQTT message.
To determine which callback function to call for a specific MQTT message topic, a callbacks_dict is used to map the topic to the appropriate callback function. The callbacks_dict is passed in by the caller and is created using the
growbuddies.settings_code.Settings.get_callbacks()
method. This allows for a generic way to handle different MQTT topics and their corresponding callback functions.The callbacks_dict is searched to find the function that should be executed when the topic of the incoming MQTT message matches a key in the dict.
SnifferBuddyReadings
The snifferbuddy_readings.py module contains the SnifferBuddyReadings class.
- class growbuddies.snifferbuddyreadings_code.SnifferBuddyReadings(mqtt_payload)
An instance of the “SnifferBuddyReadings” class simplifies access to data by processing the MQTT message payload from a SnifferBuddy device and providing properties such as the vpd, as well as a dictionary containing all properties. Using the raw MQTT payload may not be optimal, as it: - Does not include the vpd calculation. - Is specific to the sensor. - Is not as easily accessible as accessing properties directly.
For example, the mqtt air quality message of the SCD30 is:
{"Time":"2022-09-06T08:52:59", "ANALOG":{"A0":542}, "SCD30":{"CarbonDioxide":814,"eCO2":787,"Temperature":71.8,"Humidity":61.6,"DewPoint":57.9},"TempUnit":"F"}
Another air quality sensor may have a different message format that requires interpretation. However, the process of accessing properties, such as sensor readings, remains consistent and can be done in the same way as demonstrated below.
from snifferbuddy_code import SnifferBuddyReadings s = SnifferBuddyReadings(mqtt) print({s.dict}) print({s.vpd})
- Args:
mqtt_payload (str): Sensor model specific mqtt message from a SnifferBuddy. sensor (str, optional):The constant string that identifies the air quality sensor. The sensor must be listed in SnifferBuddySensors(). Defaults to SnifferBuddySensors.SCD30. log_level (logging level constant, optional):Logging level either logging.DEBUG, logging.INFO, logging.ERROR. Defaults to logging.DEBUG.
- __init__(mqtt_payload)
- property co2: float
SnifferBuddy’s reading for the CO2 concentration.
- property dict: dict
Returns SnifferBuddy readings as a dictionary.
return { "temperature": self.temperature, "humidity": self.humidity, "co2": self.co2, "vpd": self.vpd, "light_level": self.light_level, }
_Note: The time is not returned. influxdb adds a timestamp when the data is written to the database._
- property humidity: float
SnifferBuddy’s reading for the Relative Humidity
- property light_level: int
The reading of the photoresistor at the top of SnifferBuddy. The build directions say to use a Pull Down resistor. This means the lower the value of the light level, the higher the light level. The value is a number between 0 and 1023.
- property temperature: float
SnifferBuddy’s temperature reading. Whether it is in F or C is dependent on how you set up SnifferBuddy. See [GrowBuddy’s Tasmota documentation](tasmota_commands) for details.
- Returns:
float: SnifferBuddy’s reading of the air temperature.
- property time: str
A string containing the date and time SnifferBuddy sent the MQTT message. _Note: The time is not returned within the dict property, as noted earlier.
- property vpd: float
A calculation of the Vapor Pressure Deficit (vpd) based on SnifferBuddy’s temperature and humidity readings. _Note: For now, the leaf temperature is assumed to be 2 degrees F less than the air temperature._
Settings
The settings_code.py module efficiently retrieves and validates parameter input from the growbuddy_settings.json file.
- class growbuddies.settings_code.Settings
- __init__()
Set the working directory to where the Python files are located.
- get(key, default=None) str
The get() method is a convenient way to access the values stored in the growbuddies_settings.json file. It serves as a wrapper around the built-in get() method of dictionaries. If a value does not exist for a key, but a default value is provided, the default value is returned. If a value does not exist for a key and no default value is provided, an exception is raised.
- Args:
key (str): One of the keys in the growbuddies_settings.json file.
default (str, optional): A default value to return if the key does not exist. Defaults to None.
- Raises:
Exception: Occurs if the key does not exist and no default value is provided. The exception message contains the key and the value.
- Returns:
str: The value associated with the key.
- get_callbacks(key: str, instance_of_callbacks_class) dict
get_callbacks() returns a dictionary of callback methods to be called by the
growbuddies.mqtt_code.MQTTClient.on_message()
. For example, the growbudies_settings.json file contains several callback dictionaries. Here is the callback dictionary for SnifferBuddy:{ "snifferbuddy_mqtt": { "tele/snifferbuddy/SENSOR": "on_snifferbuddy_readings", "tele/snifferbuddy/LWT": "on_snifferbuddy_status" } }
The dictionary states for the key “snifferBuddy_mqtt”, when a message is received on the topic “tele/snifferbuddy/SENSOR”, the method on_snifferbuddy_readings() is to be called. When a message is received on the topic “tele/snifferbuddy/LWT”, the method on_snifferbuddy_status() is to be called.
Typically, this method is called prior to instantiating an instance of the
growbuddies.mqtt_code.MQTTService
class. For example, the following code instantiates an instance of the MQTTService class and passes the callbacks dictionary to the MQTTService class:settings = Settings() settings.load() callbacks = Callbacks() methods = settings.get_callbacks("snifferbuddy_mqtt", callbacks) mqtt_service = MQTTService(methods) mqtt_service.start()
- Args:
key (str): Key to the method names in the instance_to_callbacks_class instance that are to be called when a message is received on the topic associated with the key. In the above example, the caller has written the two callback methods, :on_snifferbuddy_readings() and on_snifferbuddy_status().
instance_to_callbacks_class (class instance): Instance of the class that contains the callback methods.
- Returns:
dict (dict): A dictionary of callback methods to be called by
growbuddies.mqtt_code.MQTTService
. The dictionary is keyed by the topic and the value is the callback method to be called.
- load() None
Load the growbuddies_settings.json file.
- Raises:
Exception: Returns a string letting the caller know the json file could not be opened.
Useful Raspberry Pi Stuff
Installed Raspberry Pi But Cannot SSH
You’ve verified Gus has an IP address. However, perhaps you accidentally entered the wrong SSID or password for your wifi. Or you forget to enable SSH. You can manually configure these options.
Add “SSH” file to the root of the image. We do this by opening a terminal on the boot partition and typing
$touch ssh
Create the
wpa_supplicant.conf
file :$touch wpa_supplicant.conf
. Copy the contents into the filenano wpa_supplicant.conf
:
country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
ssid="YOURSSID"
psk="YOURPWD"
}
Note: Multiple wifi networks can be set up by following this example:
network={
ssid="SchoolNetworkSSID"
psk="passwordSchool"
id_str="school"
}
network={
ssid="HomeNetworkSSID"
psk="passwordHome"
id_str="home"
}
Changing the ssid and psk to match your network.
Remove the SD-card.
Put the SD-card into the Rasp-Pi’s micro-SD port.
Power up the Rasp Pi. Hopefully wireless is working!
Using Rsync
Rsync is a very useful utility on the Raspberry Pi. I document my use here because I keep forgetting how to use it. Currently, I am on a Windows PC. The challenge is to start a Bash session in the right directory.
open Explorer, go to the directory to use rsync, and type in bash in the text field for the file path.
A wsl window will open at this location.
sudo rsync -avh pi@gus:/home/pi/mydata.zip .
Change Text
From within a directory within multiple files:
find /path -type f -exec sed -i 's/oldstr/newstr/g' {} \;
OSError: [Errno 98] Address already in use
Find the ProcessID
Using the command:
pi@gus:~/gus $
pi 2465 0.2 0.4 25956 17520 pts/0 T 09:31 0:00 /home/pi/gus/py_env/bin/python /home/pi/gus/py_env/bin/sphinx-autobuild docs docs/_build/html
pi 2641 0.0 0.0 7344 508 pts/0 S+ 09:34 0:00 grep --color=auto sphinx-autobuild
The PID we are interested in is 2465.
Kill the Process
Onto the kill command, which needs sudo privileges.
pi@gus:~/gus $ sudo kill -9 2465
[1]+ Killed sphinx-autobuild docs docs/_build/html (wd: ~/gus/docs)
(wd now: ~/gus)
Check which Python is in Use
It’s best practice to run Python code in a virtual environment. To check to see which interpreter is running:
py_env) pi@gus:~/growbuddies $ python
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.executable
'/home/pi/growbuddies/py_env/bin/python'
Check Rasp Pi OS Version stuff
(py_env) pi@gus:~/growbuddies/growbuddies-project $ uname -a
Linux gus 5.10.103-v7l+ #1529 SMP Tue Mar 8 12:24:00 GMT 2022 armv7l GNU/Linux
(py_env) pi@gus:~/growbuddies/growbuddies-project $ cat /etc/debian_version
10.13
(py_env) pi@gus:~/growbuddies/growbuddies-project $ cat /etc/rpi-issue
Raspberry Pi reference 2021-12-02
Note
Debian 10 is Buster. Debian 11 is Bullseye.
Ignore a Page
If you don’t want to include a document in the toctree, add :orphan: to the top of your document to get rid of the warning.
This is a File-wide metadata option. Read more from the Sphinx documentation.
Packaging
There is so much history with Python. Resource: Python packaging.