Initial commit
commit
82c0bcc836
|
@ -0,0 +1,8 @@
|
||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
set(BOARD nrf52832_outdoor)
|
||||||
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
|
||||||
|
project(tuxoutdoor)
|
||||||
|
|
||||||
|
FILE(GLOB app_sources src/*.c*)
|
||||||
|
target_sources(app PRIVATE ${app_sources})
|
|
@ -0,0 +1,8 @@
|
||||||
|
/ {
|
||||||
|
vbatt {
|
||||||
|
compatible = "voltage-divider";
|
||||||
|
io-channels = <&adc 4>;
|
||||||
|
output-ohms = <4600>;
|
||||||
|
full-ohms = <(4600 + 3230)>;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,37 @@
|
||||||
|
CONFIG_BT=y
|
||||||
|
#CONFIG_BT_SMP=y
|
||||||
|
#CONFIG_BT_SIGNING=y
|
||||||
|
CONFIG_BT_PERIPHERAL=y
|
||||||
|
#CONFIG_BT_PRIVACY=n # Random Address
|
||||||
|
#CONFIG_BT_FIXED_PASSKEY=y
|
||||||
|
CONFIG_BT_DEVICE_NAME="TuxOutdoor"
|
||||||
|
#CONFIG_BT_DEVICE_APPEARANCE=833
|
||||||
|
#CONFIG_BT_DEVICE_NAME_DYNAMIC=y
|
||||||
|
#CONFIG_BT_KEYS_OVERWRITE_OLDEST=y
|
||||||
|
#CONFIG_BT_SETTINGS=y
|
||||||
|
|
||||||
|
#CONFIG_FLASH=y
|
||||||
|
#CONFIG_FLASH_PAGE_LAYOUT=y
|
||||||
|
#CONFIG_FLASH_MAP=y
|
||||||
|
#CONFIG_NVS=y
|
||||||
|
#CONFIG_SETTINGS=y
|
||||||
|
|
||||||
|
CONFIG_LOG=y
|
||||||
|
CONFIG_LOG_DEFAULT_LEVEL=3
|
||||||
|
CONFIG_LOG_MAX_LEVEL=3
|
||||||
|
CONFIG_USE_SEGGER_RTT=y
|
||||||
|
CONFIG_LOG_BACKEND_RTT=y
|
||||||
|
CONFIG_UART_CONSOLE=n
|
||||||
|
CONFIG_RTT_CONSOLE=y
|
||||||
|
CONFIG_STDOUT_CONSOLE=y
|
||||||
|
|
||||||
|
CONFIG_ADC=y
|
||||||
|
CONFIG_GPIO=y
|
||||||
|
CONFIG_SERIAL=y
|
||||||
|
CONFIG_UART_INTERRUPT_DRIVEN=y
|
||||||
|
|
||||||
|
CONFIG_BOARD_ENABLE_DCDC=n
|
||||||
|
CONFIG_HEAP_MEM_POOL_SIZE=512
|
||||||
|
|
||||||
|
CONFIG_NEWLIB_LIBC=y
|
||||||
|
CONFIG_NEWLIB_LIBC_NANO=y
|
|
@ -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(VBATT, okay)
|
||||||
|
.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(VBATT, okay)
|
||||||
|
.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 = ÷r_config;
|
||||||
|
const struct io_channel_config *iocp = &cfg->io_channel;
|
||||||
|
const struct gpio_dt_spec *gcp = &cfg->power_gpios;
|
||||||
|
struct divider_data *ddp = ÷r_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(const struct device *arg)
|
||||||
|
{
|
||||||
|
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 = ÷r_config.power_gpios;
|
||||||
|
|
||||||
|
rc = 0;
|
||||||
|
if (gcp->port) {
|
||||||
|
rc = gpio_pin_set_dt(gcp, enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t battery_sample(void)
|
||||||
|
{
|
||||||
|
int16_t rc = -ENOENT;
|
||||||
|
|
||||||
|
if (battery_ok) {
|
||||||
|
struct divider_data *ddp = ÷r_data;
|
||||||
|
const struct divider_config *dcp = ÷r_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t battery_level_pptt(uint16_t 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));
|
||||||
|
}
|
|
@ -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.
|
||||||
|
*/
|
||||||
|
int16_t 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.
|
||||||
|
*/
|
||||||
|
uint16_t battery_level_pptt(uint16_t batt_mV,
|
||||||
|
const struct battery_level_point *curve);
|
||||||
|
|
||||||
|
#endif /* APPLICATION_BATTERY_H_ */
|
|
@ -0,0 +1,543 @@
|
||||||
|
/* main.c - Application main entry point */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015-2016 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/errno.h>
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <zephyr/sys/printk.h>
|
||||||
|
#include <zephyr/sys/byteorder.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#include <zephyr/settings/settings.h>
|
||||||
|
|
||||||
|
#include <zephyr/bluetooth/bluetooth.h>
|
||||||
|
#include <zephyr/bluetooth/hci.h>
|
||||||
|
#include <zephyr/bluetooth/conn.h>
|
||||||
|
#include <zephyr/bluetooth/uuid.h>
|
||||||
|
#include <zephyr/bluetooth/gatt.h>
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
#include <zephyr/drivers/sensor.h>
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
|
#include <zephyr/drivers/uart.h>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "battery.h"
|
||||||
|
#include "hal/nrf_gpiote.h"
|
||||||
|
|
||||||
|
static K_SEM_DEFINE(ble_init_ok, 0, 1);
|
||||||
|
static K_FIFO_DEFINE(fifo_adv_data);
|
||||||
|
struct bthome_adv_t {
|
||||||
|
void *fifo_reserved;
|
||||||
|
uint8_t type;
|
||||||
|
int16_t data_s16;
|
||||||
|
uint16_t data_u16;
|
||||||
|
uint8_t data_u8;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define UPDATE_MIN(MIN, SAMPLE) if (SAMPLE < MIN) { MIN = SAMPLE; }
|
||||||
|
#define UPDATE_MAX(MAX, SAMPLE) if (SAMPLE > MAX) { MAX = SAMPLE; }
|
||||||
|
#define UPDATE_MIN_MAX(MIN, MAX, SAMPLE) { UPDATE_MIN(MIN, SAMPLE); UPDATE_MAX(MAX, SAMPLE); }
|
||||||
|
|
||||||
|
/*
|
||||||
|
0x01: Battery
|
||||||
|
0x02: Temperature
|
||||||
|
0x0c: Voltage
|
||||||
|
0x0d: PM2.5
|
||||||
|
0x0e: PM10
|
||||||
|
*/
|
||||||
|
static uint8_t mfg_bthome_data[] = { 0xd2, 0xfc, 0x40, 0x01, 0x00, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x0e, 0x00, 0x00 };
|
||||||
|
uint8_t bthome_datapos[] = { 4, 6, 9, 12, 15 };
|
||||||
|
|
||||||
|
enum captor_type { BATTERY, TEMPERATURE, VOLTAGE, PM25, PM10 };
|
||||||
|
|
||||||
|
struct captor_wait {
|
||||||
|
enum captor_type captor;
|
||||||
|
uint16_t wait;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct captor_wait params[] = { {BATTERY, 120 }, {TEMPERATURE, 60}, {PM25, 60} };
|
||||||
|
|
||||||
|
|
||||||
|
static const struct bt_data ad[] = {
|
||||||
|
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
|
||||||
|
BT_DATA(BT_DATA_SVC_DATA16, mfg_bthome_data, 17),
|
||||||
|
};
|
||||||
|
|
||||||
|
#define GPIO_PORT DT_NODELABEL(gpio0)
|
||||||
|
|
||||||
|
#define STACKSIZE 2048
|
||||||
|
#define PRIORITY 7
|
||||||
|
|
||||||
|
static const struct battery_level_point battery_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 },
|
||||||
|
};
|
||||||
|
|
||||||
|
#define UART_PORT DT_NODELABEL(uart0)
|
||||||
|
static const struct device *const uart_dev = DEVICE_DT_GET(UART_PORT);
|
||||||
|
|
||||||
|
uint8_t SDS_SLEEPCMD[19] = {
|
||||||
|
0xAA, 0xB4, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x05, 0xAB
|
||||||
|
};
|
||||||
|
uint8_t SDS_WAKEUPCMD[19] = {
|
||||||
|
0xAA, 0xB4, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x06, 0xAB
|
||||||
|
};
|
||||||
|
|
||||||
|
static K_FIFO_DEFINE(fifo_pms);
|
||||||
|
struct pms_data_t {
|
||||||
|
void *fifo_reserved;
|
||||||
|
uint32_t ts;
|
||||||
|
char data[10];
|
||||||
|
};
|
||||||
|
|
||||||
|
//#define UART_DEVICE_NODE DT_CHOSEN(zephyr_shell_uart)
|
||||||
|
|
||||||
|
|
||||||
|
/* static void connected(struct bt_conn *conn, uint8_t err)
|
||||||
|
{
|
||||||
|
if (err) {
|
||||||
|
printk("Connection failed (err 0x%02x)\n", err);
|
||||||
|
} else {
|
||||||
|
printk("Connected\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
||||||
|
{
|
||||||
|
printk("Disconnected (reason 0x%02x)\n", reason);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
uint32_t getTs()
|
||||||
|
{
|
||||||
|
uint32_t ts = (k_uptime_get_32()/1000);
|
||||||
|
// LOG_INF("getTs: %d", ts);
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t findsmallestwait()
|
||||||
|
{
|
||||||
|
uint16_t minval = UINT16_MAX;
|
||||||
|
uint32_t ts = getTs();
|
||||||
|
for(int i = 0; i < sizeof(params) / sizeof(params[0]); i++)
|
||||||
|
{
|
||||||
|
if(params[i].wait != 0) {
|
||||||
|
//LOG_INF("%s %d", params[i].captor[j].captor, params[i].captor[j].wait);
|
||||||
|
uint32_t wait = (uint32_t)params[i].wait;
|
||||||
|
if((uint16_t)(wait-(ts % wait)) < minval) { minval = (uint16_t)(wait-(ts % wait)); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minval;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t findiftoprobe(enum captor_type captorp, bool record)
|
||||||
|
{
|
||||||
|
uint32_t ts = getTs();
|
||||||
|
//LOG_INF("findiftoprobe: %s", captorp);
|
||||||
|
for(int i = 0; i < sizeof(params) / sizeof(params[0]); i++)
|
||||||
|
{
|
||||||
|
if(captorp == params[i].captor) {
|
||||||
|
return (uint16_t)(ts % (uint32_t)params[i].wait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UINT16_MAX;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void bt_ready(void)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
printk("Bluetooth initialized\n");
|
||||||
|
|
||||||
|
/*if (IS_ENABLED(CONFIG_SETTINGS)) {
|
||||||
|
settings_load();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
|
||||||
|
if (err) {
|
||||||
|
printk("Advertising failed to start (err %d)\n", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printk("Advertising successfully started\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
|
||||||
|
{
|
||||||
|
char addr[BT_ADDR_LE_STR_LEN];
|
||||||
|
|
||||||
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
||||||
|
|
||||||
|
printk("Passkey for %s: %06u\n", addr, passkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void auth_cancel(struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
char addr[BT_ADDR_LE_STR_LEN];
|
||||||
|
|
||||||
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
|
||||||
|
|
||||||
|
printk("Pairing cancelled: %s\n", addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct bt_conn_auth_cb auth_cb = {
|
||||||
|
.passkey_display = auth_passkey_display,
|
||||||
|
.passkey_entry = NULL,
|
||||||
|
.cancel = auth_cancel,
|
||||||
|
};*/
|
||||||
|
|
||||||
|
bool test_bthome_data_set()
|
||||||
|
{
|
||||||
|
for(int i = 0; i < sizeof(bthome_datapos); i++)
|
||||||
|
{
|
||||||
|
if(mfg_bthome_data[bthome_datapos[i]] == 0x00)
|
||||||
|
{
|
||||||
|
//printk("BTHome POS %d empty\n", bthome_datapos[i]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void publish_adv_thread()
|
||||||
|
{
|
||||||
|
bool adv_activate = false;
|
||||||
|
|
||||||
|
k_sem_take(&ble_init_ok, K_FOREVER);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
struct bthome_adv_t *request = k_fifo_get(&fifo_adv_data, K_FOREVER);
|
||||||
|
if(request->type == VOLTAGE)
|
||||||
|
{
|
||||||
|
mfg_bthome_data[10] = request->data_u16 >> 8;
|
||||||
|
mfg_bthome_data[9] = request->data_u16;
|
||||||
|
} else if(request->type == BATTERY)
|
||||||
|
{
|
||||||
|
mfg_bthome_data[4] = request->data_u8;
|
||||||
|
} else if(request->type == TEMPERATURE)
|
||||||
|
{
|
||||||
|
mfg_bthome_data[7] = request->data_s16 >> 8;
|
||||||
|
mfg_bthome_data[6] = request->data_s16;
|
||||||
|
} else if(request->type == PM25)
|
||||||
|
{
|
||||||
|
mfg_bthome_data[13] = request->data_u16 >> 8;
|
||||||
|
mfg_bthome_data[12] = request->data_u16;
|
||||||
|
} else if(request->type == PM10)
|
||||||
|
{
|
||||||
|
mfg_bthome_data[16] = request->data_u16 >> 8;
|
||||||
|
mfg_bthome_data[15] = request->data_u16;
|
||||||
|
}
|
||||||
|
if(test_bthome_data_set() == true)
|
||||||
|
{
|
||||||
|
if(adv_activate == false)
|
||||||
|
{
|
||||||
|
bt_ready();
|
||||||
|
adv_activate = true;
|
||||||
|
} else {
|
||||||
|
bt_le_adv_update_data(ad, ARRAY_SIZE(ad), NULL, 0);
|
||||||
|
printk("Update ADV\n");
|
||||||
|
//k_msleep(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k_free(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
K_THREAD_DEFINE(publish_adv_thread_id, STACKSIZE, publish_adv_thread, NULL, NULL, NULL, PRIORITY, 0, 0);
|
||||||
|
|
||||||
|
static void work_battery_handler(struct k_work *work)
|
||||||
|
{
|
||||||
|
struct bthome_adv_t bthome_adv;
|
||||||
|
bthome_adv.type = VOLTAGE;
|
||||||
|
bthome_adv.data_u16 = (uint16_t) battery_sample();
|
||||||
|
size_t size = sizeof(struct bthome_adv_t);
|
||||||
|
char *mem_ptr = k_malloc(size);
|
||||||
|
memcpy(mem_ptr, &bthome_adv, size);
|
||||||
|
k_fifo_put(&fifo_adv_data, mem_ptr);
|
||||||
|
|
||||||
|
bthome_adv.type = BATTERY;
|
||||||
|
bthome_adv.data_u8 = round(battery_level_pptt(bthome_adv.data_u16, battery_levels)/100);
|
||||||
|
char *mem_ptr2 = k_malloc(size);
|
||||||
|
memcpy(mem_ptr2, &bthome_adv, size);
|
||||||
|
k_fifo_put(&fifo_adv_data, mem_ptr2);
|
||||||
|
printk("Battery: %d mV (%d%%)\n",bthome_adv.data_u16, bthome_adv.data_u8);
|
||||||
|
}
|
||||||
|
K_WORK_DEFINE(work_battery, work_battery_handler);
|
||||||
|
|
||||||
|
static void work_temperature_handler(struct k_work *work)
|
||||||
|
{
|
||||||
|
struct bthome_adv_t bthome_adv;
|
||||||
|
bthome_adv.type = TEMPERATURE;
|
||||||
|
bthome_adv.data_s16 = 2512;
|
||||||
|
size_t size = sizeof(struct bthome_adv_t);
|
||||||
|
char *mem_ptr = k_malloc(size);
|
||||||
|
memcpy(mem_ptr, &bthome_adv, size);
|
||||||
|
k_fifo_put(&fifo_adv_data, mem_ptr);
|
||||||
|
printk("Temperature: %d°\n",bthome_adv.data_s16);
|
||||||
|
}
|
||||||
|
K_WORK_DEFINE(work_temperature, work_temperature_handler);
|
||||||
|
|
||||||
|
//char pm_measure_data[10];
|
||||||
|
struct pms_data_t pms_data;
|
||||||
|
int pm_measure_position = 0;
|
||||||
|
bool pms_enable = false;
|
||||||
|
bool pms_warming = false;
|
||||||
|
#define WARMUPTIME_SDS_MS 15000
|
||||||
|
#define READINGTIME_SDS_MS 5000
|
||||||
|
|
||||||
|
|
||||||
|
void send_uart_cmd(uint8_t *buf)
|
||||||
|
{
|
||||||
|
//int msg_len = strlen(buf);
|
||||||
|
printk("Send to uart\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < 19; i++) {
|
||||||
|
uart_poll_out(uart_dev, buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void work_pms_handler(struct k_work *work)
|
||||||
|
{
|
||||||
|
uint32_t ts_start_measure;
|
||||||
|
send_uart_cmd(SDS_WAKEUPCMD);
|
||||||
|
pms_warming = true;
|
||||||
|
pms_enable = true;
|
||||||
|
k_msleep(WARMUPTIME_SDS_MS);
|
||||||
|
pms_warming = false;
|
||||||
|
ts_start_measure = getTs();
|
||||||
|
|
||||||
|
struct pms_data_t *pms_data_read;
|
||||||
|
|
||||||
|
uint32_t sds_pm10_sum = 0;
|
||||||
|
uint32_t sds_pm25_sum = 0;
|
||||||
|
uint32_t sds_val_count = 0;
|
||||||
|
uint32_t sds_pm10_max = 0;
|
||||||
|
uint32_t sds_pm10_min = 20000;
|
||||||
|
uint32_t sds_pm25_max = 0;
|
||||||
|
uint32_t sds_pm25_min = 20000;
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
pms_data_read = k_fifo_get(&fifo_pms, K_MSEC(READINGTIME_SDS_MS));
|
||||||
|
if(pms_data_read != NULL)
|
||||||
|
{
|
||||||
|
if(getTs() < (ts_start_measure + (READINGTIME_SDS_MS/1000)))
|
||||||
|
{
|
||||||
|
//printk("Parse PMS %d %d\n", ts_start_measure, getTs());
|
||||||
|
uint32_t pm10_serial = 0;
|
||||||
|
uint32_t pm25_serial = 0;
|
||||||
|
int checksum_is = pms_data_read->data[2] + pms_data_read->data[3] + pms_data_read->data[4] + pms_data_read->data[5] + pms_data_read->data[6] + pms_data_read->data[7];
|
||||||
|
if(pms_data_read->data[8] == (checksum_is % 256) && pms_data_read->data[0] == 170 && pms_data_read->data[1] == 192 && pms_data_read->data[9] == 171)
|
||||||
|
{
|
||||||
|
|
||||||
|
pm25_serial = pms_data_read->data[2] | pms_data_read->data[3] << 8;
|
||||||
|
pm10_serial = pms_data_read->data[4] | pms_data_read->data[5] << 8;
|
||||||
|
sds_pm10_sum += pm10_serial;
|
||||||
|
sds_pm25_sum += pm25_serial;
|
||||||
|
UPDATE_MIN_MAX(sds_pm10_min, sds_pm10_max, pm10_serial)
|
||||||
|
UPDATE_MIN_MAX(sds_pm25_min, sds_pm25_max, pm25_serial)
|
||||||
|
|
||||||
|
sds_val_count += 1;
|
||||||
|
|
||||||
|
|
||||||
|
printk("p10: %d p25: %d\n", pm10_serial, pm25_serial);
|
||||||
|
}
|
||||||
|
k_free(pms_data_read);
|
||||||
|
} else {
|
||||||
|
send_uart_cmd(SDS_SLEEPCMD);
|
||||||
|
pms_enable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (sds_val_count > 2) {
|
||||||
|
sds_pm10_sum = sds_pm10_sum - sds_pm10_min - sds_pm10_max;
|
||||||
|
sds_pm25_sum = sds_pm25_sum - sds_pm25_min - sds_pm25_max;
|
||||||
|
sds_val_count = sds_val_count - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sds_val_count > 0) {
|
||||||
|
|
||||||
|
struct bthome_adv_t bthome_adv;
|
||||||
|
bthome_adv.type = PM25;
|
||||||
|
bthome_adv.data_u16 = sds_pm25_sum / (sds_val_count * 10);
|
||||||
|
size_t size = sizeof(struct bthome_adv_t);
|
||||||
|
char *mem_ptr = k_malloc(size);
|
||||||
|
memcpy(mem_ptr, &bthome_adv, size);
|
||||||
|
k_fifo_put(&fifo_adv_data, mem_ptr);
|
||||||
|
printk("PM25: %d\n",bthome_adv.data_u16);
|
||||||
|
bthome_adv.type = PM10;
|
||||||
|
bthome_adv.data_u16 = sds_pm10_sum / (sds_val_count * 10);
|
||||||
|
char *mem_ptr2 = k_malloc(size);
|
||||||
|
memcpy(mem_ptr2, &bthome_adv, size);
|
||||||
|
k_fifo_put(&fifo_adv_data, mem_ptr2);
|
||||||
|
printk("PM10: %d\n",bthome_adv.data_u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
K_WORK_DEFINE(work_pms, work_pms_handler);
|
||||||
|
|
||||||
|
void serial_cb(const struct device *dev, void *user_data)
|
||||||
|
{
|
||||||
|
uint8_t c;
|
||||||
|
|
||||||
|
if (!uart_irq_update(uart_dev)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uart_irq_rx_ready(uart_dev)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read until FIFO empty */
|
||||||
|
while (uart_fifo_read(uart_dev, &c, 1) == 1) {
|
||||||
|
if(pms_enable == true && pms_warming == false)
|
||||||
|
{
|
||||||
|
//printk("UART char received: 0x%x\n", c);
|
||||||
|
if(c == 0xaa) { pm_measure_position = 0; pms_data.ts = getTs(); }
|
||||||
|
if(pm_measure_position >= 0) {
|
||||||
|
pms_data.data[pm_measure_position] = c;
|
||||||
|
pm_measure_position = pm_measure_position + 1;
|
||||||
|
}
|
||||||
|
if(pm_measure_position == 10) {
|
||||||
|
size_t size = sizeof(struct pms_data_t);
|
||||||
|
char *mem_ptr = k_malloc(size);
|
||||||
|
memcpy(mem_ptr, &pms_data, size);
|
||||||
|
k_fifo_put(&fifo_pms, mem_ptr);
|
||||||
|
}
|
||||||
|
//} else if(pms_enable == false) {
|
||||||
|
// send_uart_cmd(SDS_SLEEPCMD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
printk("Hello, It's TuxOutdooor\n");
|
||||||
|
int err;
|
||||||
|
|
||||||
|
// bt_passkey_set(192847);
|
||||||
|
|
||||||
|
err = bt_enable(NULL);
|
||||||
|
if (err) {
|
||||||
|
printk("Bluetooth init failed (err %d)\n", err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_sem_give(&ble_init_ok);
|
||||||
|
|
||||||
|
const struct device *dev = DEVICE_DT_GET(GPIO_PORT);
|
||||||
|
|
||||||
|
gpio_pin_configure(dev, 20, GPIO_OUTPUT);
|
||||||
|
gpio_pin_set(dev, 20, 0);
|
||||||
|
//bt_conn_auth_cb_register(&auth_cb);
|
||||||
|
//static const struct device *const uart_dev = DEVICE_DT_GET(UART_DEVICE_NODE);
|
||||||
|
|
||||||
|
if (!device_is_ready(uart_dev)) {
|
||||||
|
printk("UART device not found!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = uart_irq_callback_user_data_set(uart_dev, serial_cb, NULL);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
if (ret == -ENOTSUP) {
|
||||||
|
printk("Interrupt-driven UART API support not enabled\n");
|
||||||
|
} else if (ret == -ENOSYS) {
|
||||||
|
printk("UART device does not support interrupt-driven API\n");
|
||||||
|
} else {
|
||||||
|
printk("Error setting UART callback: %d\n", ret);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uart_irq_rx_enable(uart_dev);
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
if(findiftoprobe(BATTERY, true) < 5)
|
||||||
|
{
|
||||||
|
k_work_submit(&work_battery);
|
||||||
|
}
|
||||||
|
if(findiftoprobe(TEMPERATURE, true) < 5)
|
||||||
|
{
|
||||||
|
k_work_submit(&work_temperature);
|
||||||
|
}
|
||||||
|
if(findiftoprobe(PM25, true) < 5)
|
||||||
|
{
|
||||||
|
k_work_submit(&work_pms);
|
||||||
|
}
|
||||||
|
k_msleep(1000*findsmallestwait());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*int len = 0;
|
||||||
|
int pm10_serial = 0;
|
||||||
|
int pm25_serial = 0;
|
||||||
|
int checksum_is;
|
||||||
|
int checksum_ok = 0;
|
||||||
|
int error = 1;
|
||||||
|
float p10 = 0.0;
|
||||||
|
float p25 = 0.0;
|
||||||
|
for(;;) {
|
||||||
|
char value;
|
||||||
|
if(uart_poll_in(uart_dev, &value) == 0)
|
||||||
|
{
|
||||||
|
printk("UART char received: 0x%x\n", value);
|
||||||
|
printk("len %d\n", len);
|
||||||
|
switch (len) {
|
||||||
|
case (0): if (value != 170) { len = -1; }; break;
|
||||||
|
case (1): if (value != 192) { len = -1; }; break;
|
||||||
|
case (2): pm25_serial = value; checksum_is = value; break;
|
||||||
|
case (3): pm25_serial += (value << 8); checksum_is += value; break;
|
||||||
|
case (4): pm10_serial = value; checksum_is += value; break;
|
||||||
|
case (5): pm10_serial += (value << 8); checksum_is += value; break;
|
||||||
|
case (6): checksum_is += value; break;
|
||||||
|
case (7): checksum_is += value; break;
|
||||||
|
case (8): if (value == (checksum_is % 256)) { checksum_ok = 1; } else { len = -1; }; break;
|
||||||
|
case (9): if (value != 171) { len = -1; }; break;
|
||||||
|
}
|
||||||
|
len++;
|
||||||
|
if (len == 10 && checksum_ok == 1) {
|
||||||
|
p10 = (float)pm10_serial/10.0;
|
||||||
|
p25 = (float)pm25_serial/10.0;
|
||||||
|
len = 0; checksum_ok = 0; pm10_serial = 0.0; pm25_serial = 0.0; checksum_is = 0;
|
||||||
|
error = 0;
|
||||||
|
printk("p10: %f p25: %f\n", p10, p25);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//printk("No UART Data\n");
|
||||||
|
k_msleep(100);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue