29 Commits

Author SHA1 Message Date
964a2d3375 chores: remove release file from repo
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:45:20 +02:00
5ca64315eb chores: remove zephyr sample file
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:44:53 +02:00
9b06b9f6f7 chores: remove useless gitkeep file
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:44:31 +02:00
2ec7eefaa0 chore: remove .DS_STORE
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:20:38 +02:00
3293d9194c docs(GULAG): fix typo
Signed-off-by: Klagarge <remi@heredero.ch>
2026-06-04 21:20:38 +02:00
adrien balleyguier
d593dfd759 feat(nodes): setting the fastest BLE advertising frequency to 1 minute instead of 2
This is motivated by the measure period by the css811 sensor, which is 1 minute and cannot be set to 2.
Hence, we adverstise every minute to avoid trashing half of the measurements
2026-06-04 21:01:43 +02:00
adrien balleyguier
83705f4b5e fix(nodes): reduce battery consumption
co-authored-by: Klagarge <remi@heredero.ch>
co-authored-by: Terrazed <simon.donnet-monay@nordicsemi.no>
2026-06-04 21:01:43 +02:00
adrien balleyguier
dffdcd36db feat(nodes): using an explicit thread, as it may cause the huge battery usage 2026-06-04 21:01:43 +02:00
adrien balleyguier
676913e280 feat(nodes): adding battery percent as new key/value in the BLE frame
Closes: #66
2026-06-04 21:01:43 +02:00
adrien balleyguier
8a559024a6 feat(nodes): add the battery level in the ble frame
wip: The battery_setup fails for some unknown reasons
2026-06-04 21:01:43 +02:00
adrien balleyguier
7c19b5dadb fix(nodes): using MAC address as BT_ADDR 2026-06-04 21:01:42 +02:00
adrien balleyguier
c4e1315478 feat(nodes): adding management of read error on each values, hence
Closes: #65
2026-06-04 21:01:42 +02:00
adrien balleyguier
6b12612580 fix(nodes): changing incorrect comment 2026-06-04 21:01:42 +02:00
adrien balleyguier
c32728ccbe doc(nodes): adding comments in the nodes code
close #62
2026-06-04 21:01:42 +02:00
adrien balleyguier
78167ceb38 feat(nodes): adding nodes firmware v1.0
Refs: #3
2026-06-04 21:01:42 +02:00
adrien balleyguier
3cdfbef680 fix(nodes): changing window status sensor to have unequipped nodes return 'window is closed' status
Refs: #3
2026-06-04 21:01:42 +02:00
adrien balleyguier
94e0518fa6 fix(nodes): co2 level fetched as desibed in the documentation
The first samples (up to 3) sends co2 level 0xffffffff since the sensor is not ready yet
Refs: #3
2026-06-04 20:59:58 +02:00
adrien balleyguier
f4ac6e91b0 fix(nodes): properly fetching data from temp/hygro sensor
Refs: #3
2026-06-04 20:59:36 +02:00
adrien balleyguier
6fd9959329 fix(nodes): fixing switch device tree and wrong ordering of ble values 2026-06-04 20:59:20 +02:00
adrien balleyguier
35fbe7c488 feat(nodes): adding window status reading 2026-06-04 20:59:20 +02:00
adrien balleyguier
158767deec fix(nodes): adjusting broadcasted data to fit definition
Refs: #3
2026-06-04 20:59:20 +02:00
adrien balleyguier
bce2417ded feat(nodes): adding co2_level retrieval
Refs: #3
2026-06-04 20:59:10 +02:00
adrien balleyguier
53ccda306b feat(nodes): WIP adding first implementation for BLE advertising.
wip: Not tested for now

Refs: #3
2026-06-04 20:58:59 +02:00
adrien balleyguier
71ed4c4bf9 feat(nodes): adding thermometer and hygrometer
Changed sensor value retrieval to return error_code
2026-06-04 20:58:14 +02:00
adrien balleyguier
e1d274470b feat(nodes): WIP supervisor first implementation
wip: untested
2026-06-04 20:58:14 +02:00
adrien balleyguier
f0bc4ee373 feat(nodes) : setting folder construction 2026-06-04 20:47:48 +02:00
adrien balleyguier
f71db42f47 feat(nodes) : adding blinky as starting point for the node implementation 2026-06-04 20:47:48 +02:00
adrien balleyguier
8b9e215cc6 doc(nodes): adding battery to class diagram
linked #63
2026-06-04 15:06:21 +02:00
adrien balleyguier
caee92c595 fix(doc) : changing small mishaps following discussions 2026-06-04 15:06:21 +02:00
27 changed files with 869 additions and 1 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -2,7 +2,7 @@
## General
- Git history **has** to be kept linear. Branches **have** to be __rebased__ before being merged.
- Merge commit is stricly forbidden.
- Merge commit is strictly forbidden.
- Force pushes are disallowed on shared branches unless discussed beforehand with affected people.
## Branches

View File

@@ -15,6 +15,7 @@ class "Window_status" as win{}
class "Hygrometer" as hygro{}
class "Thermometer" as thermo{}
class "CO2_level" as co2{}
class "Battery_level" as batt{}
sup o-d- ble
sup o-u- sens
@@ -22,5 +23,6 @@ sens <|-l- win
sens <|-u- hygro
sens <|-u- thermo
sens <|-r- co2
sens <|-- batt
@enduml

View File

View File

8
nodes/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(plein_de_eeeeeeeeeeee)
target_sources(app PRIVATE src/main.c src/window_status.c src/window_status.h src/thermometer.c src/thermometer.h src/hygrometer.c src/hygrometer.h src/co2_level.c src/co2_level.h src/supervisor.c src/supervisor.h src/ble_advertiser.c src/ble_advertiser.h src/battery_percent.h src/battery_percent.c)
target_sources(app PRIVATE src/modules/battery.h src/modules/battery.c)

11
nodes/app.overlay Normal file
View File

@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: Apache-2.0 */
/ {
buttons{
compatible = "gpio-keys";
window_switch: window_switch {
gpios = <&sx1509b 0 GPIO_ACTIVE_LOW>;
label = "Window Switch";
};
};
};

16
nodes/prj.conf Normal file
View File

@@ -0,0 +1,16 @@
CONFIG_ADC=y
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_BT=y
CONFIG_GPIO=y
CONFIG_LOG=n
CONFIG_CONSOLE=n
CONFIG_SERIAL=n
CONFIG_PRINTK=n
CONFIG_UART_CONSOLE=n
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_PM_DEVICE_RUNTIME=y
CONFIG_CCS811_DRIVE_MODE_3=y

View File

@@ -0,0 +1,42 @@
#include "battery_percent.h"
/**
* Taken from sample/board/nordic/battery/src/main.c
* A discharge curve specific to the power source.
*/
static const struct battery_level_point levels[] = {
/* "Curve" here eyeballed from captured data for the [Adafruit
* 3.7v 2000 mAh](https://www.adafruit.com/product/2011) LIPO
* under full load that started with a charge of 3.96 V and
* dropped about linearly to 3.58 V over 15 hours. It then
* dropped rapidly to 3.10 V over one hour, at which point it
* stopped transmitting.
*
* Based on eyeball comparisons we'll say that 15/16 of life
* goes between 3.95 and 3.55 V, and 1/16 goes between 3.55 V
* and 3.1 V.
*/
{ 10000, 3950 },
{ 625, 3550 },
{ 0, 3100 },
};
enum error_code battery_init(){
enum error_code ret = init_failed;
if(0 == battery_measure_enable(true)){
ret = success;
}else{}
return ret;
}
enum error_code battery_get_value(int* holder){
enum error_code ret = read_failed;
// battery_voltage in [mV]
int battery_voltage = battery_sample();
if(battery_voltage >= 0){
*holder = (battery_level_pptt(battery_voltage, levels)/100);
ret = success;
}else{}
return ret;
}

