# Sensor Basics Utility Library

---

These are basic features that could be used if required... But was something made in "spare time"

## Source Code Location

**Files:**

- `<span class="editor-theme-code">components/sensor_board/sensor_basics/sensor_basics.h</span>`<span style="white-space: pre-wrap;"> - Function declarations and documentation</span>
- `<span class="editor-theme-code">components/sensor_board/sensor_basics/sensor_basics.c</span>`<span style="white-space: pre-wrap;"> - Implementation</span>

**Dependencies:**

- `<span class="editor-theme-code">result.h</span>`<span style="white-space: pre-wrap;"> - Standard result/error code definitions</span>
- `<span class="editor-theme-code">stdint.h</span>`<span style="white-space: pre-wrap;"> - Integer type definitions</span>

---

## pH Sensor Functions

### validate\_ph\_value()

Validates if a pH value is within the acceptable range (0-14).

```c
/**
 * @brief Validates if a pH value is within the acceptable range (0-14).
 * @param ph_value The pH value to validate.
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_ph_value(float ph_value);
```

**Implementation:**

```c
result_t validate_ph_value(float ph_value) {
    if (ph_value >= 0.0f && ph_value <= 14.0f) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">ph_value</span>`<span style="white-space: pre-wrap;"> - Float value to validate (typical range: 0.0 to 14.0)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value is outside 0-14 range</span>

**Usage Example:**

```c
float ph_reading = ph_sensor.ph_value;

if (validate_ph_value(ph_reading) == RESULT_OK) {
    LOG_INFO("pH sensor valid: %.2f", ph_reading);
    diagnostics.ph_sensor.state = SENSOR_OPERATING;
} else {
    LOG_ERROR("pH out of range: %.2f", ph_reading);
    diagnostics.ph_sensor.state = SENSOR_ERROR;
    diagnostics.ph_sensor.error_code = PHErrorCode_PH_INVALID_DATA;
}
```

---

## Temperature Conversion Functions

### celsius\_to\_fahrenheit()

Converts temperature from Celsius to Fahrenheit.

```c
/**
 * @brief Converts temperature from Celsius to Fahrenheit.
 * @param celsius The temperature in Celsius.
 * @param fahrenheit Pointer to a float where the converted Fahrenheit temperature will be stored.
 * @return RESULT_OK on success, or RESULT_ERR_INVALID_ARG if fahrenheit is NULL.
 */
result_t celsius_to_fahrenheit(float celsius, float *fahrenheit);
```

**Conversion Formula:**<span style="white-space: pre-wrap;"> °F = (°C × 9/5) + 32</span>

**Parameters:**

- `<span class="editor-theme-code">celsius</span>`<span style="white-space: pre-wrap;"> - Input temperature in Celsius</span>
- `<span class="editor-theme-code">fahrenheit</span>`<span style="white-space: pre-wrap;"> - Pointer to output variable (must not be NULL)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Conversion successful</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> - fahrenheit pointer is NULL</span>

**Status: Currently commented out in implementation**

**Usage Example:**

```c
float temp_celsius = 25.0f;
float temp_fahrenheit;

if (celsius_to_fahrenheit(temp_celsius, &temp_fahrenheit) == RESULT_OK) {
    LOG_INFO("Temperature: %.1f°C = %.1f°F", temp_celsius, temp_fahrenheit);
}
```

### fahrenheit\_to\_celsius()

Converts temperature from Fahrenheit to Celsius.

```c
/**
 * @brief Converts temperature from Fahrenheit to Celsius.
 * @param fahrenheit The temperature in Fahrenheit.
 * @param celsius Pointer to a float where the converted Celsius temperature will be stored.
 * @return RESULT_OK on success, or RESULT_ERR_INVALID_ARG if celsius is NULL.
 */
result_t fahrenheit_to_celsius(float fahrenheit, float *celsius);
```

**Conversion Formula:**<span style="white-space: pre-wrap;"> °C = (°F - 32) × 5/9</span>

**Status: Currently commented out in implementation**

---

