Merge pull request 'Add control loop state' (#7) from feature/control-status into main

Reviewed-on: #7
This commit is contained in:
Hendrik Schutter 2024-12-21 22:12:44 +01:00
commit 859611f991
6 changed files with 130 additions and 25 deletions

View File

@ -40,6 +40,7 @@ Sntp <|-- Metrics
class Control{ class Control{
+taskControl() +taskControl()
-timetable -timetable
+getControlState()
} }
class Safety{ class Safety{
@ -71,12 +72,91 @@ Sntp <|-- Metrics
} }
``` ```
### Hardware ### Prometheus Metrics
`curl http://X.X.X.X:9100/metrics`
#### Example
```
burner_fault_pending 1
circulation_pump_enabled 0
burner_enabled 1
safety_contact_enabled 1
chamber_temperature 21.812500
chamber_temperature_avg10 21.837500
chamber_temperature_avg60 21.825521
inlet_flow_temperature 22.437500
inlet_flow_temperature_avg10 22.437500
inlet_flow_temperature_avg60 22.434896
outdoor_temperature 21.937500
outdoor_temperature_avg10 21.937500
outdoor_temperature_avg60 21.933594
return_flow_temperature 22.375000
return_flow_temperature_avg10 22.375000
return_flow_temperature_avg60 22.375000
chamber_temperature_state 0
outdoor_temperature_state 0
inlet_flow_temperature_state 0
return_flow_temperature_state 0
safety_state 0
control_state 5
sntp_state 0
system_unixtime 1734814285
uptime_seconds 90
wifi_rssi -63
```
#### Status Encoding
##### Digital Themperatur Sensor DS10B20
- chamber_temperature_state
- outdoor_temperature_state
- inlet_flow_temperature_state
- return_flow_temperature_state
| Enum eSensorErrorState in [safety.h](main/safety.h) | Value | Description |
|-----------------------------------------------------|-------|---------------------|
| SENSOR_NO_ERROR | 0 | |
| SENSOR_TOO_HIGH | 1 | Sanity check failed |
| SENSOR_TOO_LOW | 2 | Sanity check failed |
| SENSOR_UNCHANGED | 3 | Sanity check failed |
| SENSOR_NOT_FOUND | 4 | No Communication |
##### Safety Loop
- safety_state
| Enum eSafetyState in [safety.h](main/safety.h) | Value | Description |
|------------------------------------------------|-------|----------------------------|
| SAFETY_NO_ERROR | 0 | |
| SAFETY_SENSOR_ERROR | 1 | At least one sensor failed |
| SAFETY_INTERNAL_ERROR | 2 | Internal error |
##### Control Loop
- control_state
| Enum eControlState in [control.h](main/control.h) | Value | Description |
|---------------------------------------------------|-------|-----------------------|
| CONTROL_STARTING | 0 | |
| CONTROL_HEATING | 1 | Burner running |
| CONTROL_OUTDOOR_TOO_WARM | 2 | Heating not needed |
| CONTROL_RETURN_FLOW_TOO_WARM | 3 | Heating not needed |
| CONTROL_BURNER_FAULT | 4 | Burner reported fault |
| CONTROL_FAULT | 5 | Unable to control |
##### SNTP Client
- sntp_state
| Enum eSntpState in [sntp.h](main/sntp.h) | Value | Description |
|------------------------------------------|-------|-------------|
| SYNC_SUCCESSFUL | 0 | |
| SYNC_NOT_STARTED | 1 | |
| SYNC_FAILED | 2 | |
## Hardware
| Function | ESP32 | PLC ES32C14 |
|---------------------------------------|-------|-------------------|
| Output CirculationPump | IO27 | Relay 1 NC1 |
| Output Burner Fire Signal | IO14 | Relay 2 NC2 |
| Output Safety Contact (powers Burner) | IO12 | Relay 3 NC2 |
| Input Burner Fault | IO19 | Digital Input IN1 |
| Input Temperature DS10B20 | IO04 | 1-Wire |
| Function | ESP32 | PLC ES32C14 |
|---------------------------------------|-------|---------------------------|
| Output CirculationPump | IO27 | Relay 1 NC1 |
| Output Burner Fire Signal | IO14 | Relay 2 NC2 |
| Output Safety Contact (powers Burner) | IO12 | Relay 3 NC2 |
| Input Burner Fault | IO19 | Digital Input IN1 |
| Input Temperature DS10B20 | IO04 | 1-Wire |

View File

@ -10,7 +10,7 @@
#define PERIODIC_INTERVAL 1U // run safety checks every 1sec #define PERIODIC_INTERVAL 1U // run safety checks every 1sec
static const char *TAG = "smart-oil-heater-control-system-control"; static const char *TAG = "smart-oil-heater-control-system-control";
static eControlState sControlState = CONTROL_STARTING;
void taskControl(void *pvParameters); void taskControl(void *pvParameters);
void initControl(void) void initControl(void)
@ -44,12 +44,14 @@ void taskControl(void *pvParameters)
if (getSafetyState() != SAFETY_NO_ERROR) if (getSafetyState() != SAFETY_NO_ERROR)
{ {
ESP_LOGW(TAG, "Control not possible due to safety fault!"); ESP_LOGW(TAG, "Control not possible due to safety fault!");
sControlState = CONTROL_FAULT;
continue; continue;
} }
if (getSntpState() != SYNC_SUCCESSFUL) if (getSntpState() != SYNC_SUCCESSFUL)
{ {
ESP_LOGW(TAG, "Control not possible due to sntp fault!"); ESP_LOGW(TAG, "Control not possible due to sntp fault!");
sControlState = CONTROL_FAULT;
continue; continue;
} }
@ -59,3 +61,8 @@ void taskControl(void *pvParameters)
setSafetyControlState(ENABLED); setSafetyControlState(ENABLED);
} }
} }
eControlState getControlState(void)
{
return sControlState;
}

