Accessing A sensor
In this tutorial we will write a module that accesses an I²C sensor to get temperature data. We will use the Adafruit sht40 sensor.
Important
- Please ensure you have read the simple tutorial before this one as the basics covered in the simple tutorial will not be covered
- This tutorial requires additional hardware and basic understanding of the Pi's GPIO pins
- Please ensure the Allsky Environment module is installed as this will install the dependencies for this example module
Connect the SHT40 to the PI as shown in the following diagram, the pin connections are the same for the pi3, 4 and 5
The SHT40 wiring
Lets start with the basic framework for the module, this does nothing other than allow you to add the module to the periodic pipeline
import allsky_shared as allsky_shared
from allsky_base import ALLSKYMODULEBASE
class ALLSKYEXAMPLE2(ALLSKYMODULEBASE):
meta_data = {
"name": "Allsky Example I²C Module",
"description": "A simple example module to read an sht40 sensor.",
"module": "allsky_example1",
"version": "v1.0.0",
"group": "Data Capture",
"events": [
"periodic"
],
"enabled": "false",
"experimental": "true",
"testable": "true",
"centersettings": "false",
"extradatafilename": "allsky_example2.json",
"extradata": {
},
"arguments":{
},
"argumentdetails": {
},
"businfo": [
],
"changelog": {
"v1.0.0" : [
{
"author": "Alex Greenland",
"authorurl": "https://github.com/allskyteam",
"changes": "Initial Release"
}
]
}
}
def run(self) -> str:
result = ""
extra_data = {}
self.log(4, result)
return result
def example2(params, event) -> str:
allsky_example2 = ALLSKYEXAMPLE2(params, event)
result = allsky_example2.run()
return result
def example2_cleanup():
moduleData = {
"metaData": ALLSKYEXAMPLE2.meta_data,
"cleanup": {
"files": {
ALLSKYEXAMPLE2.meta_data['extradatafilename']
},
"env": {}
}
}
allsky_shared.cleanupModule(moduleData)
The next thing we need to do is to add any settings we need for the sensor. We will only require one settings that allows the I²C address to be changed if needed. Noramlly you would not need to do this but adding the setting will show how the I²C device can be selected from a list.
Modify the meta data to include the I²C address field
meta_data = {
"name": "Allsky Example I²C Module",
"description": "A simple example module to read an sht40 sensor.",
"module": "allsky_example1",
"version": "v1.0.0",
"group": "Data Capture",
"events": [
"periodic"
],
"enabled": "false",
"experimental": "true",
"testable": "true",
"centersettings": "false",
"extradatafilename": "allsky_example2.json",
"extradata": {
},
"arguments":{
"I²Caddress": ""
},
"argumentdetails": {
"I²Caddress": {
"required": "false",
"description": "I²C Address",
"help": "Override the standard I²C address for a device. NOTE: This value must be hex, i.e., 0x76.",
"type": {
"fieldtype": "I²C"
}
}
},
"businfo": [
],
"changelog": {
"v1.0.0" : [
{
"author": "Alex Greenland",
"authorurl": "https://github.com/allskyteam",
"changes": "Initial Release"
}
]
}
}
Next we need to add the code to read the sensor, see the notes at the top of this page regardin installing the Environment module to get the correct dependencies.
Add the following imports at the top of the module
from typing import Optional, Dict, Tuple, Any
import board
import busio
import adafruit_sht4x
Next add a method to read the sensor
def _read_sht40(self, address: int = 0x44) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
try:
if not isinstance(address, int) or not (0x03 <= address <= 0x77):
return None, f"Invalid I²C address: {address}"
try:
I²C = busio.I²C(board.SCL, board.SDA)
except Exception as e:
return None, f"Failed to initialise I²C bus: {e}"
try:
sensor = adafruit_sht4x.SHT4x(I²C, address=address)
except Exception as e:
return None, f"SHT40 not found at I²C address 0x{address:02X}: {e}"
try:
sensor.mode = adafruit_sht4x.Mode.NOHEAT_HIGHPRECISION
except Exception:
pass
try:
temperature, humidity = sensor.measurements
except Exception as e:
return None, f"Sensor read failed: {e}"
if temperature is None or humidity is None:
return None, "Sensor returned empty values"
return {
"temperature_c": round(float(temperature), 2),
"temperature_f": round(float(temperature) * 9 / 5 + 32, 2),
"humidity": round(float(humidity), 2),
"serial_number": getattr(sensor, "serial_number", None),
"address": f"0x{address:02X}"
}, None
except Exception as e:
return None, f"Unexpected error: {e}"
Now modify the run function to use it
def run(self) -> str:
result = ""
extra_data = {}
I²C_address = self.get_param("I²Caddress", 0x44, int)
data, error = self._read_sht40(I²C_address)
if error:
result = f"ERROR: SHT40 error: {error}"
else:
extra_data["AS_EXAMPLE2_TEMP"] = data["temperature_c"]
extra_data["AS_EXAMPLE2_HUMIDITY"] = data["humidity"]
result = f"INFO: sht40 read temp_c={data['temperature_c']}, temp_f={data['temperature_f']}, humidity={data['humidity']}, serial={data['serial_number']}, address={data['address']}"
self.log(4, result)
return result
And finally add the extra data config to the meta_data
meta_data = {
"name": "Allsky Example I²C Module",
"description": "A simple example module to read an sht40 sensor.",
"module": "allsky_example1",
"version": "v1.0.0",
"group": "Data Capture",
"events": [
"periodic"
],
"enabled": "false",
"experimental": "true",
"testable": "true",
"centersettings": "false",
"extradatafilename": "allsky_example2.json",
"extradata": {
"values": {
"AS_EXAMPLE2_TEMP": {
"name": "${EXAMPLE2_TEMP}",
"format": "",
"sample": "",
"group": "User",
"description": "Temperature from the sht40",
"type": "temperature"
},
"AS_EXAMPLE2_HUMIDITY": {
"name": "${EXAMPLE2_HUMIDITY}",
"format": "",
"sample": "",
"group": "User",
"description": "Pressure from the sht40",
"type": "number"
}
}
},
"arguments":{
"I²Caddress": ""
},
"argumentdetails": {
"I²Caddress": {
"required": "false",
"description": "I²C Address",
"help": "Override the standard I²C address for a device. NOTE: This value must be hex, i.e., 0x76.",
"type": {
"fieldtype": "I²C"
}
}
},
"businfo": [
],
"changelog": {
"v1.0.0" : [
{
"author": "Alex Greenland",
"authorurl": "https://github.com/allskyteam",
"changes": "Initial Release"
}
]
}
}
And thats it a fully functioning module to read the temperature and pressure from an SHT40