## IMU (Accelerometer) Functions

### validate\_accelerometer\_value()

Validates if a single accelerometer value is within the typical range.

```c
/**
 * @brief Validates if an accelerometer value is within the typical range.
 * @param accel_value The accelerometer value to validate.
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_accelerometer_value(float accel_value);
```

**Implementation:**

```c
result_t validate_accelerometer_value(float accel_value) {
    if (accel_value >= -160.0f && accel_value <= 160.0f) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">accel_value</span>`<span style="white-space: pre-wrap;"> - Single axis acceleration value in m/s²</span>

**Valid Range:**<span style="white-space: pre-wrap;"> -160.0 to +160.0 m/s² (typical ±16g sensor range)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value exceeds acceptable limits</span>

**Usage Example:**

```c
float accel_x = imu_data.accel[0];

if (validate_accelerometer_value(accel_x) == RESULT_OK) {
    LOG_DEBUG("Accel X valid: %.2f m/s²", accel_x);
} else {
    LOG_ERROR("Accel X out of range: %.2f m/s²", accel_x);
}
```

### validate\_imu\_data()

Validates all three axes of accelerometer data simultaneously.

```c
/**
 * @brief Validates all three axes of accelerometer data.
 * @param accel_x The acceleration value for the X-axis.
 * @param accel_y The acceleration value for the Y-axis.
 * @param accel_z The acceleration value for the Z-axis.
 * @return RESULT_OK if all values are valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_imu_data(float accel_x, float accel_y, float accel_z);
```

**Implementation:**

```c
result_t validate_imu_data(float accel_x, float accel_y, float accel_z) {
    TRY(validate_accelerometer_value(accel_x));
    TRY(validate_accelerometer_value(accel_y));
    TRY(validate_accelerometer_value(accel_z));
    return RESULT_OK;
}
```

**Parameters:**

- `<span class="editor-theme-code">accel_x</span>`<span style="white-space: pre-wrap;"> - X-axis acceleration (m/s²)</span>
- `<span class="editor-theme-code">accel_y</span>`<span style="white-space: pre-wrap;"> - Y-axis acceleration (m/s²)</span>
- `<span class="editor-theme-code">accel_z</span>`<span style="white-space: pre-wrap;"> - Z-axis acceleration (m/s²)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - All three axes within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Any axis exceeds acceptable limits</span>

**Usage Example:**

```c
if (validate_imu_data(imu_data.accel[0], imu_data.accel[1], imu_data.accel[2]) == RESULT_OK) {
    LOG_INFO("IMU acceleration valid");
    diagnostics.imu_sensor.state = SENSOR_OPERATING;
} else {
    LOG_ERROR("IMU acceleration out of range");
    diagnostics.imu_sensor.state = SENSOR_ERROR;
}
```

---

## Pressure Conversion Functions

### bar\_to\_psi()

Converts pressure from bar to psi (pounds per square inch).

```c
/**
 * @brief Converts pressure from bar to psi.
 * @param bar The pressure in bar.
 * @param psi Pointer to a float where the converted psi pressure will be stored.
 * @return RESULT_OK on success, or RESULT_ERR_INVALID_ARG if psi is NULL.
 */
result_t bar_to_psi(float bar, float *psi);
```

**Conversion Formula:**<span style="white-space: pre-wrap;"> psi = bar × 14.5038</span>

**Parameters:**

- `<span class="editor-theme-code">bar</span>`<span style="white-space: pre-wrap;"> - Pressure in bar</span>
- `<span class="editor-theme-code">psi</span>`<span style="white-space: pre-wrap;"> - Pointer to output variable (must not be NULL)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Conversion successful</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> - psi pointer is NULL</span>

**Status: Currently commented out in implementation**

**Usage Example:**

```c
float pressure_bar = 5.0f;
float pressure_psi;

if (bar_to_psi(pressure_bar, &pressure_psi) == RESULT_OK) {
    LOG_INFO("Pressure: %.2f bar = %.2f psi", pressure_bar, pressure_psi);
}
```

