Skip to content

Commit

Permalink
Merge pull request #32 from tum-esm/refactor-edge-controller
Browse files Browse the repository at this point in the history
feat(controller): Send calibration correction data along with measured raw values + send config on startup
  • Loading branch information
larsfroelich authored Feb 7, 2025
2 parents 8686eb2 + 85ec9c4 commit e44f97c
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 20 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/test-edge-node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ on:
branches:
- main
paths:
- "software/**"
- "software/controller/**"
- ".github/workflows/test-edge-node.yaml"
pull_request:
branches:
- main
paths:
- "software/**"
- "software/controller/**"
- ".github/workflows/test-edge-node.yaml"

jobs:
test-edge-node-codebase:
runs-on: ubuntu-latest
defaults:
run:
working-directory: software
working-directory: software/controller
steps:
# check-out repo and install python 3.12.4
- name: Check out repository
Expand All @@ -34,8 +34,8 @@ jobs:
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: software/.venv
key: venv-${{ runner.os }}-3.12.4-${{ hashFiles('software/poetry.lock') }}
path: software/controller/.venv
key: venv-${{ runner.os }}-3.12.4-${{ hashFiles('software/controller/poetry.lock') }}

# install poetry if venv not in cache
- name: Install Poetry
Expand Down
7 changes: 7 additions & 0 deletions software/controller/src/custom_types/mqtt_playload_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ class MQTTCO2CalibrationData():
cal_sht45_humidity: Optional[float]


@dataclasses.dataclass
class MQTTCalibrationCorrectionData():
cal_gmp343_slope: float
cal_gmp343_intercept: float
cal_sht_45_offset: float


@dataclasses.dataclass
class MQTTSystemData():
enclosure_bme280_temperature: Optional[float]
Expand Down
2 changes: 1 addition & 1 deletion software/controller/src/custom_types/state_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ class State(pydantic.BaseModel):
last_calibration_attempt: Optional[float]
next_calibration_cylinder: int = pydantic.Field(..., ge=0, le=3)
sht45_humidity_offset: float
co2_sensor_offset: float
co2_sensor_intercept: float
co2_sensor_slope: float
25 changes: 18 additions & 7 deletions software/controller/src/hardware/modules/co2_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, config: config_types.Config, co2_sensor: VaisalaGMP343,
# read slope and offset from state file
state = state_interface.StateInterface.read(config=self.config)
self.slope = state.co2_sensor_slope
self.offset = state.co2_sensor_offset
self.intercept = state.co2_sensor_intercept

# hardware
self.co2_sensor = co2_sensor
Expand Down Expand Up @@ -70,7 +70,7 @@ def perform_edge_correction(
)

# Apply calibration slope and offset
co2_corrected = self.slope * co2_dry + self.offset
co2_corrected = self.slope * co2_dry + self.intercept

return round(co2_dry, 1), round(co2_corrected, 1)

Expand Down Expand Up @@ -104,7 +104,7 @@ def log_cylinder_median(self, bottle_id: int, median: float) -> None:
f"Calculated CO2 calibration bottle {bottle_id} median: {median}",
forward=True)

def calculate_offset_slope(self) -> None:
def calculate_intercept_slope(self) -> None:
# read true CO2 value for calibration gas bottle
true_values = []
measured_values = []
Expand All @@ -127,20 +127,20 @@ def calculate_offset_slope(self) -> None:
if true_values[1] > true_values[0]:
self.slope = (true_values[1] - true_values[0]) / (
measured_values[1] - measured_values[0])
self.offset = true_values[0] - self.slope * measured_values[0]
self.intercept = true_values[0] - self.slope * measured_values[0]
else:
self.slope = (true_values[0] - true_values[1]) / (
measured_values[0] - measured_values[1])
self.offset = true_values[1] - self.slope * measured_values[1]
self.intercept = true_values[1] - self.slope * measured_values[1]