View File

@@ -0,0 +1,27 @@
#ifndef BATTERY_PERCENT_H
#define BATTERY_PERCENT_H
#include <zephyr/kernel.h>
#include "modules/battery.h"
#include "error_code.h"
/**
* @brief init the battery module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code battery_init();
/**
* @brief Retrieve the battery charge percentage and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
*/
enum error_code battery_get_value(int* holder);
#endif //BATTERY_PERCENT_H

View File

@@ -0,0 +1,79 @@
#include "ble_advertiser.h"
static const int BT_MS_ADV_UP = 500; // [ms] time during which the advertising is active
// keys as defined in the specs
static const char BT_KEY_SIZE = 1; // [B]
static const char BT_KEY_WINDOW = 0x01;
static const char BT_KEY_HUMIDITY = 0x02;
static const char BT_KEY_TEMP = 0x03;
static const char BT_KEY_CO2_LVL = 0x04;
static const char BT_KEY_BATT = 0x05;
// value size [B]
static const char BT_PREAMBLE_SIZE = 2;
static const char BT_VALUE_SIZE_WINDOW = 1;
static const char BT_VALUE_SIZE_HUMIDITY = 1;
static const char BT_VALUE_SIZE_TEMP = 2;
static const char BT_VALUE_SIZE_CO2_LVL = 4;
static const char BT_VALUE_SIZE_BATT = 1;
// value start index in the frame - equal to {prior field index + prior value size + key size}
static const int BT_AD_DATA_INDEX_WINDOW = BT_PREAMBLE_SIZE + BT_KEY_SIZE;
static const int BT_AD_DATA_INDEX_HUMIDITY = BT_AD_DATA_INDEX_WINDOW + BT_VALUE_SIZE_WINDOW + BT_KEY_SIZE;
static const int BT_AD_DATA_INDEX_TEMP = BT_AD_DATA_INDEX_HUMIDITY + BT_VALUE_SIZE_HUMIDITY + BT_KEY_SIZE;
static const int BT_AD_DATA_INDEX_CO2_LVL = BT_AD_DATA_INDEX_TEMP + BT_VALUE_SIZE_TEMP + BT_KEY_SIZE;
static const int BT_AD_DATA_INDEX_BATT = BT_AD_DATA_INDEX_CO2_LVL + BT_VALUE_SIZE_CO2_LVL + BT_KEY_SIZE;
// sum of all value size + size for all keys + size preamble
static const char BT_AD_TOTAL_SIZE =
BT_PREAMBLE_SIZE + (5 * BT_KEY_SIZE) +
BT_VALUE_SIZE_WINDOW + BT_VALUE_SIZE_HUMIDITY + BT_VALUE_SIZE_TEMP + BT_VALUE_SIZE_CO2_LVL + BT_VALUE_SIZE_BATT;
// data field in the broadcasted frame
// all values are set to 0 and the "company id" field is set to 0xffff
static uint8_t ad_data[] = {
0xff, 0xff,
BT_KEY_WINDOW, 0x00,
BT_KEY_HUMIDITY, 0x00,
BT_KEY_TEMP, 0x00, 0x00,
BT_KEY_CO2_LVL, 0x00, 0x00, 0x00, 0x00,
BT_KEY_BATT, 0x00
};
// frame that will be broadcasted
static const struct bt_data ad[] = {
BT_DATA(BT_DATA_MANUFACTURER_DATA, ad_data, BT_AD_TOTAL_SIZE),
};
enum error_code ble_init(){
return ((0 == bt_enable(NULL)) ? success : init_failed);
}
enum error_code ble_advertise(
enum window_status window_value,
int hygro_value,
int thermo_value,
int co2_lvl_value,
int batt_value
){
enum error_code ret = write_failed;
int shift = 0;
ad_data[BT_AD_DATA_INDEX_WINDOW] = (uint8_t)(window_value == open ? 0x1 : 0x0);
ad_data[BT_AD_DATA_INDEX_HUMIDITY] = (uint8_t)(hygro_value & 0xff);
ad_data[BT_AD_DATA_INDEX_BATT] = (uint8_t)(batt_value & 0xff);
for(int i=0;i<BT_VALUE_SIZE_TEMP;i++){
shift = 8 * (BT_VALUE_SIZE_TEMP - i - 1);
ad_data[BT_AD_DATA_INDEX_TEMP + i] = (uint8_t)((thermo_value>>(shift)) & 0xff);
}
for(int i=0;i<BT_VALUE_SIZE_CO2_LVL;i++){
shift = 8 * (BT_VALUE_SIZE_CO2_LVL - i - 1);
ad_data[BT_AD_DATA_INDEX_CO2_LVL + i] = (uint8_t)((co2_lvl_value>>(shift)) & 0xff);
}
if(0 == bt_le_adv_start(BT_LE_ADV_NCONN_IDENTITY, ad, ARRAY_SIZE(ad), NULL, 0)){
k_msleep(BT_MS_ADV_UP);
if(0 == bt_le_adv_stop()){
ret = success;
}else{}
}else{}
return ret;
}