View File

@ -1,3 +1,14 @@
#pragma once #pragma once
typedef enum _ControlState
{
CONTROL_STARTING,
CONTROL_HEATING,
CONTROL_OUTDOOR_TOO_WARM,
CONTROL_RETURN_FLOW_TOO_WARM,
CONTROL_BURNER_FAULT,
CONTROL_FAULT,
} eControlState;
void initControl(void); void initControl(void);
eControlState getControlState(void);

View File

@ -12,6 +12,7 @@
#include "inputs.h" #include "inputs.h"
#include "safety.h" #include "safety.h"
#include "sntp.h" #include "sntp.h"
#include "control.h"
static const char *TAG = "smart-oil-heater-control-system-metrics"; static const char *TAG = "smart-oil-heater-control-system-metrics";
@ -181,15 +182,15 @@ void taskMetrics(void *pvParameters)
aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().average60s.fValue; aMetrics[u16MetricCounter].fMetricValue = getReturnFlowTemperature().average60s.fValue;
u16MetricCounter++; u16MetricCounter++;
/*Sensor status*/ /*Sensor State*/
sSensorSanityCheck aChecks[NUMBER_OF_SENSOR_SANITY_CHECKS]; sSensorSanityCheck aChecks[NUMBER_OF_SENSOR_SANITY_CHECKS];
getSensorSanityStates(aChecks); getSensorSanityStates(aChecks);
for (size_t i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++) for (size_t i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++)
{ {
strcpy(aMetrics[u16MetricCounter].caMetricName, aChecks[i].name); strcpy(aMetrics[u16MetricCounter].caMetricName, aChecks[i].name);
strcat(aMetrics[u16MetricCounter].caMetricName, "_status"); strcat(aMetrics[u16MetricCounter].caMetricName, "_state");
aMetrics[u16MetricCounter].type = INTEGER_U8; aMetrics[u16MetricCounter].type = INTEGER_U8;
aMetrics[u16MetricCounter].u8MetricValue = aChecks[i].status; aMetrics[u16MetricCounter].u8MetricValue = aChecks[i].state;
u16MetricCounter++; u16MetricCounter++;
} }
@ -199,8 +200,14 @@ void taskMetrics(void *pvParameters)
aMetrics[u16MetricCounter].u8MetricValue = getSafetyState(); aMetrics[u16MetricCounter].u8MetricValue = getSafetyState();
u16MetricCounter++; u16MetricCounter++;
/*SNTP Status*/ /*Control State*/
strcpy(aMetrics[u16MetricCounter].caMetricName, "sntp_status"); strcpy(aMetrics[u16MetricCounter].caMetricName, "control_state");
aMetrics[u16MetricCounter].type = INTEGER_U8;
aMetrics[u16MetricCounter].u8MetricValue = getControlState();
u16MetricCounter++;
/*SNTP State*/
strcpy(aMetrics[u16MetricCounter].caMetricName, "sntp_state");
aMetrics[u16MetricCounter].type = INTEGER_U8; aMetrics[u16MetricCounter].type = INTEGER_U8;
aMetrics[u16MetricCounter].u8MetricValue = getSntpState(); aMetrics[u16MetricCounter].u8MetricValue = getSntpState();
u16MetricCounter++; u16MetricCounter++;

View File