### psi\_to\_bar()

Converts pressure from psi to bar.

```c
/**
 * @brief Converts pressure from psi to bar.
 * @param psi The pressure in psi.
 * @param bar Pointer to a float where the converted bar pressure will be stored.
 * @return RESULT_OK on success, or RESULT_ERR_INVALID_ARG if bar is NULL.
 */
result_t psi_to_bar(float psi, float *bar);
```

**Conversion Formula:**<span style="white-space: pre-wrap;"> bar = psi ÷ 14.5038</span>

**Status: Currently commented out in implementation**

---

## GPS Functions

### validate\_gps\_latitude()

Validates GPS latitude value is within valid range.

```c
/**
 * @brief Validates GPS latitude value.
 * @param latitude The latitude value to validate (-90 to +90 degrees).
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_gps_latitude(double latitude);
```

**Implementation:**

```c
result_t validate_gps_latitude(double latitude) {
    if (latitude >= -90.0 && latitude <= 90.0) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">latitude</span>`<span style="white-space: pre-wrap;"> - Latitude in degrees (double precision)</span>

**Valid Range:**<span style="white-space: pre-wrap;"> -90.0 to +90.0 degrees</span>

- Negative = South
- Positive = North
- 0° = Equator

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value outside ±90 degrees</span>

### validate\_gps\_longitude()

Validates GPS longitude value is within valid range.

```c
/**
 * @brief Validates GPS longitude value.
 * @param longitude The longitude value to validate (-180 to +180 degrees).
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_gps_longitude(double longitude);
```

**Implementation:**

```c
result_t validate_gps_longitude(double longitude) {
    if (longitude >= -180.0 && longitude <= 180.0) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">longitude</span>`<span style="white-space: pre-wrap;"> - Longitude in degrees (double precision)</span>

**Valid Range:**<span style="white-space: pre-wrap;"> -180.0 to +180.0 degrees</span>

- Negative = West
- Positive = East
- 0° = Prime Meridian
- ±180° = International Date Line

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value outside ±180 degrees</span>

### validate\_gps\_hdop()

Validates GPS Horizontal Dilution of Precision (HDOP) value.

```c
/**
 * @brief Validates GPS HDOP value.
 * @param hdop The HDOP value to validate (0-50 typical range).
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_gps_hdop(float hdop);
```

**Implementation:**

```c
result_t validate_gps_hdop(float hdop) {
    if (hdop >= 0.0f && hdop <= 50.0f) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">hdop</span>`<span style="white-space: pre-wrap;"> - Horizontal dilution of precision value</span>

**Valid Range:**<span style="white-space: pre-wrap;"> 0.0 to 50.0</span>

**HDOP Quality Interpretation:**

- &lt;1 - Ideal
- 1-2 - Excellent
- 2-5 - Good
- 5-10 - Moderate
- 10-20 - Fair
- &gt;20 - Poor

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value exceeds acceptable limits</span>

### validate\_gps\_satellite\_count()

Validates GPS satellite count is within valid range.

```c
/**
 * @brief Validates GPS satellite count.
 * @param satellites The number of satellites to validate.
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_gps_satellite_count(int32_t satellites);
```

**Implementation:**

```c
result_t validate_gps_satellite_count(int32_t satellites) {
    if (satellites >= 0 && satellites <= 30) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">satellites</span>`<span style="white-space: pre-wrap;"> - Number of satellites in view</span>

**Valid Range:**<span style="white-space: pre-wrap;"> 0 to 30 satellites</span>

**Satellite Count Guidance:**

- 0-3 - No/poor fix possible
- 4-5 - 3D fix possible
- 6-9 - Good coverage
- 10+ - Excellent coverage

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Negative value or exceeds 30</span>

**Usage Example (Full GPS Validation):**