View File

@@ -0,0 +1,37 @@
#ifndef BLE_ADVERTISER_H
#define BLE_ADVERTISER_H
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/assigned_numbers.h>
#include <zephyr/bluetooth/bluetooth.h>
#include "error_code.h"
#include "window_status.h"
/**
* @brief Initialise the ble module
* @return init_failed upon any error occuring during init
* @return success otherwise
*/
enum error_code ble_init();
/**
* @brief Sets the given values as data and broadcast the BLE frame
* @param window_value : window opening status in {open/closed}
* @param hygro_value : humidity percentage [%]
* @param thermo_value : temperature in [d°C]
* @param co2_lvl_value : co2 level in [ppm]
* @param battery_percent_value : battery current level of charge [%]
* @return write_failed if any problem occur during the BLE broadcast
* @return success otherwise
* @note The broadcast is stopped at the return time
*/
enum error_code ble_advertise(
enum window_status window_value,
int hygro_value,
int thermo_value,
int co2_lvl_value,
int batt_value
);
#endif //BLE_ADVERTISER_H

37
nodes/src/co2_level.c Normal file
View File

@@ -0,0 +1,37 @@
#include "co2_level.h"
static const struct device* dev = DEVICE_DT_GET_ONE(ams_ccs811);
const int CO2_LEVEL_EMPTY_ROOM = 400; // [ppm]
enum error_code co2_lvl_init(){
enum error_code ret = init_failed;
if(device_is_ready(dev)){
ret = success;
}else{}
return ret;
}
enum error_code co2_lvl_get_value(int* holder){
struct sensor_value temp, humidity, co2;
enum error_code ret = read_failed;
int temp_value, humidity_value;
if( (success == thermo_get_value(&temp_value)) && (success == hygro_get_value(&humidity_value)) ){
// temperature conversion from deci, no function in the API
temp.val1 = temp_value/10;
temp.val2 = temp_value%10;
// humidity conversion is straight away
humidity.val1 = humidity_value;
if(
// co2 measurement requires temperature and humidity
(0 == ccs811_envdata_update(dev, &temp, &humidity)) &&
// fetch is required to update sensor read data
(0 == sensor_sample_fetch(dev)) &&
(0 == sensor_channel_get(dev, SENSOR_CHAN_CO2, &co2))
){
*holder = co2.val1; // taking only the integer part
ret = success;
}else{}
}else{}
return success;
}

30
nodes/src/co2_level.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef CO2_LEVEL_H
#define CO2_LEVEL_H
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/sensor/ccs811.h>
#include "error_code.h"
#include "hygrometer.h"
#include "thermometer.h"
extern const int CO2_LEVEL_EMPTY_ROOM; // [ppm]
/**
* @brief init the co2 level measurement module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code co2_lvl_init();
/**
* @brief Retrieve the CO2 level measurement and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
* @note the value is not read for the first few measurements after a reboot
*/
enum error_code co2_lvl_get_value(int* holder);
#endif //CO2_LEVEL_H