@ -77,7 +77,7 @@ void checkSensorSanity(void)
for (int i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++) for (int i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++)
{ {
// printf("Check sanity of sensor %s:\n", sanityChecks[i].name); // printf("Check sanity of sensor %s:\n", sanityChecks[i].name);
// printf(" Status: %u\n", sanityChecks[i].status); // printf(" state: %u\n", sanityChecks[i].state);
// printf(" Sensor Limits: Max = %.2f, Min = %.2f\n", sanityChecks[i].sSensorLimit.max, sanityChecks[i].sSensorLimit.min); // printf(" Sensor Limits: Max = %.2f, Min = %.2f\n", sanityChecks[i].sSensorLimit.max, sanityChecks[i].sSensorLimit.min);
// printf(" Last Sensor Temperature: %.2f\n", sanityChecks[i].fSensorTemperatureLast); // printf(" Last Sensor Temperature: %.2f\n", sanityChecks[i].fSensorTemperatureLast);
@ -86,7 +86,7 @@ void checkSensorSanity(void)
if (sCurrentMeasurement.state == MEASUREMENT_FAULT) if (sCurrentMeasurement.state == MEASUREMENT_FAULT)
{ {
ESP_LOGE(TAG, "%s Sensor not found!", sanityChecks[i].name); ESP_LOGE(TAG, "%s Sensor not found!", sanityChecks[i].name);
sanityChecks[i].status = SENSOR_NOT_FOUND; sanityChecks[i].state = SENSOR_NOT_FOUND;
sSafetyState = SAFETY_SENSOR_ERROR; sSafetyState = SAFETY_SENSOR_ERROR;
} }
else else
@ -97,7 +97,7 @@ void checkSensorSanity(void)
if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL)) if (sanityChecks[i].uUnchangedCounter >= (SENSOR_GRACE_PERIOD / PERIODIC_INTERVAL))
{ {
ESP_LOGE(TAG, "%s Sensor reported unchanged value! %lf == %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].fSensorTemperatureLast); ESP_LOGE(TAG, "%s Sensor reported unchanged value! %lf == %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].fSensorTemperatureLast);
sanityChecks[i].status = SENSOR_UNCHANGED; sanityChecks[i].state = SENSOR_UNCHANGED;
sSafetyState = SAFETY_SENSOR_ERROR; sSafetyState = SAFETY_SENSOR_ERROR;
} }
} }
@ -108,23 +108,23 @@ void checkSensorSanity(void)
if (sCurrentMeasurement.fCurrentValue > sanityChecks[i].sSensorLimit.max) if (sCurrentMeasurement.fCurrentValue > sanityChecks[i].sSensorLimit.max)
{ {
ESP_LOGE(TAG, "%s Sensor reported too high value! %lf > %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].sSensorLimit.max); ESP_LOGE(TAG, "%s Sensor reported too high value! %lf > %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].sSensorLimit.max);
sanityChecks[i].status = SENSOR_TOO_HIGH; sanityChecks[i].state = SENSOR_TOO_HIGH;
sSafetyState = SAFETY_SENSOR_ERROR; sSafetyState = SAFETY_SENSOR_ERROR;
} }
else if (sCurrentMeasurement.fCurrentValue < sanityChecks[i].sSensorLimit.min) else if (sCurrentMeasurement.fCurrentValue < sanityChecks[i].sSensorLimit.min)
{ {
ESP_LOGE(TAG, "%s Sensor reported too low value! %lf < %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].sSensorLimit.min); ESP_LOGE(TAG, "%s Sensor reported too low value! %lf < %lf", sanityChecks[i].name, sCurrentMeasurement.fCurrentValue, sanityChecks[i].sSensorLimit.min);
sanityChecks[i].status = SENSOR_TOO_LOW; sanityChecks[i].state = SENSOR_TOO_LOW;
sSafetyState = SAFETY_SENSOR_ERROR; sSafetyState = SAFETY_SENSOR_ERROR;
} }
else else
{ {
sanityChecks[i].uUnchangedCounter = 0U; sanityChecks[i].uUnchangedCounter = 0U;
sanityChecks[i].status = SENSOR_NO_ERROR; sanityChecks[i].state = SENSOR_NO_ERROR;
} }
} }
} }
// printf(" Status: %u\n", sanityChecks[i].status); // printf(" state: %u\n", sanityChecks[i].state);
} }
} }
@ -142,7 +142,7 @@ void getSensorSanityStates(sSensorSanityCheck *pSensorSanityChecks)
for (size_t i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++) for (size_t i = 0; i < NUMBER_OF_SENSOR_SANITY_CHECKS; i++)
{ {
// Copy only the needed attributes // Copy only the needed attributes
pSensorSanityChecks[i].status = sanityChecks[i].status; pSensorSanityChecks[i].state = sanityChecks[i].state;
strcpy(pSensorSanityChecks[i].name, sanityChecks[i].name); strcpy(pSensorSanityChecks[i].name, sanityChecks[i].name);
} }
xSemaphoreGiveRecursive(xMutexAccessSafety); xSemaphoreGiveRecursive(xMutexAccessSafety);

View File

@ -30,7 +30,7 @@ typedef struct _TemperatureSensorLimit
} sTemperatureSensorLimit; } sTemperatureSensorLimit;
typedef struct _SensorSanityCheck typedef struct _SensorSanityCheck
{ {
eSensorErrorState status; eSensorErrorState state;
char name[MAX_ERROR_STRING_SIZE]; char name[MAX_ERROR_STRING_SIZE];
sTemperatureSensorLimit sSensorLimit; sTemperatureSensorLimit sSensorLimit;
float fSensorTemperatureLast; float fSensorTemperatureLast;