# persist slope and offset to state file
state = state_interface.StateInterface.read(config=self.config)
state.co2_sensor_slope = self.slope
state.co2_sensor_offset = self.offset
state.co2_sensor_intercept = self.intercept
state_interface.StateInterface.write(state)

self.logger.info(
f"Calculated CO2 calibration slope: {self.slope} and offset: {self.offset}",
f"Calculated CO2 calibration slope: {self.slope} and intercept: {self.intercept}",
forward=True)

def send_CO2_measurement_data(
Expand Down Expand Up @@ -187,3 +187,14 @@ def send_CO2_calibration_data(self,
cal_gmp343_temperature=CO2_sensor_data.temperature,
),
)

def send_calibration_correction_data(self) -> None:

# send out MQTT measurement message
self.message_queue.enqueue_message(
timestamp=int(time.time()),
payload=mqtt_playload_types.MQTTCalibrationCorrectionData(
cal_gmp343_slope=self.slope,
cal_gmp343_intercept=self.intercept,
cal_sht_45_offset=self.inlet_sht45.humidity_offset),
)
2 changes: 1 addition & 1 deletion software/controller/src/interfaces/state_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def read(config: config_types.Config) -> state_types.State:
last_calibration_attempt=None,
next_calibration_cylinder=0,
sht45_humidity_offset=0.0,
co2_sensor_offset=0.0,
co2_sensor_intercept=0.0,
co2_sensor_slope=1.0,
)
StateInterface.write(new_empty_state)
Expand Down
2 changes: 2 additions & 0 deletions software/controller/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def run() -> None:
f"Local time is: {datetime.now().astimezone(pytz.timezone(config.local_time_zone))}",
forward=True)

logger.info(f"Started with config: {config.dict()}", forward=True)

# -------------------------------------------------------------------------

# check and provide valid state file
Expand Down
6 changes: 5 additions & 1 deletion software/controller/src/procedures/calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,13 @@ def run(self) -> None:
self.calibrate_sht45_zero_point()

if self.config.active_components.perform_co2_calibration_correction:
self.hardware_interface.co2_measurement_module.calculate_offset_slope(
self.hardware_interface.co2_measurement_module.calculate_intercept_slope(
)

# send active calibration correction values
self.hardware_interface.co2_measurement_module.send_calibration_correction_data(
)

# switch back to measurement inlet
self.hardware_interface.valves.set(
number=self.config.measurement.valve_number)
Expand Down
9 changes: 4 additions & 5 deletions software/controller/src/utils/message_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
from custom_types import mqtt_playload_types

PROJECT_DIR = dirname(dirname(os.path.abspath(__file__)))
ACROPOLIS_DATA_PATH = os.environ.get(
"ACROPOLIS_DATA_PATH") or os.path.join(
dirname(PROJECT_DIR), "data")
ACROPOLIS_DATA_PATH = os.environ.get("ACROPOLIS_DATA_PATH") or os.path.join(
dirname(PROJECT_DIR), "data")

THINGSBOARD_PAYLOADS = Union[mqtt_playload_types.MQTTCO2Data,
mqtt_playload_types.MQTTCO2CalibrationData,
mqtt_playload_types.MQTTCalibrationCorrectionData,
mqtt_playload_types.MQTTSystemData,
mqtt_playload_types.MQTTWindData,
mqtt_playload_types.MQTTWindSensorInfo,
Expand All @@ -23,8 +23,7 @@ class MessageQueue:
"""Uses an SQLite database to store messages to be forwarded to the ThingsBoard server by the gateway"""

def __init__(self) -> None:
db_path = os.path.join(ACROPOLIS_DATA_PATH,
"acropolis_comm_db.db")
db_path = os.path.join(ACROPOLIS_DATA_PATH, "acropolis_comm_db.db")

self.con = sqlite3.connect(db_path,
isolation_level=None,
Expand Down

0 comments on commit e44f97c

Please sign in to comment.