16
nodes/src/error_code.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef ERROR_CODE_H
#define ERROR_CODE_H
#include <limits.h>
// enum of all error code that may occur during the plein_de_eeeeeeeeeeeeeee application runtime
enum error_code{
success = 0, // any kind of success
init_failed, // error related to initialization
read_failed, // error related to reading action
write_failed, // error related to writing action
error_unknown, // any error not linked to other code
error_code_last, // iteration purpose
};
#endif //ERROR_CODE_H

25
nodes/src/hygrometer.c Normal file
View File

@@ -0,0 +1,25 @@
#include "hygrometer.h"
static const struct device* dev = DEVICE_DT_GET_ONE(st_hts221);
enum error_code hygro_init(){
enum error_code ret = init_failed;
if(device_is_ready(dev)){
ret = success;
}else{}
return ret;
}
enum error_code hygro_get_value(int* holder){
enum error_code ret = read_failed;
struct sensor_value humidity;
if(
// fetch is required to update sensor read data
(sensor_sample_fetch(dev) >= 0) &&
(sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &humidity) >= 0)
){
*holder = humidity.val1; //taking only the integer part
ret = success;
}else{}
return ret;
}

26
nodes/src/hygrometer.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef HYGROMETER_H
#define HYGROMETER_H
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include "error_code.h"
/**
* @brief init the hygrometer module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code hygro_init();
/**
* @brief Retrieve the humidity level and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
*/
enum error_code hygro_get_value(int* holder);
#endif //HYGROMETER_H

18
nodes/src/main.c Normal file
View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include "supervisor.h"
// size of a stack used by the supervisor thread
#define STACKSIZE 1024
// scheduling priority used by the supervisor thread
#define PRIORITY 7
K_THREAD_DEFINE(supervisor_id, STACKSIZE, thread_supervisor, NULL, NULL, NULL, PRIORITY, 0, 0);

231
nodes/src/modules/battery.c Normal file
View File

