commit 82c0bcc836972e0848d259c1719af8218709d754 Author: Nigreon Date: Fri Sep 1 03:24:45 2023 +0200 Initial commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..74707e5 --- /dev/null +++ b/CMakeLists.txt @@ -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}) diff --git a/boards/nrf52832_outdoor.overlay b/boards/nrf52832_outdoor.overlay new file mode 100644 index 0000000..7fa4ed5 --- /dev/null +++ b/boards/nrf52832_outdoor.overlay @@ -0,0 +1,8 @@ +/ { + vbatt { + compatible = "voltage-divider"; + io-channels = <&adc 4>; + output-ohms = <4600>; + full-ohms = <(4600 + 3230)>; + }; +}; diff --git a/prj.conf b/prj.conf new file mode 100644 index 0000000..fd6e606 --- /dev/null +++ b/prj.conf @@ -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 diff --git a/src/battery.c b/src/battery.c new file mode 100644 index 0000000..e2c87c8 --- /dev/null +++ b/src/battery.c @@ -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 +#include +#include + +#include +#include +#include +#include +#include +#include + +#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)); +} diff --git a/src/battery.h b/src/battery.h new file mode 100644 index 0000000..2da50ab --- /dev/null +++ b/src/battery.h @@ -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_ */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..1ca7252 --- /dev/null +++ b/src/main.c @@ -0,0 +1,543 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#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; + }