```c
if (validate_gps_latitude(gps_data.latitude) == RESULT_OK &&
    validate_gps_longitude(gps_data.longitude) == RESULT_OK &&
    validate_gps_hdop(gps_data.hdop) == RESULT_OK &&
    validate_gps_satellite_count(gps_data.satellites) == RESULT_OK) {
    
    LOG_INFO("GPS fix valid: %.6f, %.6f (sats=%d)", 
             gps_data.latitude, gps_data.longitude, gps_data.satellites);
    diagnostics.gps_sensor_1.state = SENSOR_OPERATING;
} else {
    LOG_WARNING("GPS validation failed");
    diagnostics.gps_sensor_1.state = SENSOR_ERROR;
}
```

---

## Error Handling Pattern

All validation functions follow a consistent pattern:

```c
// Check sensor data
if (validate_<sensor>_<field>(value) == RESULT_OK) {
    // Data is valid - use it
    diagnostics.<sensor>.state = SENSOR_OPERATING;
} else {
    // Data is invalid - set error state
    diagnostics.<sensor>.state = SENSOR_ERROR;
    diagnostics.<sensor>.error_code = <ERROR_CODE>_INVALID_DATA;
}
```

**TRY Macro Usage:**

<span style="white-space: pre-wrap;">The implementation uses a </span>`<span class="editor-theme-code">TRY()</span>`<span style="white-space: pre-wrap;"> macro for error propagation (from result.h):</span>

```c
// In validate_imu_data()
result_t validate_imu_data(float accel_x, float accel_y, float accel_z) {
    TRY(validate_accelerometer_value(accel_x));  // Return on error
    TRY(validate_accelerometer_value(accel_y));  // Return on error
    TRY(validate_accelerometer_value(accel_z));  // Return on error
    return RESULT_OK;
}
```

---

## Implementation Status

**Currently Implemented (Active):**

- ✓ validate\_ph\_value()
- ✓ validate\_accelerometer\_value()
- ✓ validate\_imu\_data()
- ✓ validate\_gps\_latitude()
- ✓ validate\_gps\_longitude()
- ✓ validate\_gps\_hdop()
- ✓ validate\_gps\_satellite\_count()

**Currently Commented Out (Inactive):**

- ⊘ celsius\_to\_fahrenheit() - Declared but not implemented
- ⊘ fahrenheit\_to\_celsius() - Declared but not implemented
- ⊘ bar\_to\_psi() - Declared but not implemented
- ⊘ psi\_to\_bar() - Declared but not implemented

**Note:**<span style="white-space: pre-wrap;"> Temperature and pressure conversions are stubbed out in the current implementation. They can be enabled by uncommenting the implementation in sensor\_basics.c if needed for future features.</span>

---

## Testing

**Test Suite Location:**<span style="white-space: pre-wrap;"> </span>`<span class="editor-theme-code">test/sensor_board/test_sensor_basics/</span>`

**Building Tests:**

```bash
// Run all sensor_basics tests
pio test -e sensor_board -f test_sensor_basics

// Run with verbose output
pio test -e sensor_board -f test_sensor_basics -v
```

**Test Coverage:**

- Boundary value testing (min/max ranges)
- Edge cases (0 values, extreme values)
- Valid range acceptance
- Invalid range rejection
- NULL pointer handling for conversion functions

---

## Integration in Main Application

**Typical Usage in main.c:**

```c
// After polling a sensor
result_t poll_result = poll_gps_sensor(&gps_data);

if (poll_result == RESULT_OK) {
    // Validate all GPS fields before using
    if (validate_gps_latitude(gps_data.latitude) == RESULT_OK &&
        validate_gps_longitude(gps_data.longitude) == RESULT_OK) {
        
        diagnostics.gps_sensor_1.state = SENSOR_OPERATING;
        // Safe to use: gps_data.latitude, gps_data.longitude
        
    } else {
        diagnostics.gps_sensor_1.state = SENSOR_ERROR;
        diagnostics.gps_sensor_1.error_code = GPS_INVALID_DATA;
    }
} else if (poll_result == RESULT_ERR_COMMS) {
    diagnostics.gps_sensor_1.state = SENSOR_ERROR;
    diagnostics.gps_sensor_1.error_code = GPS_COMMUNICATION_FAILURE;
}
```