@@ -0,0 +1,231 @@
/*
* Copyright (c) 2018-2019 Peter Bigot Consulting, LLC
* Copyright (c) 2019-2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include "battery.h"
LOG_MODULE_REGISTER(BATTERY, CONFIG_ADC_LOG_LEVEL);
#define VBATT DT_PATH(vbatt)
#define ZEPHYR_USER DT_PATH(zephyr_user)
#ifdef CONFIG_BOARD_THINGY52_NRF52832
/* This board uses a divider that reduces max voltage to
* reference voltage (600 mV).
*/
#define BATTERY_ADC_GAIN ADC_GAIN_1
#else
/* Other boards may use dividers that only reduce battery voltage to
* the maximum supported by the hardware (3.6 V)
*/
#define BATTERY_ADC_GAIN ADC_GAIN_1_6
#endif
struct io_channel_config {
uint8_t channel;
};
struct divider_config {
struct io_channel_config io_channel;
struct gpio_dt_spec power_gpios;
/* output_ohm is used as a flag value: if it is nonzero then
* the battery is measured through a voltage divider;
* otherwise it is assumed to be directly connected to Vdd.
*/
uint32_t output_ohm;
uint32_t full_ohm;
};
static const struct divider_config divider_config = {
#if DT_NODE_HAS_STATUS_OKAY(VBATT)
.io_channel = {
DT_IO_CHANNELS_INPUT(VBATT),
},
.power_gpios = GPIO_DT_SPEC_GET_OR(VBATT, power_gpios, {}),
.output_ohm = DT_PROP(VBATT, output_ohms),
.full_ohm = DT_PROP(VBATT, full_ohms),
#else /* /vbatt exists */
.io_channel = {
DT_IO_CHANNELS_INPUT(ZEPHYR_USER),
},
#endif /* /vbatt exists */
};
struct divider_data {
const struct device *adc;
struct adc_channel_cfg adc_cfg;
struct adc_sequence adc_seq;
int16_t raw;
};
static struct divider_data divider_data = {
#if DT_NODE_HAS_STATUS_OKAY(VBATT)
.adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(VBATT)),
#else
.adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(ZEPHYR_USER)),
#endif
};
static int divider_setup(void)
{
const struct divider_config *cfg = &divider_config;
const struct io_channel_config *iocp = &cfg->io_channel;
const struct gpio_dt_spec *gcp = &cfg->power_gpios;
struct divider_data *ddp = &divider_data;
struct adc_sequence *asp = &ddp->adc_seq;
struct adc_channel_cfg *accp = &ddp->adc_cfg;
int rc;
if (!device_is_ready(ddp->adc)) {
LOG_ERR("ADC device is not ready %s", ddp->adc->name);
return -ENOENT;
}
if (gcp->port) {
if (!device_is_ready(gcp->port)) {
LOG_ERR("%s: device not ready", gcp->port->name);
return -ENOENT;
}
rc = gpio_pin_configure_dt(gcp, GPIO_OUTPUT_INACTIVE);
if (rc != 0) {
LOG_ERR("Failed to control feed %s.%u: %d",
gcp->port->name, gcp->pin, rc);
return rc;
}
}
*asp = (struct adc_sequence){
.channels = BIT(0),
.buffer = &ddp->raw,
.buffer_size = sizeof(ddp->raw),
.oversampling = 4,
.calibrate = true,
};
#ifdef CONFIG_ADC_NRFX_SAADC
*accp = (struct adc_channel_cfg){
.gain = BATTERY_ADC_GAIN,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
};
if (cfg->output_ohm != 0) {
accp->input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0
+ iocp->channel;
} else {
accp->input_positive = SAADC_CH_PSELP_PSELP_VDD;
}
asp->resolution = 14;
#else /* CONFIG_ADC_var */
#error Unsupported ADC
#endif /* CONFIG_ADC_var */
rc = adc_channel_setup(ddp->adc, accp);
LOG_INF("Setup AIN%u got %d", iocp->channel, rc);
return rc;
}
static bool battery_ok;
static int battery_setup(void)
{
int rc = divider_setup();
battery_ok = (rc == 0);
LOG_INF("Battery setup: %d %d", rc, battery_ok);
return rc;
}
SYS_INIT(battery_setup, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
int battery_measure_enable(bool enable)
{
int rc = -ENOENT;
if (battery_ok) {
const struct gpio_dt_spec *gcp = &divider_config.power_gpios;
rc = 0;
if (gcp->port) {
rc = gpio_pin_set_dt(gcp, enable);
}
}
return rc;
}
int battery_sample(void)
{
int rc = -ENOENT;
if (battery_ok) {
struct divider_data *ddp = &divider_data;
const struct divider_config *dcp = &divider_config;
struct adc_sequence *sp = &ddp->adc_seq;
rc = adc_read(ddp->adc, sp);
sp->calibrate = false;
if (rc == 0) {
int32_t val = ddp->raw;
adc_raw_to_millivolts(adc_ref_internal(ddp->adc),
ddp->adc_cfg.gain,
sp->resolution,
&val);
if (dcp->output_ohm != 0) {
rc = val * (uint64_t)dcp->full_ohm
/ dcp->output_ohm;
LOG_INF("raw %u ~ %u mV => %d mV\n",
ddp->raw, val, rc);
} else {
rc = val;
LOG_INF("raw %u ~ %u mV\n", ddp->raw, val);
}
}
}
return rc;
}
unsigned int battery_level_pptt(unsigned int batt_mV,
const struct battery_level_point *curve)
{
const struct battery_level_point *pb = curve;
if (batt_mV >= pb->lvl_mV) {
/* Measured voltage above highest point, cap at maximum. */
return pb->lvl_pptt;
}
/* Go down to the last point at or below the measured voltage. */
while ((pb->lvl_pptt > 0)
&& (batt_mV < pb->lvl_mV)) {
++pb;
}
if (batt_mV < pb->lvl_mV) {
/* Below lowest point, cap at minimum */
return pb->lvl_pptt;
}
/* Linear interpolation between below and above points. */
const struct battery_level_point *pa = pb - 1;
return pb->lvl_pptt
+ ((pa->lvl_pptt - pb->lvl_pptt)
* (batt_mV - pb->lvl_mV)
/ (pa->lvl_mV - pb->lvl_mV));
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef APPLICATION_BATTERY_H_
#define APPLICATION_BATTERY_H_
/** Enable or disable measurement of the battery voltage.
*
* @param enable true to enable, false to disable
*
* @return zero on success, or a negative error code.
*/
int battery_measure_enable(bool enable);
/** Measure the battery voltage.
*
* @return the battery voltage in millivolts, or a negative error
* code.
*/
int battery_sample(void);
/** A point in a battery discharge curve sequence.
*
* A discharge curve is defined as a sequence of these points, where
* the first point has #lvl_pptt set to 10000 and the last point has
* #lvl_pptt set to zero. Both #lvl_pptt and #lvl_mV should be
* monotonic decreasing within the sequence.
*/
struct battery_level_point {
/** Remaining life at #lvl_mV. */
uint16_t lvl_pptt;
/** Battery voltage at #lvl_pptt remaining life. */
uint16_t lvl_mV;
};
/** Calculate the estimated battery level based on a measured voltage.
*
* @param batt_mV a measured battery voltage level.
*
* @param curve the discharge curve for the type of battery installed
* on the system.
*
* @return the estimated remaining capacity in parts per ten
* thousand.
*/
unsigned int battery_level_pptt(unsigned int batt_mV,
const struct battery_level_point *curve);
#endif /* APPLICATION_BATTERY_H_ */

75
nodes/src/supervisor.c Normal file
View File

@@ -0,0 +1,75 @@
#include "supervisor.h"
const int SLEEP_GRANULARITY = 1; // [min]
const int SLEEP_MIN_DURATION = SLEEP_GRANULARITY; // [min]
const int SLEEP_MAX_DURATION = 30; // [min]
// Zephyr related stuff
void thread_supervisor(){
supervisor_init();
supervisor_run();
//should never return
}
// supervisor stuff
enum error_code supervisor_init(){
enum error_code ret = init_failed;
if(
success == ble_init() &&
success == co2_lvl_init() &&
success == hygro_init() &&
success == thermo_init() &&
success == window_init() &&
success == battery_init()
){
ret = success;
}else{}
return ret;
}
enum error_code supervisor_run(){
int co2_lvl_value = -1;
int hygro_value = -1;
int thermo_value = -1;
int batt_value = -1;
enum window_status window_value = unknown;
enum error_code co2_lvl_status, hygro_status, thermo_status, window_status, batt_status;
int current_sleep_time = SLEEP_MIN_DURATION;
while(1){
co2_lvl_status = co2_lvl_get_value(&co2_lvl_value);
hygro_status = hygro_get_value(&hygro_value);
thermo_status = thermo_get_value(&thermo_value);
window_status = window_get_value(&window_value);
batt_status = battery_get_value(&batt_value);
if(success != window_status){
window_value = unknown;
}else{}
if(success != hygro_status){
hygro_value = -1;
}else{}
if(success != thermo_status){
thermo_value = -1;
}else{}
if(success != co2_lvl_status){
co2_lvl_value = -1;
}else{}
if(success != batt_status){
batt_value = -1;
}else{}
ble_advertise(window_value, hygro_value, thermo_value, co2_lvl_value, batt_value);
if((co2_lvl_value > CO2_LEVEL_EMPTY_ROOM) || (window_value == open)){
// there are people in the room, or someone forgot to close the window
current_sleep_time = SLEEP_MIN_DURATION;
}else{
// no one is in the room, we can wait a litle bit longer before getting the next data point
current_sleep_time += SLEEP_GRANULARITY;
if(current_sleep_time > SLEEP_MAX_DURATION){
current_sleep_time = SLEEP_MAX_DURATION;
}else{}
}
k_sleep(K_MINUTES(current_sleep_time));
}
return error_unknown; // should never return
}

38
nodes/src/supervisor.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef SUPERVISOR_H
#define SUPERVISOR_H
#include <zephyr/kernel.h>
#include "error_code.h"
#include "ble_advertiser.h"
#include "window_status.h"
#include "thermometer.h"
#include "hygrometer.h"
#include "co2_level.h"
#include "battery_percent.h"
extern const int SLEEP_GRANULARITY; // [min]
extern const int SLEEP_MIN_DURATION; // [min]
extern const int SLEEP_MAX_DURATION; // [min]
/**
* @brief thread function to run the supervisor in zephyr environment
*/
void thread_supervisor();
/**
* @brief init the supervisor, and thus the complete plein_de_ee application
* @return init_failed upon any error occurring during the initialisation of any module
* @return success otherwise
*/
enum error_code supervisor_init();
/**
* @brief exexcute the plein_de_eeeeeee application
* @return error_unknown
* @note should never return
*/
enum error_code supervisor_run();
#endif //SUPERVISOR_H

23
nodes/src/thermometer.c Normal file
View File

@@ -0,0 +1,23 @@
#include "thermometer.h"
static const struct device* dev = DEVICE_DT_GET_ONE(st_hts221);
enum error_code thermo_init(){
enum error_code ret = init_failed;
if(device_is_ready(dev)){
ret = success;
}else{}
return ret;
}
enum error_code thermo_get_value(int* holder){
enum error_code ret = read_failed;
struct sensor_value temp;
if( (sensor_sample_fetch(dev) >= 0) &&
(sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp) >= 0)
){
*holder = sensor_value_to_deci(&temp);
ret = success;
}else{}
return ret;
}

26
nodes/src/thermometer.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef THERMOMETER_H
#define THERMOMETER_H
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include "error_code.h"
/**
* @brief init the thermometer module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code thermo_init();
/**
* @brief Retrieve the temperature and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
*/
enum error_code thermo_get_value(int* holder);
#endif //THERMOMETER_H

17
nodes/src/window_status.c Normal file
View File

@@ -0,0 +1,17 @@
#include "window_status.h"
static const struct gpio_dt_spec window_switch = GPIO_DT_SPEC_GET(DT_NODELABEL(window_switch), gpios);
enum error_code window_init(){
enum error_code ret = init_failed;
if(gpio_is_ready_dt(&window_switch) && (0 == gpio_pin_configure_dt(&window_switch, GPIO_INPUT))){
ret = success;
}else{}
return ret;
}
enum error_code window_get_value(enum window_status* holder){
// active high
*holder = (gpio_pin_get_dt(&window_switch) ? closed : open);
return success;
}

31
nodes/src/window_status.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef WINDOW_STATUS_H
#define WINDOW_STATUS_H
#include <zephyr/drivers/gpio.h>
#include "error_code.h"
enum window_status{
closed = 0, // window is closed or sensor is not connected
open, // window is opened
unknown, // window opening status could not be read
windows_status_last, // iteration purpose
};
/**
* @brief init the window opening status module
* @return init_failed upon any error during init
* @return success otherwise
*/
enum error_code window_init();
/**
* @brief Retrieve the window opening status and stores it in the given parameter
* @param[out] holder : pointer where the measurement will be stored
* @return read_failed upon any error occuring during measurement
* @return success otherwise
* @note the content of holder should not be trusted upon returning something else than success
*/
enum error_code window_get_value(enum window_status* holder);
#endif //WINDOW_STATUS_

View File