├── local.mk ├── .gitignore ├── sha3.h ├── Makefile ├── FreeRTOSConfig.h ├── web.h ├── sht21.h ├── i2c.h ├── push.h ├── bmp180.h ├── leds.h ├── bme280.h ├── content ├── bufsize.html ├── index.html ├── plot.html ├── plot.js ├── config.html └── smoothie.js ├── ds3231.h ├── pms.h ├── i2c.c ├── flash.h ├── buffer.h ├── config.h ├── config.c ├── leds.c ├── bmp180.c ├── bme280.c ├── ds3231.c ├── README.md ├── sht21.c ├── LICENSE ├── pms.c ├── sha3.c ├── buffer.c ├── push.c └── flash.c /local.mk: -------------------------------------------------------------------------------- 1 | FLASH_SIZE ?= 32 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !local.mk 2 | build 3 | firmware 4 | -------------------------------------------------------------------------------- /sha3.h: -------------------------------------------------------------------------------- 1 | extern void FIPS202_SHA3_224(const unsigned char *input, unsigned int inputByteLen, unsigned char *output); 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROGRAM=oaq 2 | EXTRA_COMPONENTS=extras/stdin_uart_interrupt extras/i2c extras/bmp180 extras/bmp280 extras/ds3231 extras/dhcpserver extras/wificfg 3 | 4 | EXTRA_CFLAGS+=-DTCPIP_THREAD_STACKSIZE=384 5 | 6 | include ../../common.mk 7 | -------------------------------------------------------------------------------- /FreeRTOSConfig.h: -------------------------------------------------------------------------------- 1 | /* The serial driver depends on counting semaphores */ 2 | #define configUSE_COUNTING_SEMAPHORES 1 3 | 4 | #define configCHECK_FOR_STACK_OVERFLOW 2 5 | 6 | /* IDLE task. */ 7 | #define configMINIMAL_STACK_SIZE 192 8 | 9 | /* Debugging */ 10 | #define configUSE_TRACE_FACILITY 1 11 | #define configGENERATE_RUN_TIME_STATS 1 12 | #define portGET_RUN_TIME_COUNTER_VALUE() (RTC.COUNTER) 13 | #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() {} 14 | 15 | /* Use the defaults for everything else */ 16 | #include_next 17 | -------------------------------------------------------------------------------- /web.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Web interface. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/ 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 16 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS WITH THE SOFTWARE. 20 | * 21 | */ 22 | 23 | void init_web(); 24 | -------------------------------------------------------------------------------- /sht21.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the SHT2x temperature and humidity sensor. 3 | * 4 | * Licensed under the Apache License, Version 2.0, January 2004 (the 5 | * "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/ 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 10 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 12 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 13 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 14 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 16 | * DEALINGS WITH THE SOFTWARE. 17 | * 18 | */ 19 | 20 | void init_sht2x(); 21 | 22 | bool sht2x_temp_rh(uint32_t *counter, float *temp, float *rh); 23 | -------------------------------------------------------------------------------- /i2c.h: -------------------------------------------------------------------------------- 1 | /* 2 | * I2C support. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | #define I2C_BUS 0 23 | 24 | extern SemaphoreHandle_t i2c_sem; 25 | 26 | void init_i2c(); 27 | -------------------------------------------------------------------------------- /push.h: -------------------------------------------------------------------------------- 1 | /* 2 | * HTTP-Post the flash sectors to a server. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/ 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 16 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS WITH THE SOFTWARE. 20 | * 21 | */ 22 | 23 | void init_post(); 24 | 25 | extern TaskHandle_t post_data_task; 26 | -------------------------------------------------------------------------------- /bmp180.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the BMP180 pressure sensor. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | void init_bmp180(); 23 | 24 | bool bmp180_temp_press(uint32_t *counter, float *temp, float *press); 25 | -------------------------------------------------------------------------------- /leds.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LED support for blinking progress. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | void init_blink(); 23 | void blink_green(); 24 | void blink_blue(); 25 | void blink_red(); 26 | void blink_white(); 27 | -------------------------------------------------------------------------------- /bme280.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the BME280 pressure sensor. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | void init_bme280(); 23 | 24 | bool bme280_temp_press_rh(uint32_t *counter, float *temp, float *press, float *rh); 25 | -------------------------------------------------------------------------------- /content/bufsize.html: -------------------------------------------------------------------------------- 1 | "" 2 | "" 3 | "" 4 | "" 5 | "" 6 | "" 7 | "" 8 | "" 16 | "
" 17 | "
" 18 | "Buffer size request" 19 | "
" 20 | "
" 21 | "
" 23 | "
" 24 | "
" 25 | "

 

" 26 | "" 27 | "
" 28 | "" 29 | -------------------------------------------------------------------------------- /ds3231.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the DS3231 real time clock. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | #include 23 | #include "i2c/i2c.h" 24 | 25 | extern i2c_dev_t ds3231_dev; 26 | 27 | void ds3231_note_time(time_t time); 28 | void init_ds3231(); 29 | 30 | bool ds3231_time_temp(uint32_t *counter, struct tm *time, float *temp); 31 | -------------------------------------------------------------------------------- /pms.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the Plantower PMS3003 and PMS5003. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | void init_pms(); 23 | 24 | bool pms_last_data(uint32_t *counter, uint16_t *pm1a, uint16_t *pm25a, uint16_t *pm10a, uint16_t *pm1b, uint16_t *pm25b, uint16_t *pm10b, uint16_t *c1, uint16_t *c2, uint16_t *c3, uint16_t *c4, uint16_t *c5, uint16_t *c6, uint16_t *r1); 25 | -------------------------------------------------------------------------------- /i2c.c: -------------------------------------------------------------------------------- 1 | /* 2 | * I2C support. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "FreeRTOS.h" 29 | #include "task.h" 30 | #include "semphr.h" 31 | #include "i2c/i2c.h" 32 | #include "i2c.h" 33 | #include "config.h" 34 | 35 | /* To synchronize access to the I2C interface. */ 36 | SemaphoreHandle_t i2c_sem; 37 | 38 | void init_i2c() 39 | { 40 | i2c_init(I2C_BUS, param_i2c_scl, param_i2c_sda, I2C_FREQ_100K); 41 | i2c_sem = xSemaphoreCreateMutex(); 42 | } 43 | -------------------------------------------------------------------------------- /flash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Write the memory resident buffers to flash. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | uint32_t get_buffer_to_post(uint32_t *index, uint32_t *start, uint8_t *buf); 23 | void note_buffer_posted(uint32_t index, uint32_t size); 24 | uint32_t maybe_buffer_to_post(void); 25 | void clear_maybe_buffer_to_post(void); 26 | 27 | uint32_t init_flash(void); 28 | void flash_data(void *pvParameters); 29 | 30 | extern TaskHandle_t flash_data_task; 31 | 32 | uint32_t get_buffer_size(uint32_t requested_index, uint32_t *index, uint32_t *next_index, bool *sealed); 33 | bool get_buffer_range(uint32_t index, uint32_t start, uint32_t end, uint8_t *buf); 34 | bool erase_flash_data(void); 35 | -------------------------------------------------------------------------------- /content/index.html: -------------------------------------------------------------------------------- 1 | "" 2 | "" 3 | "" 4 | "" 5 | "", 6 | "" 7 | "" 8 | "" 9 | "" 10 | "" 11 | "", 20 | "
" 21 | "
" 22 | "Log a text message" 23 | "

Short text messages can be inserted into the data log. The web browser time is included, and an empty message may be submitted to just log a time stamped event.

" 24 | "
" 25 | "" 26 | "    " 27 | "" 28 | "" 29 | "" 30 | "
" 31 | "
" 32 | "
" 33 | "" 34 | -------------------------------------------------------------------------------- /buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Memory resident (RAM) buffer support. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | bool get_buffer_logging(void); 23 | bool set_buffer_logging(bool enable); 24 | uint32_t get_buffer_to_write(uint8_t *buf, uint32_t *start); 25 | void note_buffer_written(uint32_t index, uint32_t size); 26 | uint32_t dbuf_head_index(); 27 | uint32_t dbuf_append(uint32_t index, uint16_t code, uint8_t *data, uint32_t size, 28 | int low_res_time); 29 | void reset_dbuf(void); 30 | 31 | uint32_t emit_leb128(uint8_t *buf, uint32_t start, uint64_t v); 32 | uint32_t emit_leb128_signed(uint8_t *buf, uint32_t start, int64_t v); 33 | 34 | 35 | /* Plantower PMS3003 */ 36 | #define DBUF_EVENT_PMS3003 1 37 | 38 | /* Plantower PMS1003 PMS5003 PMS7003 */ 39 | #define DBUF_EVENT_PMS5003 2 40 | 41 | #define DBUF_EVENT_POST_TIME 3 42 | 43 | #define DBUF_EVENT_ESP8266_STARTUP 4 44 | 45 | #define DBUF_EVENT_SHT2X_TEMP_HUM 5 46 | 47 | #define DBUF_EVENT_BMP180_TEMP_PRESSURE 6 48 | 49 | #define DBUF_EVENT_DS3231_TIME_TEMP 7 50 | #define DBUF_EVENT_DS3231_TIME_STEP 8 51 | 52 | #define DBUF_EVENT_BMP280_TEMP_PRESSURE 9 53 | #define DBUF_EVENT_BME280_TEMP_PRESSURE_RH 10 54 | 55 | #define DBUF_EVENT_CLIENT_UTIME 11 56 | 57 | #define DBUF_EVENT_SEGMENT_START 12 58 | 59 | #define DBUF_EVENT_START_LOGGING 13 60 | 61 | #define DBUF_EVENT_PAUSE_LOGGING 14 62 | 63 | #define DBUF_EVENT_TEXT_MESSAGE 15 64 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Configuration parameters. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/ 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 16 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS WITH THE SOFTWARE. 20 | * 21 | */ 22 | 23 | /* 24 | * Parameters. 25 | * 26 | * The 'leds' can be 0 to disable use of LEDS, 1 for Nodemcu, and 2 for 27 | * Witty. 28 | */ 29 | extern uint8_t param_leds; 30 | 31 | /* 32 | * The PMS*003 serial port: 33 | * 0 - None, disabled (default). 34 | * 1 - UART0 on GPIO3 aka RX (Nodemcu pin D9). 35 | * 2 - UART0 swapped pins mode, GPIO13 (Nodemcu pin D7). 36 | * 3 - TODO Flipping between the above for two sensors? 37 | */ 38 | extern uint8_t param_pms_uart; 39 | 40 | /* 41 | * I2C bus pin definitions, GPIO numbers. 42 | * 43 | * SCL defaults to GPIO 5 (Nodemcu pin D1) and SDA to GPIO 4 (Nodemcu 44 | * pin D2) if not supplied. 45 | */ 46 | extern uint8_t param_i2c_scl; 47 | extern uint8_t param_i2c_sda; 48 | 49 | /* 50 | * Logging to the data buffers can be disabled by clearing this variable, and 51 | * this is the start of the data flow so it stops more data entering, but it 52 | * continues to trigger writes to flush the pipeline. 53 | */ 54 | extern bool param_logging; 55 | 56 | /* 57 | * Network parameters. If not sufficiently initialized to communicate with a 58 | * server then wifi is disabled and the post-data task is not created. 59 | */ 60 | 61 | extern char *param_web_server; 62 | extern char param_web_port[]; 63 | extern char *param_web_path; 64 | extern uint32_t param_sensor_id; 65 | extern uint32_t param_key_size; 66 | extern uint8_t *param_sha3_key; 67 | 68 | void init_params(); 69 | 70 | 71 | -------------------------------------------------------------------------------- /content/plot.html: -------------------------------------------------------------------------------- 1 | "" 2 | "" 3 | "" 4 | "" 5 | "" 6 | "" 7 | "", 8 | "" 9 | "" 10 | "" 11 | "" 12 | "" 21 | "

Particle mass estimates for PM2.5a, PM1.0a, PM2.5b, and PM1.0b

" 22 | "" 23 | "" 24 | "
" 25 | "

Particle counts at 0.3µm, 0.5µm, 1.0µm, 2.5µm, 5µm, and 10µm

" 26 | "" 27 | "" 28 | "
" 29 | "

Temperature in degrees celcius from the BME280 and RTC

" 30 | "" 31 | "" 32 | "
" 33 | "

Relative humidity percentage from the BME280

" 34 | "" 35 | "" 36 | "
" 37 | "

Air pressure in pascals from the BME280

" 38 | "" 39 | "" 40 | "" 41 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Configuration parameters. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/ 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 16 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS WITH THE SOFTWARE. 20 | * 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "sysparam.h" 30 | 31 | /* 32 | * Parameters. 33 | */ 34 | uint8_t param_leds; 35 | uint8_t param_pms_uart; 36 | uint8_t param_i2c_scl; 37 | uint8_t param_i2c_sda; 38 | uint8_t param_logging; 39 | char *param_web_server; 40 | char param_web_port[7]; 41 | char *param_web_path; 42 | uint32_t param_sensor_id; 43 | uint32_t param_key_size; 44 | uint8_t *param_sha3_key; 45 | 46 | void init_params() 47 | { 48 | sysparam_status_t status; 49 | 50 | param_leds = 1; 51 | param_pms_uart = 2; 52 | param_i2c_scl = 5; 53 | param_i2c_sda = 4; 54 | param_logging = 1; 55 | param_web_server = NULL; 56 | bzero(param_web_port, sizeof(param_web_port)); 57 | param_web_path = NULL; 58 | param_sensor_id = 0; 59 | param_key_size = 0; 60 | param_sha3_key = NULL; 61 | 62 | sysparam_get_int8("oaq_leds", (int8_t *)¶m_leds); 63 | sysparam_get_int8("oaq_pms_uart", (int8_t *)¶m_pms_uart); 64 | sysparam_get_int8("oaq_i2c_scl", (int8_t *)¶m_i2c_scl); 65 | sysparam_get_int8("oaq_i2c_sda", (int8_t *)¶m_i2c_sda); 66 | 67 | sysparam_get_int8("oaq_logging", (int8_t *)¶m_logging); 68 | 69 | sysparam_get_string("oaq_web_server", ¶m_web_server); 70 | int32_t port = 80; 71 | sysparam_get_int32("oaq_web_port", &port); 72 | snprintf(param_web_port, sizeof(param_web_port), "%u", port); 73 | sysparam_get_string("oaq_web_path", ¶m_web_path); 74 | 75 | sysparam_get_int32("oaq_sensor_id", (int32_t *)¶m_sensor_id); 76 | status = sysparam_get_data("oaq_sha3_key", ¶m_sha3_key, ¶m_key_size, NULL); 77 | if (status != SYSPARAM_OK) { 78 | param_key_size = 0; 79 | param_sha3_key = NULL; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /content/plot.js: -------------------------------------------------------------------------------- 1 | "HTTP/1.1 200 \r\n" 2 | "Content-Type: text/javascript\r\n" 3 | "Cache-Control: max-age=900\r\n" 4 | "Transfer-Encoding: chunked\r\n" 5 | "Connection: close\r\n" 6 | "\r\n", 7 | "var pmsmoothie=new SmoothieChart({responsive:true,minValue:0,timestampFormatter:SmoothieChart.timeFormatter,millisPerPixel:5e3,labels:{fillStyle:\"#e0e0e0\"},grid:{millisPerLine:6e5,fillStyle:\"#303030\"}});var pcsmoothie=new SmoothieChart({responsive:true,minValue:0,timestampFormatter:SmoothieChart.timeFormatter,millisPerPixel:5e3,labels:{fillStyle:\"#e0e0e0\"},grid:{millisPerLine:6e5,fillStyle:\"#303030\"}});var tempsmoothie=new SmoothieChart({responsive:true,timestampFormatter:SmoothieChart.timeFormatter,millisPerPixel:5e3,labels:{fillStyle:\"#e0e0e0\"},grid:{millisPerLine:6e5,fillStyle:\"#303030\"}});var presssmoothie=new SmoothieChart({responsive:true,timestampFormatter:SmoothieChart.timeFormatter,millisPerPixel:5e3,labels:{fillStyle:\"#e0e0e0\"},grid:{millisPerLine:6e5,fillStyle:\"#303030\"}});var rhsmoothie=new SmoothieChart({responsive:true,timestampFormatter:SmoothieChart.timeFormatter,millisPerPixel:5e3,labels:{fillStyle:\"#e0e0e0\"},grid:{millisPerLine:6e5,fillStyle:\"#303030\"}});var pm10a_line=new TimeSeries;var pm25a_line=new TimeSeries;var pm10b_line=new TimeSeries;var pm25b_line=new TimeSeries;var pc03_line=new TimeSeries;var pc05_line=new TimeSeries;var pc10_line=new TimeSeries;var pc25_line=new TimeSeries;var pc50_line=new TimeSeries;var pc100_line=new TimeSeries;var bme280_temp_line=new TimeSeries;var ds3231_temp_line=new TimeSeries;var rh_line=new TimeSeries;var press_line=new TimeSeries;var ds3231_counter=0;var bme280_counter=0;var pms_counter=0;pmsmoothie.addTimeSeries(pm25a_line,{strokeStyle:\"#fef0d9\"});pmsmoothie.addTimeSeries(pm10a_line,{strokeStyle:\"#fdcc8a\"});pmsmoothie.addTimeSeries(pm25b_line,{strokeStyle:\"#fc8d59\"});pmsmoothie.addTimeSeries(pm10b_line,{strokeStyle:\"#d7301f\"});pcsmoothie.addTimeSeries(pc03_line,{strokeStyle:\"#fef0d9\"});pcsmoothie.addTimeSeries(pc05_line,{strokeStyle:\"#fdd49e\"});pcsmoothie.addTimeSeries(pc10_line,{strokeStyle:\"#fdbb84\"});pcsmoothie.addTimeSeries(pc25_line,{strokeStyle:\"#fc8d59\"});pcsmoothie.addTimeSeries(pc50_line,{strokeStyle:\"#e34a33\"});pcsmoothie.addTimeSeries(pc100_line,{strokeStyle:\"#b30000\"});tempsmoothie.addTimeSeries(bme280_temp_line,{strokeStyle:\"#fdbb84\"});tempsmoothie.addTimeSeries(ds3231_temp_line,{strokeStyle:\"#e34a33\"});presssmoothie.addTimeSeries(press_line,{strokeStyle:\"#fdbb84\"});rhsmoothie.addTimeSeries(rh_line,{strokeStyle:\"#fdbb84\"});var getJSON=function(e,i,t){var r=typeof XMLHttpRequest!=\"undefined\"?new XMLHttpRequest:new ActiveXObject(\"Microsoft.XMLHTTP\");var n=\"responseType\"in r;r.open(\"POST\",e,true);r.setRequestHeader(\"Content-Type\",\"application/x-www-form-urlencoded\");var m=Date.now();if(n){r.responseType=\"json\"}r.onreadystatechange=function(){var e=r.status;var m;if(r.readyState==4){if(e==200){i&&i(n?r.response:JSON.parse(r.responseText))}else{t&&t(e)}}};r.send(\"oaq_utimeh=\"+Math.floor(m/4294967296)+\"&oaq_utimel=\"+(m>>>0))};setInterval(function(){getJSON(\"/recentdata.html\",function(e){var i=e.counter;now=(new Date).getTime();if(e.pms_counter!=null&&e.pms_counter!=pms_counter){pm25a_line.append(now,e.pm25a);pm10a_line.append(now,e.pm10a);pm25b_line.append(now,e.pm25b);pm10b_line.append(now,e.pm10b);pc03_line.append(now,e.pc03);pc05_line.append(now,e.pc05);pc10_line.append(now,e.pc10);pc25_line.append(now,e.pc25);pc50_line.append(now,e.pc50);pc100_line.append(now,e.pc100);pms_counter=e.pms_counter}if(e.ds3231_counter!=null&&e.ds3231_counter!=ds3231_counter){ds3231_temp_line.append(now,e.ds3231_temp);ds3231_counter=e.ds3231_counter}if(e.bme280_counter!=null&&e.bme280_counter!=bme280_counter){bme280_temp_line.append(now,e.bme280_temp);press_line.append(now,e.bme280_press);rh_line.append(now,e.bme280_rh);bme280_counter=e.bme280_counter}},function(e){})},1e4);" 8 | -------------------------------------------------------------------------------- /leds.c: -------------------------------------------------------------------------------- 1 | /* 2 | * LED support for blinking progress. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | #include "FreeRTOS.h" 23 | #include "task.h" 24 | #include "esp/gpio.h" 25 | #include "config.h" 26 | 27 | void init_blink() 28 | { 29 | switch (param_leds) { 30 | case 0: 31 | // LEDs not used. 32 | break; 33 | case 1: 34 | // Nodemcu 35 | gpio_enable(16, GPIO_OUTPUT); 36 | gpio_enable(2, GPIO_OUTPUT); 37 | gpio_write(16, 1); 38 | gpio_write(2, 1); 39 | break; 40 | 41 | case 2: 42 | /* 43 | * Witty param_leds setup. 44 | * Multi-color LED is on pins 12 13 and 15. 45 | */ 46 | 47 | //iomux_set_gpio_function(12, 1); 48 | //iomux_set_gpio_function(13, 1); 49 | //iomux_set_gpio_function(15, 1); 50 | gpio_enable(12, GPIO_OUTPUT); // Green 51 | gpio_enable(13, GPIO_OUTPUT); // Blue 52 | gpio_enable(15, GPIO_OUTPUT); // Red 53 | gpio_write(12, 0); 54 | gpio_write(13, 0); 55 | gpio_write(15, 0); 56 | break; 57 | } 58 | } 59 | 60 | void blink_green() 61 | { 62 | switch (param_leds) { 63 | case 1: 64 | // Nodemcu 65 | gpio_write(16, 0); 66 | vTaskDelay(20 / portTICK_PERIOD_MS); 67 | gpio_write(16, 1); 68 | break; 69 | case 2: 70 | // Witty Green 71 | gpio_write(12, 1); 72 | vTaskDelay(20 / portTICK_PERIOD_MS); 73 | gpio_write(12, 0); 74 | break; 75 | } 76 | } 77 | 78 | void blink_blue() 79 | { 80 | switch (param_leds) { 81 | case 1: 82 | // Nodemcu 83 | gpio_write(2, 0); 84 | vTaskDelay(20 / portTICK_PERIOD_MS); 85 | gpio_write(2, 1); 86 | break; 87 | case 2: 88 | // Witty Blue 89 | gpio_write(13, 1); 90 | vTaskDelay(20 / portTICK_PERIOD_MS); 91 | gpio_write(13, 0); 92 | break; 93 | } 94 | } 95 | 96 | void blink_red() 97 | { 98 | switch (param_leds) { 99 | case 1: 100 | // Nodemcu 101 | gpio_write(16, 0); 102 | vTaskDelay(50 / portTICK_PERIOD_MS); 103 | gpio_write(16, 1); 104 | break; 105 | 106 | case 2: 107 | // Witty Red 108 | gpio_write(15, 1); 109 | vTaskDelay(50 / portTICK_PERIOD_MS); 110 | gpio_write(15, 0); 111 | break; 112 | } 113 | } 114 | 115 | void blink_white() 116 | { 117 | switch (param_leds) { 118 | case 1: 119 | // Nodemcu. 120 | gpio_write(16, 0); 121 | gpio_write(2, 0); 122 | vTaskDelay(20 / portTICK_PERIOD_MS); 123 | gpio_write(16, 1); 124 | gpio_write(2, 1); 125 | break; 126 | 127 | case 2: 128 | // Witty Green, Blue, Red 129 | gpio_write(12, 1); 130 | gpio_write(13, 1); 131 | gpio_write(15, 1); 132 | vTaskDelay(20 / portTICK_PERIOD_MS); 133 | gpio_write(12, 0); 134 | gpio_write(13, 0); 135 | gpio_write(15, 0); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /bmp180.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the BMP180 pressure sensor. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "FreeRTOS.h" 29 | #include "task.h" 30 | #include "semphr.h" 31 | #include "i2c/i2c.h" 32 | #include "bmp180/bmp180.h" 33 | #include "ds3231/ds3231.h" 34 | 35 | #include 36 | #include "espressif/esp8266/gpio_register.h" 37 | 38 | #include "buffer.h" 39 | #include "i2c.h" 40 | #include "leds.h" 41 | 42 | 43 | 44 | static i2c_dev_t bmp180_dev = { 45 | .addr = BMP180_DEVICE_ADDRESS, 46 | .bus = I2C_BUS, 47 | }; 48 | 49 | static bool bmp180_available = false; 50 | static uint32_t bmp180_counter = 0; 51 | static int32_t bmp180_temperature = 0; 52 | static uint32_t bmp180_pressure = 0; 53 | 54 | bool bmp180_temp_press(uint32_t *counter, float *temp, float *press) 55 | { 56 | if (!bmp180_available) 57 | return false; 58 | 59 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 60 | *counter = bmp180_counter; 61 | *temp = (float)bmp180_temperature/10.0; 62 | *press = (float)bmp180_pressure; 63 | xSemaphoreGive(i2c_sem); 64 | 65 | return true; 66 | } 67 | 68 | static void bmp180_read_task(void *pvParameters) 69 | { 70 | /* Delta encoding state. */ 71 | uint32_t last_segment = 0; 72 | uint32_t last_bmp180_temp = 0; 73 | uint32_t last_bmp180_pressure = 0; 74 | 75 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 76 | bmp180_constants_t constants; 77 | bool available = bmp180_is_available(&bmp180_dev) && 78 | bmp180_fillInternalConstants(&bmp180_dev, &constants); 79 | xSemaphoreGive(i2c_sem); 80 | 81 | if (!available) 82 | vTaskDelete(NULL); 83 | 84 | for (;;) { 85 | vTaskDelay(10000 / portTICK_PERIOD_MS); 86 | 87 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 88 | 89 | int32_t temperature; 90 | uint32_t pressure; 91 | if (!bmp180_measure(&bmp180_dev, &constants, &temperature, &pressure, 3)) { 92 | xSemaphoreGive(i2c_sem); 93 | blink_red(); 94 | continue; 95 | } 96 | 97 | bmp180_available = true; 98 | bmp180_counter = RTC.COUNTER; 99 | bmp180_temperature = temperature; 100 | bmp180_pressure = pressure; 101 | 102 | xSemaphoreGive(i2c_sem); 103 | 104 | while (1) { 105 | uint8_t outbuf[12]; 106 | /* Delta encoding */ 107 | int32_t temp_delta = (int32_t)temperature - (int32_t)last_bmp180_temp; 108 | uint32_t len = emit_leb128_signed(outbuf, 0, temp_delta); 109 | int32_t pressure_delta = (int32_t)pressure - (int32_t)last_bmp180_pressure; 110 | len = emit_leb128_signed(outbuf, len, pressure_delta); 111 | int32_t code = DBUF_EVENT_BMP180_TEMP_PRESSURE; 112 | uint32_t new_segment = dbuf_append(last_segment, code, outbuf, len, 1); 113 | if (new_segment == last_segment) { 114 | /* 115 | * Commit the values logged. Note this is the only task 116 | * accessing this state so these updates are synchronized with 117 | * the last event of this class append. 118 | */ 119 | last_bmp180_temp = temperature; 120 | last_bmp180_pressure = pressure; 121 | break; 122 | } 123 | 124 | /* Moved on to a new buffer. Reset the delta encoding state and 125 | * retry. */ 126 | last_segment = new_segment; 127 | last_bmp180_temp = 0; 128 | last_bmp180_pressure = 0; 129 | }; 130 | 131 | blink_green(); 132 | } 133 | } 134 | 135 | 136 | 137 | void init_bmp180() 138 | { 139 | xTaskCreate(&bmp180_read_task, "bmp180_read_task", 240, NULL, 11, NULL); 140 | } 141 | -------------------------------------------------------------------------------- /bme280.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the BME280 pressure sensor. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "FreeRTOS.h" 29 | #include "task.h" 30 | #include "semphr.h" 31 | #include "i2c/i2c.h" 32 | #include "bmp280/bmp280.h" 33 | #include "ds3231/ds3231.h" 34 | 35 | #include 36 | #include "espressif/esp8266/gpio_register.h" 37 | 38 | #include "buffer.h" 39 | #include "i2c.h" 40 | #include "leds.h" 41 | 42 | 43 | 44 | static bool bme280_available = false; 45 | static uint32_t bme280_counter = 0; 46 | static int32_t bme280_temperature = 0; 47 | static uint32_t bme280_pressure = 0; 48 | static uint32_t bme280_rh = 0; 49 | 50 | bool bme280_temp_press_rh(uint32_t *counter, float *temp, float *press, float *rh) 51 | { 52 | if (!bme280_available) 53 | return false; 54 | 55 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 56 | *counter = bme280_counter; 57 | *temp = (float)bme280_temperature/100.0; 58 | *press = (float)bme280_pressure/256.0; 59 | *rh = (float)bme280_rh/1024.0; 60 | xSemaphoreGive(i2c_sem); 61 | 62 | return true; 63 | } 64 | 65 | static void bme280_read_task(void *pvParameters) 66 | { 67 | /* Delta encoding state. */ 68 | uint32_t last_segment = 0; 69 | uint32_t last_bme280_temp = 0; 70 | uint32_t last_bme280_pressure = 0; 71 | uint32_t last_bme280_humidity = 0; 72 | 73 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 74 | 75 | bmp280_params_t bme280_params; 76 | bmp280_init_default_params(&bme280_params); 77 | bme280_params.mode = BMP280_MODE_NORMAL; 78 | bme280_params.filter = BMP280_FILTER_16; 79 | bme280_params.oversampling_pressure = BMP280_ULTRA_HIGH_RES; 80 | bme280_params.oversampling_temperature = BMP280_ULTRA_HIGH_RES; 81 | bme280_params.oversampling_humidity = BMP280_ULTRA_HIGH_RES; 82 | bme280_params.standby = BMP280_STANDBY_250; 83 | 84 | bmp280_t bme280_dev; 85 | bme280_dev.i2c_dev.bus = I2C_BUS; 86 | bme280_dev.i2c_dev.addr = BMP280_I2C_ADDRESS_0; 87 | bool available = bmp280_init(&bme280_dev, &bme280_params); 88 | xSemaphoreGive(i2c_sem); 89 | 90 | if (!available) 91 | vTaskDelete(NULL); 92 | 93 | bool bme280p = bme280_dev.id == BME280_CHIP_ID; 94 | 95 | for (;;) { 96 | vTaskDelay(10000 / portTICK_PERIOD_MS); 97 | 98 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 99 | 100 | int32_t temperature; 101 | uint32_t pressure; 102 | uint32_t humidity = 0; 103 | if (!bmp280_read_fixed(&bme280_dev, &temperature, &pressure, 104 | bme280p ? &humidity : NULL)) { 105 | xSemaphoreGive(i2c_sem); 106 | blink_red(); 107 | continue; 108 | } 109 | 110 | bme280_available = true; 111 | bme280_counter = RTC.COUNTER; 112 | bme280_temperature = temperature; 113 | bme280_pressure = pressure; 114 | bme280_rh = humidity; 115 | 116 | xSemaphoreGive(i2c_sem); 117 | 118 | while (1) { 119 | uint8_t outbuf[15]; 120 | /* Delta encoding */ 121 | int32_t temp_delta = (int32_t)temperature - (int32_t)last_bme280_temp; 122 | uint32_t len = emit_leb128_signed(outbuf, 0, temp_delta); 123 | int32_t pressure_delta = (int32_t)pressure - (int32_t)last_bme280_pressure; 124 | len = emit_leb128_signed(outbuf, len, pressure_delta); 125 | int32_t code = DBUF_EVENT_BMP280_TEMP_PRESSURE; 126 | 127 | if (bme280p) { 128 | int32_t humidity_delta = (int32_t)humidity - (int32_t)last_bme280_humidity; 129 | len = emit_leb128_signed(outbuf, len, humidity_delta); 130 | code = DBUF_EVENT_BME280_TEMP_PRESSURE_RH; 131 | } 132 | 133 | uint32_t new_segment = dbuf_append(last_segment, code, outbuf, len, 1); 134 | if (new_segment == last_segment) { 135 | /* 136 | * Commit the values logged. Note this is the only task 137 | * accessing this state so these updates are synchronized with 138 | * the last event of this class append. 139 | */ 140 | last_bme280_temp = temperature; 141 | last_bme280_pressure = pressure; 142 | last_bme280_humidity = humidity; 143 | break; 144 | } 145 | 146 | /* Moved on to a new buffer. Reset the delta encoding state and 147 | * retry. */ 148 | last_segment = new_segment; 149 | last_bme280_temp = 0; 150 | last_bme280_pressure = 0; 151 | last_bme280_humidity = 0; 152 | }; 153 | 154 | blink_green(); 155 | } 156 | } 157 | 158 | 159 | 160 | void init_bme280() 161 | { 162 | xTaskCreate(&bme280_read_task, "BME280 reader", 288, NULL, 11, NULL); 163 | } 164 | -------------------------------------------------------------------------------- /content/config.html: -------------------------------------------------------------------------------- 1 | "" 2 | "" 3 | "" 4 | "" 5 | "", 6 | "" 7 | "" 8 | "" 9 | "" 10 | "" 11 | "" 19 | "
" 20 | "
" 21 | "Sensor configuration" 22 | "
" 23 | "
" 24 | "
" 32 | "
" 33 | "
" 41 | "
" 42 | "
" 45 | "
" 46 | "
" 49 | "
" 50 | "
" 53 | "
" 54 | "
" 56 | "
" 57 | "
" 58 | "Settings required only for posting data to a server" 59 | "
" 60 | "
" 61 | "
" 64 | "
" 65 | "
" 68 | "
" 69 | "
" 72 | "
" 73 | "
" 76 | "
" 77 | "
" 80 | "
" 81 | "
" 82 | "

 

" 83 | "
" 84 | "" 85 | "
", 86 | "
" 87 | "
" 88 | "Synchronize Real Time Clock (RTC) to web browser time" 89 | "

The web browser time may be used as a reference to set the device clock. This also passes the web browser time zone offset to the device to configure the time zone.

" 90 | "
" 91 | "" 92 | "" 93 | "
" 94 | "
" 95 | "
" 96 | "
" 97 | "
" 98 | "Manual Real Time Clock (RTC) configuration" 99 | "Check and set the timezone above before changing the time below. The time is synchronized to the server when posting data to it so will change again if the timezone is not correct. When daylight saving changes, change the timezone above, not the time below. Or use a timezone of zero above and enter the time below in universal time." 100 | "
" 101 | "
" 102 | "
" 105 | "
" 106 | "
" 109 | "
" 110 | "
" 113 | "
" 114 | "
" 117 | "
" 118 | "
" 121 | "
" 122 | "
" 125 | "
" 126 | "
 
" 127 | "
" 128 | "
", 129 | "
" 130 | "
" 131 | "Erase the data stored in flash memory" 132 | "

Erases the data stored in the flash, but not the configuration information. This can take some time.

" 133 | "
" 134 | "
" 135 | "
", 136 | "" 137 | -------------------------------------------------------------------------------- /ds3231.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the DS3231 real time clock. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "FreeRTOS.h" 31 | #include "task.h" 32 | #include "semphr.h" 33 | #include "i2c/i2c.h" 34 | #include "bmp180/bmp180.h" 35 | #include "ds3231/ds3231.h" 36 | 37 | #include 38 | #include "espressif/esp8266/gpio_register.h" 39 | 40 | #include "buffer.h" 41 | #include "i2c.h" 42 | #include "leds.h" 43 | 44 | 45 | 46 | i2c_dev_t ds3231_dev = { 47 | .addr = DS3231_ADDR, 48 | .bus = I2C_BUS, 49 | }; 50 | 51 | 52 | /* 53 | * Handle a time in a response from a server. 54 | */ 55 | void ds3231_note_time(time_t recv_time) 56 | { 57 | struct tm tm; 58 | 59 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 60 | 61 | bool ds3231_available = ds3231_getTime(&ds3231_dev, &tm); 62 | if (ds3231_available) { 63 | time_t clock_time = mktime(&tm); 64 | /* 65 | * If the clock time is less that the server response time 66 | * then the clock must be slow so in that case update the 67 | * clock time. If the clock time is behind then this brings it 68 | * forward to within the minimum network delay of the server 69 | * time. 70 | * 71 | * If the clock time is greater than the server response time 72 | * then this might just be due to the delay replying, and this 73 | * could vary. But if it is more than four seconds ahead then 74 | * update the clock time. 75 | */ 76 | if (clock_time < recv_time || clock_time > recv_time + 4) { 77 | gmtime_r(&recv_time, &tm); 78 | if (ds3231_setTime(&ds3231_dev, &tm)) { 79 | /* 80 | * Log all steps in the clock time. 81 | */ 82 | uint32_t last_segment = dbuf_head_index(); 83 | while (1) { 84 | uint8_t outbuf[8]; 85 | outbuf[0] = clock_time; 86 | outbuf[1] = clock_time >> 8; 87 | outbuf[2] = clock_time >> 16; 88 | outbuf[3] = clock_time >> 24; 89 | outbuf[4] = recv_time; 90 | outbuf[5] = recv_time >> 8; 91 | outbuf[6] = recv_time >> 16; 92 | outbuf[7] = recv_time >> 24; 93 | int32_t code = DBUF_EVENT_DS3231_TIME_STEP; 94 | uint32_t new_segment = dbuf_append(last_segment, code, outbuf, sizeof(outbuf), 1); 95 | if (new_segment == last_segment) 96 | break; 97 | 98 | /* Moved on to a new buffer, retry. */ 99 | last_segment = new_segment; 100 | }; 101 | } 102 | } 103 | } 104 | 105 | xSemaphoreGive(i2c_sem); 106 | } 107 | 108 | bool ds3231_available = false; 109 | int32_t ds3231_counter = 0; 110 | struct tm ds3231_time; 111 | int16_t ds3231_temperature = 0; 112 | 113 | bool ds3231_time_temp(uint32_t *counter, struct tm *time, float *temp) 114 | { 115 | if (!ds3231_available) 116 | return false; 117 | 118 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 119 | *counter = ds3231_counter; 120 | *time = ds3231_time; 121 | *temp = (float)ds3231_temperature * 0.25; 122 | xSemaphoreGive(i2c_sem); 123 | 124 | return true; 125 | } 126 | 127 | static void ds3231_read_task(void *pvParameters) 128 | { 129 | /* Delta encoding state. */ 130 | uint32_t last_segment = 0; 131 | time_t last_clock_time = 0; 132 | int16_t last_temperature = 0; 133 | 134 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 135 | 136 | bzero(&ds3231_time, sizeof(ds3231_time)); 137 | 138 | struct tm time; 139 | bool available = ds3231_getTime(&ds3231_dev, &time); 140 | 141 | xSemaphoreGive(i2c_sem); 142 | 143 | if (!available) 144 | vTaskDelete(NULL); 145 | 146 | for (;;) { 147 | vTaskDelay(180000 / portTICK_PERIOD_MS); 148 | 149 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 150 | 151 | if (!ds3231_getTime(&ds3231_dev, &time)) { 152 | xSemaphoreGive(i2c_sem); 153 | blink_red(); 154 | continue; 155 | } 156 | 157 | time_t clock_time = mktime(&time); 158 | int16_t temperature; 159 | if (!ds3231_getRawTemp(&ds3231_dev, &temperature)) { 160 | xSemaphoreGive(i2c_sem); 161 | blink_red(); 162 | continue; 163 | } 164 | 165 | ds3231_available = true; 166 | ds3231_counter = RTC.COUNTER; 167 | ds3231_time = time; 168 | ds3231_temperature = temperature; 169 | 170 | xSemaphoreGive(i2c_sem); 171 | 172 | while (1) { 173 | uint8_t outbuf[12]; 174 | /* Delta encoding */ 175 | uint32_t time_delta = clock_time - last_clock_time; 176 | uint32_t len = emit_leb128(outbuf, 0, time_delta); 177 | int32_t temp_delta = (int32_t)temperature - (int32_t)last_temperature; 178 | len = emit_leb128_signed(outbuf, len, temp_delta); 179 | int32_t code = DBUF_EVENT_DS3231_TIME_TEMP; 180 | uint32_t new_segment = dbuf_append(last_segment, code, outbuf, len, 1); 181 | if (new_segment == last_segment) 182 | break; 183 | 184 | /* Moved on to a new buffer. Reset the delta encoding 185 | * state and retry. */ 186 | last_segment = new_segment; 187 | last_clock_time = 0; 188 | last_temperature = 0; 189 | }; 190 | 191 | blink_green(); 192 | 193 | /* 194 | * Commit the values logged. Note this is the only task 195 | * accessing this state so these updates are synchronized 196 | * with the last event of this class append. 197 | */ 198 | last_clock_time = clock_time; 199 | last_temperature = temperature; 200 | } 201 | } 202 | 203 | 204 | 205 | 206 | void init_ds3231() 207 | { 208 | xTaskCreate(&ds3231_read_task, "DS3231 reader", 224, NULL, 11, NULL); 209 | } 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Our Air Quality logger for the ESP8266 running the [ESP Open RTOS](https://github.com/SuperHouse/esp-open-rtos) 2 | 3 | A community developed open source air quality data logger. 4 | 5 | Currently supporting the [Plantower](http://plantower.com/) PMS1003, PMS3003, PMS5003, and PMS7003 particle counters. 6 | 7 | Supports a few temperature, relative humidity, and pressure sensors on the same I2C. These are auto-detected and used if available. This data might be important for qualifying if the air conditions support reliable PM measurement. 8 | 9 | * BME280 temperature, relative humidity and air pressure sensor. 10 | 11 | * SHT2x temperature and relative humidity sensors. 12 | 13 | * BMP180 temperature and air pressure sensor. 14 | 15 | The DS3231 real-time-clock is also supported on the I2C bus and used if available. The time is logged periodically and synchronsized to the time that the http server sends in its responses. This can be very helpful when logging data away from Wifi and Internet access so that measurments are correctly time-stamped. 16 | 17 | 18 | ## Building 19 | 20 | Follow the build instructions for esp-open-rtos, and when the examples are running copy this code into `examples/oaq/`. 21 | 22 | This code supports a 4MiB SPI flash, so the ESP12, and the Nodemcu and Witty boards have been tested. For this flash it was necessary to edit `parameters.mk` to change `FLASH_SIZE ?= 32` and `FLASH_MODE ?= dio`. 23 | 24 | The following will build the code and flash a device on Linux. 25 | 26 | `make flash -j4 -C examples/oaq ESPPORT=/dev/ttyUSB0` 27 | 28 | 29 | ## Features 30 | 31 | * Logs the data to a memory buffer and then to the SPI flash storage, using 3MiB of the 4MiB for data storage. This allows the data logger to be used out of reach of Wifi and the Internet for a period. 32 | 33 | * All the data from the Plantower sensor is logged, each sample at 0.8 second intervals, and all the data in the samples which includes the PM1.0, PM2.5, and PM10 values plus the particle counts, and even the checksum for each sample. This might be useful for local sources of pollution that can cause quick changes in air quality, and might be useful for post-analysis such as noise reduction. 34 | 35 | * The data is compressed to fit more data into the flash and this also reduces wear on the flash and perhaps power usage. The particle count distributions are converted to differential values reducing their magnitude, and after delta encoding they are encoding using a custom variable bit length code. There are special events for the case of no change in the values in which case only a time delta is encoded. This typically compresses the data to 33% to 15% of the original size. 36 | 37 | * The compressed data is stored in flash sectors, and each sector stands on its own and can be uncompressed on its own. An attempt is made to handle bad sectors, in which case the data is written to the next good sector. Each valid sector is assigned a monotonically increasing 32-bit index. The sectors are organized as a ring-buffer, so when full the oldest is overwritten. The sectors are buffered in memory before writing to reduce the number of writes and the current data is periodically flushed to the flash storage to avoid too much data loss if power is lost. ESP flash tools can read these sectors for downloading the data without Wifi. 38 | 39 | * The compressed sectors are HTTP-POSTed to a server. The current head sector is periodically posted to the server too to keep it updated and only the new data is posted. The server response can request re-sending of sectors still stored on the device to handle data loss at the server. The server can not affected the data stored on the device or the logging of the data to flash as a safety measure. 40 | 41 | * The ESP8266 Real-Time-Clock (RTC) counter is logged with every event. The server response includes the real time and response events are logged allowing estimation of the real time of events in post-analysis. This can be be supported by the optional DS3231 real-time-clock. Support for logging a button press will be added to allow people to synchronize logging and events times manually. 42 | 43 | * The data posted to the server is signed using the MAC-SHA3 algorithm ensuring integrity of the data and preventing forgery of data posted to the server. 44 | 45 | 46 | ## Connection 47 | 48 | Two ESP8266 boards have been tested. 49 | 50 | ### Nodemcu 51 | 52 | UART pins for the PMSx003: 53 | 54 | | PCB name | Function | Cable color | 55 | | -------- | -------- | ----------- | 56 | | VIN | +5V | Purple | 57 | | GND | 0V | Orange | 58 | | D7 | RX | Green | 59 | | D8 | TX | Blue | 60 | 61 | Note: the Lolin V3 Nodemcu board VIN pin is not usable as a +5V output, but it has +5V output on it's VU pin which is a reserved pin on other Nodemcu boards. 62 | 63 | Note: the TX line is to be not connect at present as the device sends verbose debug output on the serial line that might affect the sensor. 64 | 65 | I2C pins: 66 | 67 | | PCB name | Function | 68 | | -------- | -------- | 69 | | D1 | SCL | 70 | | D2 | SDA | 71 | | 3V3 | +V | 72 | | G | Ground | 73 | 74 | ### Witty 75 | 76 | UART pins for the PMSx003: 77 | 78 | | PCB name | Function | Cable color | 79 | | -------- | -------- | ----------- | 80 | | VIN | +5V | Purple | 81 | | GND | 0V | Orange | 82 | | RX | RX | Green | 83 | | TX | TX | Blue | 84 | 85 | I2C pins: 86 | 87 | | PCB name | Function | 88 | | -------- | -------- | 89 | | GPIO5 | SCL | 90 | | GPIO4 | SDA | 91 | | VCC | +V | 92 | | G | Ground | 93 | 94 | 95 | ## Configuration 96 | 97 | The configuration is stored in the esp-free-rtos sysparam database. See the esp-free-rtos sysparam example for an editor which can be used to set these for now. The database uses four more sectors at the end of the flash. 98 | 99 | * `board` - single binary byte that can be 0 for Nodemcu, and 1 for Witty. It is only used to blink some LEDs at present. 100 | 101 | * `pms_uart` - single binary byte that gives the PMS*003 serial port: 0 - None, disabled (default); 1 - UART0 on GPIO3 aka RX (Nodemcu pin D9); 2 - UART0 swapped pins mode, GPIO13 (Nodemcu pin D7); 3 - TODO Flipping between the above for two sensors? 102 | 103 | * `i2c_scl`, `i2c_sda` - single binary bytes giving the I2C bus pin definitions, GPIO numbers. SCL defaults to GPIO 0 (Nodemcu pin D3) and SDA to GPIO 2 (Nodemcu pin D4) if not supplied. 104 | 105 | The follow are network parameters. If not sufficiently initialized to communicate with a server then Wifi is disabled and the post-data task is not created, but the data will still be logged to the internal Flash storage and can be downloaded to a PC. 106 | 107 | * `web_server` - a string, e.g. 'ourairquality.org', '192.168.1.1' 108 | 109 | * `web_port` - a string, e.g. '80' 110 | 111 | * `web_path` - a string, e.g. '/cgi-bin/recv' 112 | 113 | * `sensor_id` - a binary 32 bit number. 114 | 115 | * `key_size` - a binary 32 bit number. 116 | 117 | * `sha3_key` - a binary blob, with `key_size` bytes. 118 | 119 | * `wifi_ssid` - a string, the Wifi station ID. 120 | 121 | * `wifi_pass` - a string, the respective Wifi password. 122 | 123 | 124 | ## TODO 125 | 126 | This is at the proof of concept stage. 127 | 128 | The URL to upload the data to and the key is hard coded, see `post.c`, and this needs to be configurable. 129 | 130 | TODO a web client app to create the sysparam sectors. 131 | 132 | The server side code for logging the data to files has been prototyped, and is CGI code written in a few pages of C code and tested on Apache and expected to work on economical cPanel shared hosting. The client front end is stil TODO and is just some hack scripts for now. 133 | -------------------------------------------------------------------------------- /content/smoothie.js: -------------------------------------------------------------------------------- 1 | "HTTP/1.1 200 \r\n" 2 | "Content-Type: text/javascript\r\n" 3 | "Cache-Control: max-age=900\r\n" 4 | "Transfer-Encoding: chunked\r\n" 5 | "Connection: close\r\n" 6 | "\r\n", 7 | "(function(e){var t={extend:function(){arguments[0]=arguments[0]||{};for(var e=1;ethis.maxValue){this.maxValue=t}if(t=0&&this.data[a][0]>e){a--}if(a===-1){this.data.splice(0,0,[e,t])}else if(this.data.length>0&&this.data[a][0]===e){if(i){this.data[a][1]+=t;t=this.data[a][1]}else{this.data[a][1]=t}}else if(a=t&&this.data[i+1][0]0){e.resetBoundsTimerId=setInterval(function(){e.resetBounds()},e.options.resetBoundsInterval)}};a.prototype.removeTimeSeries=function(e){var t=this.seriesSet.length;for(var i=0;i.1||Math.abs(o)>.1;this.currentValueRange+=e.scaleSmoothing*l;this.currentVisMinValue+=e.scaleSmoothing*o}this.valueRange={min:i,max:t}};a.prototype.render=function(e,t){var i=(new Date).getTime();if(!this.isAnimatingScale){var a=Math.min(1e3/6,this.options.millisPerPixel);if(i-this.lastRenderTimeMillis0){s.beginPath();for(var u=t-t%n.grid.millisPerLine;u>=l;u-=n.grid.millisPerLine){var m=h(u);if(n.grid.sharpLines){m-=.5}s.moveTo(m,0);s.lineTo(m,r.height)}s.stroke();s.closePath()}for(var d=1;d1){if(x.fillStyle){s.lineTo(r.width+x.lineWidth+1,V);s.lineTo(r.width+x.lineWidth+1,r.height+x.lineWidth+1);s.lineTo(b,r.height+x.lineWidth);s.fillStyle=x.fillStyle;s.fill()}if(x.strokeStyle&&x.strokeStyle!==\"none\"){s.stroke()}s.closePath()}s.restore()}if(!n.labels.disabled&&!isNaN(this.valueRange.min)&&!isNaN(this.valueRange.max)){var M=n.yMaxFormatter(this.valueRange.max,n.labels.precision),k=n.yMinFormatter(this.valueRange.min,n.labels.precision),F=n.scrollBackwards?0:r.width-s.measureText(M).width-2,R=n.scrollBackwards?0:r.width-s.measureText(k).width-2;s.fillStyle=n.labels.fillStyle;s.fillText(M,F,n.labels.fontSize);s.fillText(k,R,r.height-2)}if(n.timestampFormatter&&n.grid.millisPerLine>0){var A=n.scrollBackwards?s.measureText(k).width:r.width-s.measureText(k).width+4;for(var u=t-t%n.grid.millisPerLine;u>=l;u-=n.grid.millisPerLine){var m=h(u);if(!n.scrollBackwards&&mA){var B=new Date(u),W=n.timestampFormatter(B),L=s.measureText(W).width;A=n.scrollBackwards?m+L+2:m-L-2;s.fillStyle=n.labels.fillStyle;if(n.scrollBackwards){s.fillText(W,m,r.height-2)}else{s.fillText(W,m-L,r.height-2)}}}}s.restore()};a.timeFormatter=function(e){function t(e){return(e<10?\"0\":\"\")+e}return t(e.getHours())+\":\"+t(e.getMinutes())+\":\"+t(e.getSeconds())};e.TimeSeries=i;e.SmoothieChart=a})(typeof exports===\"undefined\"?this:exports);" 8 | -------------------------------------------------------------------------------- /sht21.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the SHT2x temperature and humidity sensor. 3 | * 4 | * Licensed under the Apache License, Version 2.0, January 2004 (the 5 | * "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/ 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 10 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 12 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 13 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 14 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 16 | * DEALINGS WITH THE SOFTWARE. 17 | * 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "FreeRTOS.h" 27 | #include "task.h" 28 | #include "semphr.h" 29 | #include "i2c/i2c.h" 30 | #include "bmp180/bmp180.h" 31 | #include "ds3231/ds3231.h" 32 | 33 | #include 34 | #include "espressif/esp8266/gpio_register.h" 35 | 36 | #include "buffer.h" 37 | #include "i2c.h" 38 | #include "leds.h" 39 | 40 | 41 | 42 | /* Sensor commands */ 43 | typedef enum 44 | { 45 | TRIG_T_MEASUREMENT_HM = 0xE3, /* Trigger temp measurement - hold master. */ 46 | TRIG_RH_MEASUREMENT_HM = 0xE5, /* Trigger humidity measurement - hold master. */ 47 | TRIG_T_MEASUREMENT_POLL = 0xF3, /* Trigger temp measurement - no hold master. */ 48 | TRIG_RH_MEASUREMENT_POLL = 0xF5, /* Trigger humidity measurement - no hold master. */ 49 | USER_REG_W = 0xE6, /* Writing user register. */ 50 | USER_REG_R = 0xE7, /* Reading user register. */ 51 | SOFT_RESET = 0xFE /* Soft reset. */ 52 | } sht2x_command; 53 | 54 | typedef enum 55 | { 56 | SHT2x_RES_12_14BIT = 0x00, // RH=12bit, T=14bit 57 | SHT2x_RES_8_12BIT = 0x01, // RH= 8bit, T=12bit 58 | SHT2x_RES_10_13BIT = 0x80, // RH=10bit, T=13bit 59 | SHT2x_RES_11_11BIT = 0x81, // RH=11bit, T=11bit 60 | SHT2x_RES_MASK = 0x81 // Mask for res. bits (7,0) in user reg. 61 | } sht2x_resolution; 62 | 63 | typedef enum 64 | { 65 | SHT2x_EOB_ON = 0x40, // end of battery 66 | SHT2x_EOB_MASK = 0x40, // Mask for EOB bit(6) in user reg. 67 | } sht2x_eob; 68 | 69 | typedef enum 70 | { 71 | I2C_ADR_W = 128, /* Sensor I2C address + write bit. */ 72 | I2C_ADR_R = 129 /* Sensor I2C address + read bit. */ 73 | } sht2x_addr; 74 | 75 | 76 | static bool sht2x_check_crc(uint8_t data[], uint8_t num_bytes, uint8_t checksum) 77 | { 78 | uint8_t crc = 0; 79 | uint8_t i; 80 | 81 | for (i = 0; i < num_bytes; ++i) { 82 | crc ^= (data[i]); 83 | for (uint8_t bit = 8; bit > 0; --bit) { 84 | if (crc & 0x80) 85 | crc = (crc << 1) ^ 0x131; 86 | else 87 | crc = (crc << 1); 88 | } 89 | } 90 | return crc == checksum; 91 | } 92 | 93 | static bool sht2x_read_user_register(uint8_t *value) 94 | { 95 | i2c_start(I2C_BUS); 96 | 97 | if (!i2c_write(I2C_BUS, I2C_ADR_W) || !i2c_write(I2C_BUS, USER_REG_R)) { 98 | i2c_stop(I2C_BUS); 99 | return false; 100 | } 101 | 102 | i2c_start(I2C_BUS); 103 | 104 | if (!i2c_write(I2C_BUS, I2C_ADR_R)) { 105 | i2c_stop(I2C_BUS); 106 | return false; 107 | } 108 | 109 | *value = i2c_read(I2C_BUS, 0); 110 | uint8_t crc = i2c_read(I2C_BUS, 1); 111 | i2c_stop(I2C_BUS); 112 | return sht2x_check_crc(value, 1, crc); 113 | } 114 | 115 | static bool sht2x_write_user_register(uint8_t value) 116 | { 117 | i2c_start(I2C_BUS); 118 | 119 | bool result = i2c_write(I2C_BUS, I2C_ADR_W) && 120 | i2c_write(I2C_BUS, USER_REG_W) && 121 | i2c_write(I2C_BUS, value); 122 | 123 | i2c_stop(I2C_BUS); 124 | return result; 125 | } 126 | 127 | static bool sht2x_soft_reset() 128 | { 129 | i2c_start(I2C_BUS); 130 | 131 | bool result = i2c_write(I2C_BUS, I2C_ADR_W) && i2c_write(I2C_BUS, SOFT_RESET); 132 | i2c_stop(I2C_BUS); 133 | /* Wait until sensor restarted. */ 134 | sdk_os_delay_us(15000); 135 | 136 | return result; 137 | } 138 | 139 | static bool sht2x_get_serial_number(uint8_t *serial_number) 140 | { 141 | i2c_start(I2C_BUS); 142 | if (!i2c_write(I2C_BUS, I2C_ADR_W) || 143 | !i2c_write(I2C_BUS, 0xFA) || 144 | !i2c_write(I2C_BUS, 0x0F)) 145 | goto fail; 146 | i2c_start(I2C_BUS); 147 | if (!i2c_write(I2C_BUS, I2C_ADR_R)) 148 | goto fail; 149 | serial_number[5] = i2c_read(I2C_BUS, 0); 150 | if (!sht2x_check_crc(&serial_number[5], 1, i2c_read(I2C_BUS, 0))) 151 | goto fail; 152 | serial_number[4] = i2c_read(I2C_BUS, 0); 153 | if (!sht2x_check_crc(&serial_number[4], 1, i2c_read(I2C_BUS, 0))) 154 | goto fail; 155 | serial_number[3] = i2c_read(I2C_BUS, 0); 156 | if (!sht2x_check_crc(&serial_number[3], 1, i2c_read(I2C_BUS, 0))) 157 | goto fail; 158 | serial_number[2] = i2c_read(I2C_BUS, 0); 159 | if (!sht2x_check_crc(&serial_number[2], 1, i2c_read(I2C_BUS, 1))) 160 | goto fail; 161 | i2c_stop(I2C_BUS); 162 | 163 | i2c_start(I2C_BUS); 164 | if (!i2c_write(I2C_BUS, I2C_ADR_W) || 165 | !i2c_write(I2C_BUS, 0xFC) || 166 | !i2c_write(I2C_BUS, 0xC9)) 167 | goto fail; 168 | i2c_start(I2C_BUS); 169 | if (!i2c_write(I2C_BUS, I2C_ADR_R)) 170 | goto fail; 171 | uint8_t data[2]; 172 | data[0] = i2c_read(I2C_BUS, 0); 173 | data[1] = i2c_read(I2C_BUS, 0); 174 | if (!sht2x_check_crc(data, 2, i2c_read(I2C_BUS, 0))) 175 | goto fail; 176 | serial_number[1] = data[0]; 177 | serial_number[0] = data[1]; 178 | data[0] = i2c_read(I2C_BUS, 0); 179 | data[1] = i2c_read(I2C_BUS, 0); 180 | if (!sht2x_check_crc(data, 2, i2c_read(I2C_BUS, 1))) 181 | goto fail; 182 | serial_number[7] = data[0]; 183 | serial_number[6] = data[1]; 184 | i2c_stop(I2C_BUS); 185 | 186 | return true; 187 | 188 | fail: 189 | i2c_stop(I2C_BUS); 190 | return false; 191 | } 192 | 193 | /* 194 | * Measure the temperature if temp_rh is 0 and the relative humidity 195 | * if temp_rh is 1. Return 0 on success and 1 if there is an error. 196 | */ 197 | static uint8_t sht2x_measure_poll(int temp_rh, uint8_t data[], uint8_t *crc) 198 | { 199 | i2c_start(I2C_BUS); 200 | if (!i2c_write(I2C_BUS, I2C_ADR_W) || 201 | !i2c_write(I2C_BUS, temp_rh ? TRIG_RH_MEASUREMENT_POLL : TRIG_T_MEASUREMENT_POLL)) { 202 | i2c_stop(I2C_BUS); 203 | return 1; 204 | } 205 | 206 | int i = 0; 207 | int res; 208 | do { 209 | i2c_start(I2C_BUS); 210 | sdk_os_delay_us(10000); 211 | res = i2c_write(I2C_BUS, I2C_ADR_R); 212 | if (i++ >= 20) { 213 | i2c_stop(I2C_BUS); 214 | return 1; 215 | } 216 | } while (res == 0); 217 | 218 | data[0] = i2c_read(I2C_BUS, 0); 219 | data[1] = i2c_read(I2C_BUS, 0); 220 | *crc = i2c_read(I2C_BUS, 1); 221 | i2c_stop(I2C_BUS); 222 | return sht2x_check_crc(data, 2, *crc); 223 | } 224 | 225 | 226 | 227 | static bool sht2x_available = false; 228 | static uint32_t sht2x_counter = 0; 229 | static uint8_t sht2x_serial_number[8]; 230 | static uint16_t sht2x_temperature = 0; 231 | static uint16_t sht2x_rh = 0; 232 | 233 | bool sht2x_temp_rh(uint32_t *counter, float *temp, float *rh) 234 | { 235 | if (!sht2x_available) 236 | return false; 237 | 238 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 239 | *counter = sht2x_counter; 240 | *temp = -46.85 + (175.72/16384.0) * (float)sht2x_temperature; 241 | *rh = -6.0 + (125.0/16384.0) * (float)sht2x_rh; 242 | xSemaphoreGive(i2c_sem); 243 | 244 | return true; 245 | } 246 | 247 | static void sht2x_read_task(void *pvParameters) 248 | { 249 | /* Delta encoding state. */ 250 | uint32_t last_segment = 0; 251 | uint16_t last_temp = 0; 252 | uint16_t last_rh = 0; 253 | 254 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 255 | 256 | /* 257 | * Reset the sensor and try reading the serial number to try 258 | * detecting the sensor. 259 | */ 260 | bool available = sht2x_soft_reset() && 261 | sht2x_get_serial_number(sht2x_serial_number); 262 | 263 | /* Set resolution to 12 bit temperature and 14 bit relative humidity. */ 264 | uint8_t user_reg; 265 | if (sht2x_read_user_register(&user_reg)) { 266 | user_reg = (user_reg & ~SHT2x_RES_MASK) | SHT2x_RES_12_14BIT; 267 | if (!sht2x_write_user_register(user_reg)) 268 | available = false; 269 | } else { 270 | available = false; 271 | } 272 | 273 | xSemaphoreGive(i2c_sem); 274 | 275 | if (!available) 276 | vTaskDelete(NULL); 277 | 278 | for (;;) { 279 | vTaskDelay(10000 / portTICK_PERIOD_MS); 280 | 281 | xSemaphoreTake(i2c_sem, portMAX_DELAY); 282 | 283 | uint8_t data[4]; 284 | uint8_t temp_crc; 285 | if (!sht2x_measure_poll(0, data, &temp_crc)) { 286 | xSemaphoreGive(i2c_sem); 287 | blink_red(); 288 | continue; 289 | } 290 | 291 | uint16_t temp = ((uint16_t) data[0]) << 8 | data[1]; 292 | temp >>= 2; /* Strip the two low status bits */ 293 | 294 | uint8_t rh_crc; 295 | if (!sht2x_measure_poll(1, &data[2], &rh_crc)) { 296 | xSemaphoreGive(i2c_sem); 297 | blink_red(); 298 | continue; 299 | } 300 | 301 | uint16_t rh = ((uint16_t) data[2]) << 8 | data[3]; 302 | rh >>= 2; /* Strip the two low status bits */ 303 | 304 | sht2x_available = true; 305 | sht2x_counter = RTC.COUNTER; 306 | sht2x_temperature = temp; 307 | sht2x_rh = rh; 308 | 309 | xSemaphoreGive(i2c_sem); 310 | 311 | while (1) { 312 | uint8_t outbuf[8]; 313 | /* Delta encoding */ 314 | int32_t temp_delta = (int32_t)temp - (int32_t)last_temp; 315 | uint32_t len = emit_leb128_signed(outbuf, 0, temp_delta); 316 | int32_t rh_delta = (int32_t)rh - (int32_t)last_rh; 317 | len = emit_leb128_signed(outbuf, len, rh_delta); 318 | /* Include the xor of both crcs */ 319 | outbuf[len++] = temp_crc ^ rh_crc; 320 | int32_t code = DBUF_EVENT_SHT2X_TEMP_HUM; 321 | uint32_t new_segment = dbuf_append(last_segment, code, outbuf, len, 1); 322 | if (new_segment == last_segment) { 323 | /* 324 | * Commit the values logged. Note this is the only task 325 | * accessing this state so these updates are synchronized with 326 | * the last event of this class append. 327 | */ 328 | last_temp = temp; 329 | last_rh = rh; 330 | break; 331 | } 332 | 333 | /* Moved on to a new buffer. Reset the delta encoding state and 334 | * retry. */ 335 | last_segment = new_segment; 336 | last_temp = 0; 337 | last_rh = 0; 338 | }; 339 | 340 | blink_green(); 341 | } 342 | } 343 | 344 | 345 | 346 | 347 | void init_sht2x() 348 | { 349 | xTaskCreate(&sht2x_read_task, "sht2x_read_task", 208, NULL, 11, NULL); 350 | } 351 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pms.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for the Plantower PMS3003 and PMS5003. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * http://www.apache.org/licenses/ 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 12 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 13 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 15 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 18 | * DEALINGS WITH THE SOFTWARE. 19 | * 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "FreeRTOS.h" 30 | #include "task.h" 31 | 32 | #include "buffer.h" 33 | #include "leds.h" 34 | #include "config.h" 35 | 36 | 37 | 38 | static uint8_t mygetc() 39 | { 40 | uint8_t ch; 41 | while (1) { 42 | if (read(0, (void*)&ch, 1)) { 43 | return ch; 44 | } 45 | } 46 | } 47 | 48 | 49 | /* 50 | * Variable bit length encoding support. 51 | */ 52 | static uint8_t outbuf[256]; 53 | static int noutbits; 54 | static uint32_t outbits; 55 | static int outlen; 56 | 57 | static void emitbits(uint32_t bits, uint8_t nbits) 58 | { 59 | outbits |= bits << noutbits; 60 | noutbits += nbits; 61 | while (noutbits >= 8) { 62 | outbuf[outlen++] = (unsigned char) (outbits & 0xFF); 63 | outbits >>= 8; 64 | noutbits -= 8; 65 | } 66 | } 67 | 68 | static void init_outbuf() 69 | { 70 | outlen = 0; 71 | noutbits = 0; 72 | outbits = 0; 73 | } 74 | 75 | /* 76 | * Variable length encoded value for the PMS*003 events. 77 | */ 78 | 79 | static void emit_var_int(int32_t v) 80 | { 81 | if (v == 0) { 82 | /* 0 -> '1' */ 83 | emitbits(1, 1); 84 | return; 85 | } 86 | 87 | emitbits(0, 1); 88 | 89 | /* The sign bit. */ 90 | if (v < 0) { 91 | emitbits(1, 1); 92 | v = -v; 93 | } else { 94 | emitbits(0, 1); 95 | } 96 | 97 | if (v == 1) { 98 | /* +1 -> '001' 99 | * -1 -> '011' 100 | */ 101 | emitbits(1, 1); 102 | return; 103 | } 104 | 105 | emitbits(0, 1); 106 | 107 | if (v < 33) { 108 | /* +2 to +32 -> '000 xxxxx' 109 | * -2 to -32 -> '010 xxxxx' 110 | */ 111 | emitbits(v - 2, 5); 112 | return; 113 | } 114 | 115 | emitbits(0x1f, 5); 116 | 117 | /* 16 bit unsigned value: 118 | * +33 to 65568 : #x000 11111 xxxx xxxx xxxx xxxx 119 | * -33 to 65568 : #x010 11111 xxxx xxxx xxxx xxxx 120 | */ 121 | v = v - 33; 122 | emitbits(v & 0xffff, 16); 123 | } 124 | 125 | static bool pms_available = false; 126 | static uint32_t pms_counter = 0; 127 | static uint16_t pms_pm1a = 0; 128 | static uint16_t pms_pm25a = 0; 129 | static uint16_t pms_pm10a = 0; 130 | static uint16_t pms_pm1b = 0; 131 | static uint16_t pms_pm25b = 0; 132 | static uint16_t pms_pm10b = 0; 133 | static uint16_t pms_c1 = 0; 134 | static uint16_t pms_c2 = 0; 135 | static uint16_t pms_c3 = 0; 136 | static uint16_t pms_c4 = 0; 137 | static uint16_t pms_c5 = 0; 138 | static uint16_t pms_c6 = 0; 139 | static uint16_t pms_r1 = 0; 140 | 141 | bool pms_last_data(uint32_t *counter, uint16_t *pm1a, uint16_t *pm25a, uint16_t *pm10a, uint16_t *pm1b, uint16_t *pm25b, uint16_t *pm10b, uint16_t *c1, uint16_t *c2, uint16_t *c3, uint16_t *c4, uint16_t *c5, uint16_t *c6, uint16_t *r1) 142 | { 143 | *counter = pms_counter; 144 | *pm1a = pms_pm1a; 145 | *pm25a = pms_pm25a; 146 | *pm10a = pms_pm10a; 147 | *pm1b = pms_pm1b; 148 | *pm25b = pms_pm25b; 149 | *pm10b = pms_pm10b; 150 | *c1 = pms_c1; 151 | *c2 = pms_c2; 152 | *c3 = pms_c3; 153 | *c4 = pms_c4; 154 | *c5 = pms_c5; 155 | *c6 = pms_c6; 156 | *r1 = pms_r1; 157 | return pms_available; 158 | } 159 | 160 | /* 161 | * The particle counter events are compressed. The prior event values are noted 162 | * here to support delta encoding, and initialized to zeros at the start of each 163 | * new data buffer so that each buffer can be decoded on its own. 164 | */ 165 | 166 | static void pms_read_task(void *pvParameters) 167 | { 168 | /* Delta encoding state. */ 169 | uint32_t last_segment = 0; 170 | int32_t last_pm1a = 0; 171 | int32_t last_pm25ad = 0; 172 | int32_t last_pm10ad = 0; 173 | int32_t last_pm1b = 0; 174 | int32_t last_pm25bd = 0; 175 | int32_t last_pm10bd = 0; 176 | int32_t last_c1d = 0; 177 | int32_t last_c2d = 0; 178 | int32_t last_c3d = 0; 179 | int32_t last_c4d = 0; 180 | int32_t last_c5d = 0; 181 | int32_t last_c6 = 0; 182 | int32_t last_r1 = 0; 183 | 184 | for (;;) { 185 | /* Search for the "BM" header. */ 186 | uint8_t c = mygetc(); 187 | if (c != 'B') 188 | continue; 189 | 190 | c = mygetc(); 191 | if (c != 'M') 192 | continue; 193 | 194 | /* Read the length. */ 195 | uint16_t msb = mygetc(); 196 | uint8_t lsb = mygetc(); 197 | uint16_t length = msb << 8 | lsb; 198 | if (length != 0x14 && length != 0x1c) 199 | continue; 200 | 201 | uint16_t checksum = 143 + msb + lsb; 202 | 203 | msb = mygetc(); 204 | lsb = mygetc(); 205 | checksum += msb + lsb; 206 | int32_t pm1a = msb << 8 | lsb; 207 | 208 | msb = mygetc(); 209 | lsb = mygetc(); 210 | checksum += msb + lsb; 211 | int32_t pm25a = msb << 8 | lsb; 212 | int32_t pm25ad = pm25a - pm1a; 213 | 214 | msb = mygetc(); 215 | lsb = mygetc(); 216 | checksum += msb + lsb; 217 | int32_t pm10a = msb << 8 | lsb; 218 | int32_t pm10ad = pm10a - pm25a; 219 | 220 | msb = mygetc(); 221 | lsb = mygetc(); 222 | checksum += msb + lsb; 223 | int32_t pm1b = msb << 8 | lsb; 224 | 225 | msb = mygetc(); 226 | lsb = mygetc(); 227 | checksum += msb + lsb; 228 | int32_t pm25b = msb << 8 | lsb; 229 | int32_t pm25bd = pm25b - pm1b; 230 | 231 | msb = mygetc(); 232 | lsb = mygetc(); 233 | checksum += msb + lsb; 234 | int32_t pm10b = msb << 8 | lsb; 235 | int32_t pm10bd = pm10b - pm25b; 236 | 237 | msb = mygetc(); 238 | lsb = mygetc(); 239 | checksum += msb + lsb; 240 | int32_t c1 = msb << 8 | lsb; 241 | 242 | msb = mygetc(); 243 | lsb = mygetc(); 244 | checksum += msb + lsb; 245 | int32_t c2 = msb << 8 | lsb; 246 | 247 | int32_t c1d = c1 - c2; 248 | 249 | int32_t c3 = 0; 250 | int32_t c4 = 0; 251 | int32_t c5 = 0; 252 | int32_t c6 = 0; 253 | 254 | if (length == 0x1c) { 255 | msb = mygetc(); 256 | lsb = mygetc(); 257 | checksum += msb + lsb; 258 | c3 = msb << 8 | lsb; 259 | 260 | msb = mygetc(); 261 | lsb = mygetc(); 262 | checksum += msb + lsb; 263 | c4 = msb << 8 | lsb; 264 | 265 | msb = mygetc(); 266 | lsb = mygetc(); 267 | checksum += msb + lsb; 268 | c5 = msb << 8 | lsb; 269 | 270 | msb = mygetc(); 271 | lsb = mygetc(); 272 | checksum += msb + lsb; 273 | c6 = msb << 8 | lsb; 274 | } 275 | 276 | int32_t c2d = c2 - c3; 277 | 278 | int32_t c3d = 0; 279 | int32_t c4d = 0; 280 | int32_t c5d = 0; 281 | 282 | if (length == 0x1c) { 283 | c3d = c3 - c4; 284 | c4d = c4 - c5; 285 | c5d = c5 - c6; 286 | } 287 | 288 | msb = mygetc(); 289 | lsb = mygetc(); 290 | checksum += msb + lsb; 291 | int32_t r1 = msb << 8 | lsb; 292 | 293 | msb = mygetc(); 294 | lsb = mygetc(); 295 | uint16_t expected_checksum = msb << 8 | lsb; 296 | 297 | if (checksum != expected_checksum) { 298 | blink_red(); 299 | continue; 300 | } 301 | 302 | pms_available = true; 303 | pms_counter = RTC.COUNTER; 304 | pms_pm1a = pm1a; 305 | pms_pm25a = pm25a; 306 | pms_pm10a = pm10a; 307 | pms_pm1b = pm1b; 308 | pms_pm25b = pm25b; 309 | pms_pm10b = pm10b; 310 | pms_c1 = c1; 311 | pms_c2 = c2; 312 | pms_c3 = c3; 313 | pms_c4 = c4; 314 | pms_c5 = c5; 315 | pms_c6 = c6; 316 | pms_r1 = r1; 317 | 318 | while (1) { 319 | /* Variable length encoding. */ 320 | init_outbuf(); 321 | emit_var_int(pm1a - last_pm1a); 322 | emit_var_int(pm25ad - last_pm25ad); 323 | emit_var_int(pm10ad - last_pm10ad); 324 | emit_var_int(pm1b - last_pm1b); 325 | emit_var_int(pm25bd - last_pm25bd); 326 | emit_var_int(pm10bd - last_pm10bd); 327 | emit_var_int(c1d - last_c1d); 328 | emit_var_int(c2d - last_c2d); 329 | if (length == 0x1c) { 330 | emit_var_int(c3d - last_c3d); 331 | emit_var_int(c4d - last_c4d); 332 | emit_var_int(c5d - last_c5d); 333 | emit_var_int(c6 - last_c6); 334 | } 335 | emit_var_int(r1 - last_r1); 336 | 337 | /* Emit at least eight bits of the device supplied checksum and fill 338 | * to a byte boundary with the rest so there will always be at least 339 | * eight checksum bits and often more and at most 15 bits. */ 340 | emitbits(expected_checksum, 15); 341 | 342 | int len = outlen; 343 | int32_t code = length == 0x14 ? DBUF_EVENT_PMS3003 : DBUF_EVENT_PMS5003; 344 | uint32_t new_segment = dbuf_append(last_segment, code, outbuf, len, 1); 345 | if (new_segment == last_segment) { 346 | /* Commit the values logged. Note this is the only task 347 | * accessing this state so these updates are synchronized with 348 | * the last event of this class append. */ 349 | last_pm1a = pm1a; 350 | last_pm25ad = pm25ad; 351 | last_pm10ad = pm10ad; 352 | last_pm1b = pm1b; 353 | last_pm25bd = pm25bd; 354 | last_pm10bd = pm10bd; 355 | last_c1d = c1d; 356 | last_c2d = c2d; 357 | last_c3d = c3d; 358 | last_c4d = c4d; 359 | last_c5d = c5d; 360 | last_c6 = c6; 361 | last_r1 = r1; 362 | break; 363 | } 364 | 365 | /* Moved on to a new buffer. Reset the delta encoding state and 366 | * retry. */ 367 | last_segment = new_segment; 368 | last_pm1a = 0; 369 | last_pm25ad = 0; 370 | last_pm10ad = 0; 371 | last_pm1b = 0; 372 | last_pm25bd = 0; 373 | last_pm10bd = 0; 374 | last_c1d = 0; 375 | last_c2d = 0; 376 | last_c3d = 0; 377 | last_c4d = 0; 378 | last_c5d = 0; 379 | last_c6 = 0; 380 | last_r1 = 0; 381 | }; 382 | 383 | blink_green(); 384 | } 385 | } 386 | 387 | void init_pms() 388 | { 389 | if (param_pms_uart) { 390 | if (param_pms_uart == 2) { 391 | /* For the benefit of the nodemcu board allow swapping uart0 pins. */ 392 | sdk_system_uart_swap(); 393 | } 394 | uart_set_baud(0, 9600); 395 | xTaskCreate(&pms_read_task, "PMS reader", 272, NULL, 11, NULL); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /sha3.c: -------------------------------------------------------------------------------- 1 | /* 2 | Implementation by the Keccak, Keyak and Ketje Teams, namely, Guido Bertoni, 3 | Joan Daemen, Michaël Peeters, Gilles Van Assche and Ronny Van Keer, hereby 4 | denoted as "the implementer". 5 | 6 | For more information, feedback or questions, please refer to our websites: 7 | http://keccak.noekeon.org/ 8 | http://keyak.noekeon.org/ 9 | http://ketje.noekeon.org/ 10 | 11 | To the extent possible under law, the implementer has waived all copyright 12 | and related or neighboring rights to the source code in this file. 13 | http://creativecommons.org/publicdomain/zero/1.0/ 14 | */ 15 | 16 | /* 17 | ================================================================ 18 | The purpose of this source file is to demonstrate a readable and compact 19 | implementation of all the Keccak instances approved in the FIPS 202 standard, 20 | including the hash functions and the extendable-output functions (XOFs). 21 | 22 | We focused on clarity and on source-code compactness, 23 | rather than on the performance. 24 | 25 | The advantages of this implementation are: 26 | + The source code is compact, after removing the comments, that is. :-) 27 | + There are no tables with arbitrary constants. 28 | + For clarity, the comments link the operations to the specifications using 29 | the same notation as much as possible. 30 | + There is no restriction in cryptographic features. In particular, 31 | the SHAKE128 and SHAKE256 XOFs can produce any output length. 32 | + The code does not use much RAM, as all operations are done in place. 33 | 34 | The drawbacks of this implementation are: 35 | - There is no message queue. The whole message must be ready in a buffer. 36 | - It is not optimized for peformance. 37 | 38 | The implementation is even simpler on a little endian platform. Just define the 39 | LITTLE_ENDIAN symbol in that case. 40 | 41 | For a more complete set of implementations, please refer to 42 | the Keccak Code Package at https://github.com/gvanas/KeccakCodePackage 43 | 44 | For more information, please refer to: 45 | * [Keccak Reference] http://keccak.noekeon.org/Keccak-reference-3.0.pdf 46 | * [Keccak Specifications Summary] http://keccak.noekeon.org/specs_summary.html 47 | 48 | This file uses UTF-8 encoding, as some comments use Greek letters. 49 | ================================================================ 50 | */ 51 | 52 | /** 53 | * Function to compute the Keccak[r, c] sponge function over a given input. 54 | * @param rate The value of the rate r. 55 | * @param capacity The value of the capacity c. 56 | * @param input Pointer to the input message. 57 | * @param inputByteLen The number of input bytes provided in the input message. 58 | * @param delimitedSuffix Bits that will be automatically appended to the end 59 | * of the input message, as in domain separation. 60 | * This is a byte containing from 0 to 7 bits 61 | * These n bits must be in the least significant bit positions 62 | * and must be delimited with a bit 1 at position n 63 | * (counting from 0=LSB to 7=MSB) and followed by bits 0 64 | * from position n+1 to position 7. 65 | * Some examples: 66 | * - If no bits are to be appended, then @a delimitedSuffix must be 0x01. 67 | * - If the 2-bit sequence 0,1 is to be appended (as for SHA3-*), @a delimitedSuffix must be 0x06. 68 | * - If the 4-bit sequence 1,1,1,1 is to be appended (as for SHAKE*), @a delimitedSuffix must be 0x1F. 69 | * - If the 7-bit sequence 1,1,0,1,0,0,0 is to be absorbed, @a delimitedSuffix must be 0x8B. 70 | * @param output Pointer to the buffer where to store the output. 71 | * @param outputByteLen The number of output bytes desired. 72 | * @pre One must have r+c=1600 and the rate a multiple of 8 bits in this implementation. 73 | */ 74 | void Keccak(unsigned int rate, unsigned int capacity, const unsigned char *input, unsigned long long int inputByteLen, unsigned char delimitedSuffix, unsigned char *output, unsigned long long int outputByteLen); 75 | 76 | /** 77 | * Function to compute SHAKE128 on the input message with any output length. 78 | */ 79 | void FIPS202_SHAKE128(const unsigned char *input, unsigned int inputByteLen, unsigned char *output, int outputByteLen) 80 | { 81 | Keccak(1344, 256, input, inputByteLen, 0x1F, output, outputByteLen); 82 | } 83 | 84 | /** 85 | * Function to compute SHAKE256 on the input message with any output length. 86 | */ 87 | void FIPS202_SHAKE256(const unsigned char *input, unsigned int inputByteLen, unsigned char *output, int outputByteLen) 88 | { 89 | Keccak(1088, 512, input, inputByteLen, 0x1F, output, outputByteLen); 90 | } 91 | 92 | /** 93 | * Function to compute SHA3-224 on the input message. The output length is fixed to 28 bytes. 94 | */ 95 | void FIPS202_SHA3_224(const unsigned char *input, unsigned int inputByteLen, unsigned char *output) 96 | { 97 | Keccak(1152, 448, input, inputByteLen, 0x06, output, 28); 98 | } 99 | 100 | /** 101 | * Function to compute SHA3-256 on the input message. The output length is fixed to 32 bytes. 102 | */ 103 | void FIPS202_SHA3_256(const unsigned char *input, unsigned int inputByteLen, unsigned char *output) 104 | { 105 | Keccak(1088, 512, input, inputByteLen, 0x06, output, 32); 106 | } 107 | 108 | /** 109 | * Function to compute SHA3-384 on the input message. The output length is fixed to 48 bytes. 110 | */ 111 | void FIPS202_SHA3_384(const unsigned char *input, unsigned int inputByteLen, unsigned char *output) 112 | { 113 | Keccak(832, 768, input, inputByteLen, 0x06, output, 48); 114 | } 115 | 116 | /** 117 | * Function to compute SHA3-512 on the input message. The output length is fixed to 64 bytes. 118 | */ 119 | void FIPS202_SHA3_512(const unsigned char *input, unsigned int inputByteLen, unsigned char *output) 120 | { 121 | Keccak(576, 1024, input, inputByteLen, 0x06, output, 64); 122 | } 123 | 124 | /* 125 | ================================================================ 126 | Technicalities 127 | ================================================================ 128 | */ 129 | 130 | typedef unsigned char UINT8; 131 | typedef unsigned long long int UINT64; 132 | typedef UINT64 tKeccakLane; 133 | 134 | #ifndef LITTLE_ENDIAN 135 | /** Function to load a 64-bit value using the little-endian (LE) convention. 136 | * On a LE platform, this could be greatly simplified using a cast. 137 | */ 138 | static UINT64 load64(const UINT8 *x) 139 | { 140 | int i; 141 | UINT64 u=0; 142 | 143 | for(i=7; i>=0; --i) { 144 | u <<= 8; 145 | u |= x[i]; 146 | } 147 | return u; 148 | } 149 | 150 | /** Function to store a 64-bit value using the little-endian (LE) convention. 151 | * On a LE platform, this could be greatly simplified using a cast. 152 | */ 153 | static void store64(UINT8 *x, UINT64 u) 154 | { 155 | unsigned int i; 156 | 157 | for(i=0; i<8; ++i) { 158 | x[i] = u; 159 | u >>= 8; 160 | } 161 | } 162 | 163 | /** Function to XOR into a 64-bit value using the little-endian (LE) convention. 164 | * On a LE platform, this could be greatly simplified using a cast. 165 | */ 166 | static void xor64(UINT8 *x, UINT64 u) 167 | { 168 | unsigned int i; 169 | 170 | for(i=0; i<8; ++i) { 171 | x[i] ^= u; 172 | u >>= 8; 173 | } 174 | } 175 | #endif 176 | 177 | /* 178 | ================================================================ 179 | A readable and compact implementation of the Keccak-f[1600] permutation. 180 | ================================================================ 181 | */ 182 | 183 | #define ROL64(a, offset) ((((UINT64)a) << offset) ^ (((UINT64)a) >> (64-offset))) 184 | #define i(x, y) ((x)+5*(y)) 185 | 186 | #ifdef LITTLE_ENDIAN 187 | #define readLane(x, y) (((tKeccakLane*)state)[i(x, y)]) 188 | #define writeLane(x, y, lane) (((tKeccakLane*)state)[i(x, y)]) = (lane) 189 | #define XORLane(x, y, lane) (((tKeccakLane*)state)[i(x, y)]) ^= (lane) 190 | #else 191 | #define readLane(x, y) load64((UINT8*)state+sizeof(tKeccakLane)*i(x, y)) 192 | #define writeLane(x, y, lane) store64((UINT8*)state+sizeof(tKeccakLane)*i(x, y), lane) 193 | #define XORLane(x, y, lane) xor64((UINT8*)state+sizeof(tKeccakLane)*i(x, y), lane) 194 | #endif 195 | 196 | /** 197 | * Function that computes the linear feedback shift register (LFSR) used to 198 | * define the round constants (see [Keccak Reference, Section 1.2]). 199 | */ 200 | int LFSR86540(UINT8 *LFSR) 201 | { 202 | int result = ((*LFSR) & 0x01) != 0; 203 | if (((*LFSR) & 0x80) != 0) 204 | /* Primitive polynomial over GF(2): x^8+x^6+x^5+x^4+1 */ 205 | (*LFSR) = ((*LFSR) << 1) ^ 0x71; 206 | else 207 | (*LFSR) <<= 1; 208 | return result; 209 | } 210 | 211 | /** 212 | * Function that computes the Keccak-f[1600] permutation on the given state. 213 | */ 214 | void KeccakF1600_StatePermute(void *state) 215 | { 216 | unsigned int round, x, y, j, t; 217 | UINT8 LFSRstate = 0x01; 218 | 219 | for(round=0; round<24; round++) { 220 | { /* === θ step (see [Keccak Reference, Section 2.3.2]) === */ 221 | tKeccakLane C[5], D; 222 | 223 | /* Compute the parity of the columns */ 224 | for(x=0; x<5; x++) 225 | C[x] = readLane(x, 0) ^ readLane(x, 1) ^ readLane(x, 2) ^ readLane(x, 3) ^ readLane(x, 4); 226 | for(x=0; x<5; x++) { 227 | /* Compute the θ effect for a given column */ 228 | D = C[(x+4)%5] ^ ROL64(C[(x+1)%5], 1); 229 | /* Add the θ effect to the whole column */ 230 | for (y=0; y<5; y++) 231 | XORLane(x, y, D); 232 | } 233 | } 234 | 235 | { /* === ρ and π steps (see [Keccak Reference, Sections 2.3.3 and 2.3.4]) === */ 236 | tKeccakLane current, temp; 237 | /* Start at coordinates (1 0) */ 238 | x = 1; y = 0; 239 | current = readLane(x, y); 240 | /* Iterate over ((0 1)(2 3))^t * (1 0) for 0 ≤ t ≤ 23 */ 241 | for(t=0; t<24; t++) { 242 | /* Compute the rotation constant r = (t+1)(t+2)/2 */ 243 | unsigned int r = ((t+1)*(t+2)/2)%64; 244 | /* Compute ((0 1)(2 3)) * (x y) */ 245 | unsigned int Y = (2*x+3*y)%5; x = y; y = Y; 246 | /* Swap current and state(x,y), and rotate */ 247 | temp = readLane(x, y); 248 | writeLane(x, y, ROL64(current, r)); 249 | current = temp; 250 | } 251 | } 252 | 253 | { /* === χ step (see [Keccak Reference, Section 2.3.1]) === */ 254 | tKeccakLane temp[5]; 255 | for(y=0; y<5; y++) { 256 | /* Take a copy of the plane */ 257 | for(x=0; x<5; x++) 258 | temp[x] = readLane(x, y); 259 | /* Compute χ on the plane */ 260 | for(x=0; x<5; x++) 261 | writeLane(x, y, temp[x] ^((~temp[(x+1)%5]) & temp[(x+2)%5])); 262 | } 263 | } 264 | 265 | { /* === ι step (see [Keccak Reference, Section 2.3.5]) === */ 266 | for(j=0; j<7; j++) { 267 | unsigned int bitPosition = (1< 283 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 284 | 285 | void Keccak(unsigned int rate, unsigned int capacity, const unsigned char *input, unsigned long long int inputByteLen, unsigned char delimitedSuffix, unsigned char *output, unsigned long long int outputByteLen) 286 | { 287 | UINT8 state[200]; 288 | unsigned int rateInBytes = rate/8; 289 | unsigned int blockSize = 0; 290 | unsigned int i; 291 | 292 | if (((rate + capacity) != 1600) || ((rate % 8) != 0)) 293 | return; 294 | 295 | /* === Initialize the state === */ 296 | memset(state, 0, sizeof(state)); 297 | 298 | /* === Absorb all the input blocks === */ 299 | while(inputByteLen > 0) { 300 | blockSize = MIN(inputByteLen, rateInBytes); 301 | for(i=0; i 0) { 325 | blockSize = MIN(outputByteLen, rateInBytes); 326 | memcpy(output, state, blockSize); 327 | output += blockSize; 328 | outputByteLen -= blockSize; 329 | 330 | if (outputByteLen > 0) 331 | KeccakF1600_StatePermute(state); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /buffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Particle counter data logger. 3 | * Memory resident (RAM) buffer support. 4 | * 5 | * Copyright (C) 2016, 2017 OurAirQuality.org 6 | * 7 | * Licensed under the Apache License, Version 2.0, January 2004 (the 8 | * "License"); you may not use this file except in compliance with the 9 | * License. You may obtain a copy of the License at 10 | * http://www.apache.org/licenses/ 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 16 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS WITH THE SOFTWARE. 20 | * 21 | * 22 | * Data is collected in 4096 byte buffers, the same size as the flash sectors, 23 | * and then saved to the flash storage, and then posted to a server. 24 | * 25 | * Separate tasks: 1. read the UART appending events to a ring of memory 26 | * resident buffers; 2. save these buffers to flash sectors; 3. HTTP-Post these 27 | * sectors to a server. 28 | * 29 | * Within each buffer the values may be compressed, but each buffer stands 30 | * alone. 31 | * 32 | * Unused bytes in the buffer are filled with ones bits to support writing to 33 | * flash multiple times for saving partial buffers. The buffer encoding must 34 | * allow recovery of the entries from these ones-filled buffers, which requires 35 | * that each entry have at least one zero bit. 36 | * 37 | * Events added to the buffer have a time stamp which is delta encoded. These 38 | * are not required to be real times and are expected to be synchronized to 39 | * external events such as successful posts to a server. 40 | */ 41 | 42 | #include "espressif/esp_common.h" 43 | #include "espressif/esp_system.h" 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include "FreeRTOS.h" 53 | #include "task.h" 54 | #include "semphr.h" 55 | 56 | #include "buffer.h" 57 | #include "config.h" 58 | #include "leds.h" 59 | #include "flash.h" 60 | #include "web.h" 61 | #include "push.h" 62 | #include "pms.h" 63 | #include "i2c.h" 64 | #include "sht21.h" 65 | #include "bmp180.h" 66 | #include "bme280.h" 67 | #include "ds3231.h" 68 | 69 | 70 | 71 | #define DBUF_DATA_SIZE 4096 72 | 73 | typedef struct { 74 | /* The size of the filled data bytes. */ 75 | uint32_t size; 76 | /* The size that has been saved successfully. */ 77 | uint32_t save_size; 78 | /* Time-stamp of the first event written to the buffer after the last save, 79 | * or the time of the oldest event not saved. */ 80 | uint32_t write_time; 81 | /* The data. Initialized to all ones bits (0xff). The first two 32 bit words 82 | * are an unique index that is monotonically increasing. The second copy is 83 | * for redundancy and is inverted to help catch errors when saved to 84 | * flash. */ 85 | uint8_t data[DBUF_DATA_SIZE]; 86 | } dbuf_t; 87 | 88 | /* 89 | * The buffers are managed as a ring-buffer. If the oldest data is not saved in 90 | * time then it is discarded. 91 | */ 92 | 93 | #define NUM_DBUFS 2 94 | 95 | static dbuf_t dbufs[NUM_DBUFS]; 96 | 97 | /* The current data is written to the dbufs_head buffer. */ 98 | static uint32_t dbufs_head; 99 | /* The oldest buffer is the dbufs_tail, which is equal to the dbufs_head if 100 | * there is only one live buffer. */ 101 | static uint32_t dbufs_tail; 102 | 103 | /* To synchronize access to the data buffers. */ 104 | static SemaphoreHandle_t dbufs_sem; 105 | 106 | /* 107 | * Logging to the data buffers can be disabled by clearing this variable, and 108 | * this is the start of the data flow so it stops more data entering. 109 | */ 110 | static bool dbuf_logging_enabled = false; 111 | 112 | /* Return the index for the buffer number. */ 113 | static uint32_t dbuf_index(uint32_t num) 114 | { 115 | uint32_t *words = (uint32_t *)(dbufs[num].data); 116 | uint32_t index = words[0]; 117 | return index; 118 | } 119 | 120 | uint32_t dbuf_head_index() { 121 | xSemaphoreTake(dbufs_sem, portMAX_DELAY); 122 | uint32_t index = dbuf_index(dbufs_head); 123 | xSemaphoreGive(dbufs_sem); 124 | return index; 125 | } 126 | 127 | static void set_dbuf_index(uint32_t num, uint32_t index) 128 | { 129 | uint32_t *words = (uint32_t *)(dbufs[num].data); 130 | words[0] = index; 131 | words[1] = index ^ 0xffffffff; 132 | } 133 | 134 | static void initialize_dbuf(uint32_t num) 135 | { 136 | dbuf_t *dbuf = &dbufs[num]; 137 | dbuf->size = 0; 138 | dbuf->save_size = 0; 139 | uint32_t time = RTC.COUNTER; 140 | dbuf->write_time = time; 141 | memset(dbuf->data, 0xff, DBUF_DATA_SIZE); 142 | } 143 | 144 | bool get_buffer_logging() { 145 | return dbuf_logging_enabled; 146 | } 147 | 148 | bool set_buffer_logging(bool enable) { 149 | /* If logging is being paused then note this event before pausing. It is 150 | * possible that a few other log entries are added after this. */ 151 | if (dbuf_logging_enabled && !enable) { 152 | uint32_t last_segment = 0; 153 | while (1) { 154 | uint32_t new_segment = dbuf_append(last_segment, DBUF_EVENT_PAUSE_LOGGING, 155 | NULL, 0, 1); 156 | if (new_segment == last_segment) 157 | break; 158 | last_segment = new_segment; 159 | } 160 | } 161 | 162 | xSemaphoreTake(dbufs_sem, portMAX_DELAY); 163 | bool old_value = dbuf_logging_enabled; 164 | dbuf_logging_enabled = enable; 165 | xSemaphoreGive(dbufs_sem); 166 | 167 | /* If logging has just been enabled then log this event along with an RTC 168 | * calibration. Otherwise, if the device started up with logging disabled 169 | * then there would be no startup event to give an RTC calibration. */ 170 | if (!old_value && enable) { 171 | uint32_t restart[1]; 172 | /* Include a RTC calibration, and average a few calls as it seems rather 173 | * noisy. */ 174 | restart[0] = 0; 175 | for (int i = 0; i < 32; i++) 176 | restart[0] += sdk_system_rtc_clock_cali_proc(); 177 | restart[0] >>= 5; 178 | uint32_t last_segment = 0; 179 | while (1) { 180 | uint32_t new_segment = dbuf_append(last_segment, DBUF_EVENT_START_LOGGING, 181 | (void *)restart, sizeof(restart), 1); 182 | if (new_segment == last_segment) 183 | break; 184 | last_segment = new_segment; 185 | } 186 | } 187 | 188 | return old_value; 189 | } 190 | 191 | 192 | /* Emit an unsigned leb128. */ 193 | uint32_t emit_leb128(uint8_t *buf, uint32_t start, uint64_t v) 194 | { 195 | while (1) { 196 | if (v < 0x80) { 197 | buf[start++] = v; 198 | return start; 199 | } 200 | buf[start++] = (v & 0x7f) | 0x80; 201 | v >>= 7; 202 | } 203 | } 204 | 205 | /* Emit a signed leb128. */ 206 | uint32_t emit_leb128_signed(uint8_t *buf, uint32_t start, int64_t v) 207 | { 208 | while (1) { 209 | if (-0x40 <= v && v <= 0x3f) { 210 | buf[start++] = v & 0x7f; 211 | return start; 212 | } 213 | buf[start++] = (v & 0x7f) | 0x80; 214 | v >>= 7; 215 | } 216 | } 217 | 218 | /* 219 | * Append an event to the buffers. This function firstly emits the common event 220 | * header including the event code, size, and the time stamp using the RTC 221 | * counter. 222 | * 223 | * Emitting the code and length here supports skipping unrecognized events. 224 | * 225 | * Emitting the time here keeps the times increasing, whereas if the caller 226 | * emitted the time then multiple callers might race to append their events and 227 | * the times might not be in order. 228 | * 229 | * When the low_res_time option is selected some of the time low bits are 230 | * allowed to be zeroed, effectively moving the event back in time a 231 | * little. This can support a more compact encoding for the time. The time is 232 | * only truncated when it does not cause a backward step in time since the last 233 | * time-stamp. 234 | * 235 | * If the caller wishes to avoid redundantly repeated entries then it should 236 | * implement that logic itself. That is not possible here in general as 237 | * dropping entries would invalidate the callers delta encoding. 238 | * 239 | * If entries are dropped due to logging being disabled then that will break the 240 | * callers delta encoding, so a segment restart is flagged in that case. 241 | * 242 | * The append operation might fail if there is not room, and the caller is 243 | * expected to retry. Each buffer stands alone, so delta encoding needs to be 244 | * reset for each new buffer, and if the delta encoding changes then the encoded 245 | * data size might change too so the caller needs to re-encode the event 246 | * data. The caller needs to know when the buffer has changed to reset the state 247 | * and to do this the segment index is passed in an if not the current segment 248 | * index then the append aborts and the current segment index is returned. 249 | */ 250 | static uint32_t current_segment; 251 | static bool dbuf_stream_restart_required; 252 | static int32_t last_code; 253 | static int32_t last_size; 254 | static uint32_t last_time; 255 | 256 | uint32_t dbuf_append(uint32_t segment, uint16_t code, uint8_t *data, uint32_t size, 257 | int low_res_time) 258 | { 259 | xSemaphoreTake(dbufs_sem, portMAX_DELAY); 260 | 261 | if (!dbuf_logging_enabled) { 262 | /* An entry is being dropped, and might have been delta encoded, so note 263 | * that a segment restart is needed. The segment index will be advanced 264 | * but there is no need to advance it here now, and the callers can keep 265 | * using the current segment index - the output is just being 266 | * discarded. When the stream restarts the segment index will change and 267 | * callers will then need to reset their delta encoding state. */ 268 | dbuf_stream_restart_required = true; 269 | 270 | xSemaphoreGive(dbufs_sem); 271 | 272 | /* 273 | * Continue to wakeup the flash_data task, even if new data is not 274 | * being accepted into the data buffers. 275 | */ 276 | if (flash_data_task) 277 | xTaskNotify(flash_data_task, 0, eNoAction); 278 | 279 | /* Consume it to allow the caller to proceed. */ 280 | return segment; 281 | } 282 | 283 | /* A stream restart is required. */ 284 | if (dbuf_stream_restart_required) { 285 | /* Reset the prior-event state. */ 286 | last_code = 0; 287 | last_size = 0; 288 | last_time = 0; 289 | /* Advance the segment index. */ 290 | current_segment++; 291 | /* An entry needs to be added to the stream log now, so hijack this call 292 | * and the caller will retry as the segment index has advanced. If there 293 | * is no room for this entry then it will advance to the next buffer 294 | * which resets the state anyway. The segment restart flag can be 295 | * cleared now as all exits either log a restart event or roll over to a 296 | * new buffer. */ 297 | dbuf_stream_restart_required = false; 298 | if (segment == current_segment) { 299 | printf("Error: unexpected segment index\n"); 300 | } 301 | segment = current_segment; 302 | code = DBUF_EVENT_SEGMENT_START; 303 | data = NULL; 304 | size = 0; 305 | low_res_time = 1; 306 | } 307 | 308 | if (segment != current_segment) { 309 | /* The stream has been interrupted, so the caller must reset any delta 310 | * encoding state and retry. */ 311 | segment = current_segment; 312 | xSemaphoreGive(dbufs_sem); 313 | return segment; 314 | } 315 | 316 | uint32_t time = RTC.COUNTER; 317 | /* Space to emit the code, size, and time. */ 318 | uint8_t header[15]; 319 | uint32_t header_size = 0; 320 | 321 | if (low_res_time) { 322 | /* Protect against stepping backwards in time, which would look like 323 | * wrapping which would be a big step forward in time. If the low bits 324 | * of the last_time are zero then truncating the current time low bits 325 | * can not step backwards. If the significant bits of the last_time and 326 | * current time are not equal then it is also safe. */ 327 | if ((last_time & 0x00001fff) == 0 || 328 | (last_time & 0xffffe000) != (time & 0xffffe000)) { 329 | /* Truncate the time, don't need all the precision. Note that the 330 | * time delta low bits will not necessarily be zero for this event, 331 | * but if the following event also uses a low_res_time then the time 332 | * delta low bits will be zero then. */ 333 | time = time & 0xffffe000; 334 | } 335 | } 336 | 337 | /* The time is always at least delta encoded, mod32. */ 338 | uint32_t time_delta = time - last_time; 339 | 340 | /* The first two bits, the two lsb, encode the header format. 341 | * 342 | * Bit 0: 343 | * 0 = same code and size as last event. 344 | * 1 = leb128 event code (two low bits removed), and size. 345 | * 346 | * Bit 1: 347 | * 0 = leb128 time delta. 348 | * 1 = leb128 truncated time delta. 349 | * 350 | * The event code must have one zero bit in the first 5 bits to ensure that 351 | * the first byte always has one zero bit if there is an event, and that 352 | * 0xff terminates the event log. 353 | */ 354 | 355 | if (code == last_code && size == last_size) { 356 | if ((time_delta & 0x00001fff) == 0) { 357 | uint64_t v = time_delta >> (13 - 2) | 2 | 0; 358 | header_size = emit_leb128(header, header_size, v); 359 | } else { 360 | uint64_t v = (uint64_t)time_delta << 2UL | 0 | 0; 361 | header_size = emit_leb128(header, header_size, v); 362 | } 363 | } else { 364 | if ((time_delta & 0x00001fff) == 0) { 365 | header_size = emit_leb128(header, header_size, (code << 2) | 2 | 1); 366 | header_size = emit_leb128(header, header_size, size); 367 | uint64_t v = time_delta >> 13; 368 | header_size = emit_leb128(header, header_size, v); 369 | } else { 370 | header_size = emit_leb128(header, header_size, (code << 2) | 0 | 1); 371 | header_size = emit_leb128(header, header_size, size); 372 | uint64_t v = (uint64_t)time_delta; 373 | header_size = emit_leb128(header, header_size, v); 374 | } 375 | } 376 | 377 | uint32_t total_size = header_size + size; 378 | 379 | /* Guard against logging data too big to fit in any buffer. */ 380 | if (total_size > DBUF_DATA_SIZE - 8) { 381 | xSemaphoreGive(dbufs_sem); 382 | /* Consume it to clear the error. This will break delta encoding for the 383 | * caller, but this is an exceptional path that should not occur in 384 | * normal operation. */ 385 | printf("Error: data too large to buffer?\n"); 386 | return segment; 387 | } 388 | 389 | /* Check if there is room in the current buffer. */ 390 | dbuf_t *head = &dbufs[dbufs_head]; 391 | if (head->size + total_size > DBUF_DATA_SIZE) { 392 | /* Full, move to the next buffer. */ 393 | uint32_t index = dbuf_index(dbufs_head) + 1; 394 | /* Reuse the head buffer if it is the only active buffer and its data 395 | * has been saved. This check prevents a saved buffer being retained 396 | * which would break an assumed invariant. */ 397 | if (dbufs_head != dbufs_tail || head->size != head->save_size) { 398 | /* Can not reuse the head buffer. */ 399 | dbufs_head++; 400 | if (dbufs_head >= NUM_DBUFS) 401 | dbufs_head = 0; 402 | if (dbufs_head == dbufs_tail) { 403 | /* Wrapped, discard the tail buffer. */ 404 | dbufs_tail++; 405 | if (dbufs_tail >= NUM_DBUFS) 406 | dbufs_tail = 0; 407 | } 408 | head = &dbufs[dbufs_head]; 409 | } 410 | initialize_dbuf(dbufs_head); 411 | set_dbuf_index(dbufs_head, index); 412 | head->size = 8; 413 | /* Reset the prior-event state. */ 414 | last_code = 0; 415 | last_size = 0; 416 | last_time = 0; 417 | /* Advance the segment index. The caller, and other callers using the 418 | * old segment, must reset any delta encoding state and retry. */ 419 | segment = current_segment + 1; 420 | current_segment = segment; 421 | /* Clear the segment restart flag, as it is no longer necessary. */ 422 | dbuf_stream_restart_required = false; 423 | xSemaphoreGive(dbufs_sem); 424 | return segment; 425 | } 426 | 427 | /* Reset the write time if this is the first real write to the buffer, or 428 | * the first write since the last save. This prevents an immediate or early 429 | * save of new content added. */ 430 | if (head->size <= 8 || head->size == head->save_size) 431 | head->write_time = time; 432 | 433 | /* Emit the event header. */ 434 | uint32_t i; 435 | for (i = 0; i < header_size; i++) 436 | head->data[head->size + i] = header[i]; 437 | /* Emit the event data. */ 438 | for (i = 0; i < size; i++) 439 | head->data[head->size + header_size + i] = data[i]; 440 | 441 | head->size += total_size; 442 | 443 | last_code = code; 444 | last_size = size; 445 | last_time = time; 446 | 447 | xSemaphoreGive(dbufs_sem); 448 | 449 | /* Wakeup the flash_data task. */ 450 | if (flash_data_task) 451 | xTaskNotify(flash_data_task, 0, eNoAction); 452 | 453 | return segment; 454 | } 455 | 456 | /* 457 | * Search for a buffer to write to flash. Fill the target buffer and return the 458 | * size currently used if there is something to send, otherwise return zero. If 459 | * some of the buffer has been successfully posted then the start of the 460 | * non-written elements is set. The full buffer is always copied, to get the 461 | * trailing ones, and because the flash write might fail and the entire buffer 462 | * might need to be re-written to the next flash sector. 463 | * 464 | * The buffers are always returned in the order of their index, so this starts 465 | * searching at the tail of the buffer FIFO, and if nothing else then see if the 466 | * current buffer could be usefully saved. 467 | * 468 | * A copy of the buffer is made to allow the dbufs_sem to be released quickly. 469 | * 470 | * On success note_buffer_written() should be called to allow the buffer to be 471 | * freed and reused, and the index is at the head of the buffer. 472 | * 473 | * It is assumed that the memory resident buffers are saved well before the RTC 474 | * time used here can wrap. 475 | */ 476 | 477 | uint32_t get_buffer_to_write(uint8_t *buf, uint32_t *start) 478 | { 479 | uint32_t size = 0; 480 | 481 | xSemaphoreTake(dbufs_sem, portMAX_DELAY); 482 | 483 | if (dbufs_tail != dbufs_head) { 484 | dbuf_t *dbuf = &dbufs[dbufs_tail]; 485 | if (dbuf->size > dbuf->save_size) { 486 | uint32_t j; 487 | size = dbuf->size; 488 | for (j = 0; j < DBUF_DATA_SIZE; j++) 489 | buf[j] = dbuf->data[j]; 490 | *start = dbuf->save_size; 491 | xSemaphoreGive(dbufs_sem); 492 | return size; 493 | } 494 | xSemaphoreGive(dbufs_sem); 495 | return 0; 496 | } 497 | 498 | /* Otherwise check if the head buffer needs to be saved. Don't bother 499 | * saving a sector with only an index. */ 500 | dbuf_t *head = &dbufs[dbufs_head]; 501 | if (head->size > 8 && head->size > head->save_size) { 502 | uint32_t delta = RTC.COUNTER - head->write_time; 503 | // Currently about 120 seconds. 504 | if (delta > 20000000) { 505 | uint32_t j; 506 | size = head->size; 507 | for (j = 0; j < DBUF_DATA_SIZE; j++) 508 | buf[j] = head->data[j]; 509 | *start = head->save_size; 510 | xSemaphoreGive(dbufs_sem); 511 | return size; 512 | } 513 | } 514 | 515 | xSemaphoreGive(dbufs_sem); 516 | return 0; 517 | } 518 | 519 | /* 520 | * Callback to note that a buffer has been successfully written to flash 521 | * storage. The buffer index is passed in to locate the buffer. The size is 522 | * passed in to update the successfully saved size, and is the total size saved, 523 | * not an incremental update. 524 | * 525 | * The buffer might have been filled more and even moved from being the head to 526 | * a non-head buffer, so the size is used to check if all the buffer has been 527 | * saved and only then can it be freed. The head buffer is never freed as it 528 | * likely has room for more events. 529 | * 530 | * The buffer might have wrapped and been re-used, in which case it has already 531 | * been freed. 532 | */ 533 | void note_buffer_written(uint32_t index, uint32_t size) 534 | { 535 | xSemaphoreTake(dbufs_sem, portMAX_DELAY); 536 | 537 | uint32_t i = dbufs_tail; 538 | while (1) { 539 | if (dbuf_index(i) == index) 540 | break; 541 | if (i == dbufs_head) { 542 | /* Did not find the index, possibly wrapped already so give up. */ 543 | xSemaphoreGive(dbufs_sem); 544 | return; 545 | } 546 | if (++i >= NUM_DBUFS) 547 | i = 0; 548 | } 549 | 550 | /* Update the save_size */ 551 | dbufs[i].save_size = size; 552 | 553 | /* Update the write_time here. More content might have been saved so this 554 | * might be a little late for some content, but that would only delay the 555 | * next write a little. If this is not a head buffer then the time is not 556 | * even used, rather just the save_size. */ 557 | dbufs[i].write_time = RTC.COUNTER; 558 | 559 | /* Free saved buffers from the tail. */ 560 | for (; dbufs_tail != dbufs_head; ) { 561 | dbuf_t *dbuf = &dbufs[dbufs_tail]; 562 | if (dbuf->save_size == dbuf->size) { 563 | dbufs_tail++; 564 | if (dbufs_tail >= NUM_DBUFS) 565 | dbufs_tail = 0; 566 | } else { 567 | break; 568 | } 569 | } 570 | 571 | xSemaphoreGive(dbufs_sem); 572 | blink_blue(); 573 | } 574 | 575 | /* 576 | * Reset the buffers, discarding any data in them. The current segment index is 577 | * is not reset here but dbuf_stream_restart_required is set. 578 | */ 579 | void reset_dbuf() 580 | { 581 | xSemaphoreTake(dbufs_sem, portMAX_DELAY); 582 | 583 | dbufs_head = 0; 584 | dbufs_tail = 0; 585 | initialize_dbuf(dbufs_head); 586 | set_dbuf_index(dbufs_head, 0); 587 | dbufs[dbufs_head].size = 8; 588 | 589 | last_code = 0; 590 | last_size = 0; 591 | last_time = 0; 592 | dbuf_stream_restart_required = true; 593 | 594 | xSemaphoreGive(dbufs_sem); 595 | } 596 | 597 | 598 | 599 | void user_init(void) 600 | { 601 | init_params(); 602 | 603 | init_i2c(); 604 | 605 | dbufs_head = 0; 606 | dbufs_tail = 0; 607 | initialize_dbuf(dbufs_head); 608 | uint32_t last_index = init_flash(); 609 | set_dbuf_index(dbufs_head, last_index); 610 | dbufs[dbufs_head].size = 8; 611 | 612 | current_segment = 0; 613 | last_code = 0; 614 | last_size = 0; 615 | last_time = 0; 616 | 617 | dbufs_sem = xSemaphoreCreateMutex(); 618 | 619 | /* Set the flag directly to avoid logging an event. */ 620 | dbuf_logging_enabled = param_logging; 621 | 622 | init_blink(); 623 | blink_red(); 624 | blink_blue(); 625 | blink_green(); 626 | blink_white(); 627 | 628 | /* Log a startup event. */ 629 | uint32_t startup[8 + 1]; 630 | /* Include the SDK reset info. */ 631 | struct sdk_rst_info* reset_info = sdk_system_get_rst_info(); 632 | memcpy(startup, reset_info, sizeof(struct sdk_rst_info)); 633 | /* Include the initial RTC calibration, and average a few calls as it seems 634 | * rather noisy. */ 635 | startup[8] = 0; 636 | for (int i = 0; i < 32; i++) 637 | startup[8] += sdk_system_rtc_clock_cali_proc(); 638 | startup[8] >>= 5; 639 | uint32_t last_segment = current_segment; 640 | while (1) { 641 | uint32_t new_segment = dbuf_append(last_segment, DBUF_EVENT_ESP8266_STARTUP, 642 | (void *)startup, sizeof(startup), 1); 643 | if (new_segment == last_segment) 644 | break; 645 | last_segment = new_segment; 646 | } 647 | 648 | /* Start logging to the RAM buffer immediately. */ 649 | init_pms(); 650 | init_sht2x(); 651 | init_bmp180(); 652 | init_bme280(); 653 | init_ds3231(); 654 | 655 | init_web(); 656 | init_post(); 657 | 658 | xTaskCreate(&flash_data, "OAQ Flash", 196, NULL, 11, &flash_data_task); 659 | } 660 | -------------------------------------------------------------------------------- /push.c: -------------------------------------------------------------------------------- 1 | /* 2 | * HTTP-Post the flash sectors to a server. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/ 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 16 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS WITH THE SOFTWARE. 20 | * 21 | */ 22 | #include "espressif/esp_common.h" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "FreeRTOS.h" 29 | #include "task.h" 30 | #include "semphr.h" 31 | 32 | #include "lwip/err.h" 33 | #include "lwip/sockets.h" 34 | #include "lwip/sys.h" 35 | #include "lwip/netdb.h" 36 | #include "lwip/dns.h" 37 | 38 | #include "buffer.h" 39 | #include "config.h" 40 | #include "flash.h" 41 | #include "sha3.h" 42 | #include "ds3231.h" 43 | #include "leds.h" 44 | 45 | #include "config.h" 46 | 47 | #include "wificfg/wificfg.h" 48 | 49 | 50 | /* For signaling and waiting for data to post. */ 51 | TaskHandle_t post_data_task = NULL; 52 | 53 | /* 54 | * A single buffer is allocated to hold the HTTP data to be sent and it is large 55 | * enough for the HTTP header plus the content including a signature suffix. The 56 | * content is located at a fixed position into the buffer and word aligned so 57 | * the flash data can be copied directly to this buffer. For the computation of 58 | * a MAC-SHA3 signature the key prefix is copied before the data so the prefix 59 | * allocation needs to be large enough for this too. 60 | */ 61 | 62 | #define SIGNATURE_SIZE 28 63 | 64 | /* At least the key_size of 287 bytes, round up gives an alignment 32. */ 65 | #define PREFIX_SIZE 288 66 | 67 | /* 68 | * Sectors are posted in chunks, to limit buffer size here. FIPS202 69 | * SHA3_224 naturally works in block sizes of 144 bytes, so choose two 70 | * of these blocks gives a size of 288 bytes. 71 | */ 72 | #define CHUNK_SIZE 288 73 | 74 | /* Total size here is 620 bytes. */ 75 | #define POST_BUFFER_SIZE (PREFIX_SIZE + 4 + 4 + 4 + 4 + CHUNK_SIZE + SIGNATURE_SIZE) 76 | static uint8_t *post_buf; 77 | 78 | #define MAX_HOLD_OFF_TIME 1800000 /* 30 minutes */ 79 | 80 | static void post_data(void *pvParameters) 81 | { 82 | uint32_t last_segment = 0; 83 | uint32_t last_recv_sec = 0; 84 | 85 | /* 86 | * A retry hold-off time in msec. Reset to zero upon a success and otherwise 87 | * increased on each retry. This is intended avoid loading the network and 88 | * server with retries if there is an error processing a post request. 89 | */ 90 | uint32_t hold_off_time = 0; 91 | 92 | /* 93 | * Can not flag every index that has been sent and how much, so do a scan 94 | * and keep the head state: the index currently being pushed or last pushed, 95 | * the total size of that index, and the amount that has been push, and flag 96 | * if it was known to have been sealed and not open to being extended. 97 | */ 98 | uint32_t index_being_pushed = 0; 99 | uint32_t index_size_being_pushed = 0; 100 | uint32_t index_size_pushed = 0; 101 | bool index_being_pushed_sealed = false; 102 | uint32_t index_being_pushed_next_index = 0xffffffff; 103 | 104 | while (1) { 105 | xTaskNotifyWait(0, 0, NULL, 120000 / portTICK_PERIOD_MS); 106 | 107 | /* Try to flush all the pending buffers before waiting again. */ 108 | while (1) { 109 | int j; 110 | 111 | /* Lightweight check if there is anything to post. */ 112 | if (!maybe_buffer_to_post()) 113 | break; 114 | 115 | /* Hold off if retrying. */ 116 | if (hold_off_time > MAX_HOLD_OFF_TIME) 117 | hold_off_time = MAX_HOLD_OFF_TIME; 118 | vTaskDelay(hold_off_time / portTICK_PERIOD_MS); 119 | hold_off_time += (hold_off_time >> 2) + 1000; 120 | 121 | /* 122 | * Wait until connected, and try connecting to the server before 123 | * requesting the actual data to post as this might take some time 124 | * to succeed and by then there might be much more data to 125 | * send. Also a time is sent and the intention is that it is as 126 | * close to the time posted as possible and it can not be patched in 127 | * just before sending as it is part of the signed message. 128 | */ 129 | 130 | while (1) { 131 | uint8_t connect_status = sdk_wifi_station_get_connect_status(); 132 | if (connect_status == STATION_GOT_IP) 133 | break; 134 | vTaskDelay(1000 / portTICK_PERIOD_MS); 135 | } 136 | 137 | /* 138 | * Notifty wificfg to disable the AP interface on the next restart 139 | * if that option is enabled. 140 | */ 141 | wificfg_got_sta_connect(); 142 | 143 | const struct addrinfo hints = { 144 | .ai_family = AF_INET, 145 | .ai_socktype = SOCK_STREAM, 146 | }; 147 | struct addrinfo *res; 148 | 149 | int err = getaddrinfo(param_web_server, param_web_port, &hints, &res); 150 | if (err != 0 || res == NULL) { 151 | if (res) 152 | freeaddrinfo(res); 153 | continue; 154 | } 155 | 156 | int s = socket(res->ai_family, res->ai_socktype, 0); 157 | if (s < 0) { 158 | freeaddrinfo(res); 159 | continue; 160 | } 161 | 162 | /* Route via the station interface, which is always en0. */ 163 | const struct ifreq ifreq = { "en0" }; 164 | setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)); 165 | 166 | const struct timeval timeout = { 60, 0 }; /* 60 second timeout */ 167 | setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); 168 | setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); 169 | 170 | if (connect(s, res->ai_addr, res->ai_addrlen) != 0) { 171 | close(s); 172 | freeaddrinfo(res); 173 | continue; 174 | } 175 | 176 | freeaddrinfo(res); 177 | 178 | 179 | /* Delay this allocation, to avoid using this memory unless a 180 | * connection is possible. */ 181 | if (!post_buf) { 182 | post_buf = malloc(POST_BUFFER_SIZE); 183 | if (!post_buf) { 184 | close(s); 185 | continue; 186 | } 187 | } 188 | 189 | /* 190 | * Support for pushing the data to the server. 191 | * 192 | * 193 | * For efficiency, want to find an index and total size to send, then 194 | * send it in chunks, and only when this is done to go back and look 195 | * for more in that index or move on and get a new index and size. 196 | */ 197 | 198 | uint32_t size; 199 | do { 200 | size = index_size_being_pushed - index_size_pushed; 201 | if (size > 4096) { 202 | /* Reset to search for the current index. */ 203 | index_being_pushed = 0xffffffff; 204 | index_size_being_pushed = 0; 205 | index_size_pushed = 0; 206 | index_being_pushed_sealed = false; 207 | index_being_pushed_next_index = 0xffffffff; 208 | size = 0; 209 | break; 210 | } 211 | if (size > 0) { 212 | /* More to push in the current known index range. 213 | * Limit the size to the maximum chunk size. */ 214 | if (size > CHUNK_SIZE) { 215 | size = CHUNK_SIZE; 216 | } 217 | if (!get_buffer_range(index_being_pushed, index_size_pushed, 218 | index_size_pushed + size, 219 | &post_buf[PREFIX_SIZE + 16])) { 220 | /* Reset to search for the current index. */ 221 | index_being_pushed = 0xffffffff; 222 | index_size_being_pushed = 0; 223 | index_size_pushed = 0; 224 | index_being_pushed_sealed = false; 225 | index_being_pushed_next_index = 0xffffffff; 226 | size = 0; 227 | } 228 | break; 229 | } 230 | 231 | /* Check if the index range has grown. */ 232 | uint32_t last_index = index_being_pushed; 233 | index_size_being_pushed = get_buffer_size(index_being_pushed, 234 | &index_being_pushed, 235 | &index_being_pushed_next_index, 236 | &index_being_pushed_sealed); 237 | 238 | if (index_being_pushed != last_index) { 239 | /* The index being pushed was not found. This might occur if 240 | * the buffer wraps and erases the sector, but this is 241 | * unlikely. It might also occur if searching for an index 242 | * beyond the head. Push the index found, from the start. */ 243 | index_size_pushed = 0; 244 | } 245 | 246 | if (index_size_being_pushed > index_size_pushed) { 247 | /* Some more data to push now. */ 248 | size = index_size_being_pushed - index_size_pushed; 249 | if (size > CHUNK_SIZE) { 250 | size = CHUNK_SIZE; 251 | } 252 | if (get_buffer_range(index_being_pushed, index_size_pushed, 253 | index_size_pushed + size, 254 | &post_buf[PREFIX_SIZE + 16])) { 255 | /* Done */ 256 | break; 257 | } 258 | 259 | /* This might occur if the buffer wraps and erases the 260 | * sector. Just loop and retry. */ 261 | index_size_being_pushed = 0; 262 | index_size_pushed = 0; 263 | index_being_pushed_sealed = false; 264 | index_being_pushed_next_index = 0xffffffff; 265 | continue; 266 | } 267 | 268 | if (index_size_being_pushed == index_size_pushed) { 269 | /* Nothing more to send for this index */ 270 | size = 0; 271 | if (index_being_pushed_sealed && 272 | index_being_pushed_next_index != 0xffffffff) { 273 | /* Move on to the next index and recheck. */ 274 | index_being_pushed = index_being_pushed_next_index; 275 | index_size_being_pushed = 0; 276 | index_size_pushed = 0; 277 | index_being_pushed_sealed = false; 278 | index_being_pushed_next_index = 0xffffffff; 279 | continue; 280 | } 281 | /* No more data is available, wait. */ 282 | break; 283 | } 284 | 285 | /* Reset to search for the current index. */ 286 | index_being_pushed = 0xffffffff; 287 | index_size_being_pushed = 0; 288 | index_size_pushed = 0; 289 | index_being_pushed_sealed = false; 290 | index_being_pushed_next_index = 0xffffffff; 291 | size = 0; 292 | break; 293 | } while (size == 0); 294 | 295 | if (size == 0) { 296 | clear_maybe_buffer_to_post(); 297 | close(s); 298 | break; 299 | } 300 | 301 | /* 302 | * The sensor ID, and the index of the record, the local time, and 303 | * the index at which this content starts within the record are 304 | * prefixed. 305 | */ 306 | post_buf[PREFIX_SIZE + 0] = param_sensor_id; 307 | post_buf[PREFIX_SIZE + 1] = param_sensor_id >> 8; 308 | post_buf[PREFIX_SIZE + 2] = param_sensor_id >> 16; 309 | post_buf[PREFIX_SIZE + 3] = param_sensor_id >> 24; 310 | 311 | uint32_t time = RTC.COUNTER; 312 | post_buf[PREFIX_SIZE + 4] = time; 313 | post_buf[PREFIX_SIZE + 5] = time >> 8; 314 | post_buf[PREFIX_SIZE + 6] = time >> 16; 315 | post_buf[PREFIX_SIZE + 7] = time >> 24; 316 | 317 | post_buf[PREFIX_SIZE + 8] = index_being_pushed; 318 | post_buf[PREFIX_SIZE + 9] = index_being_pushed >> 8; 319 | post_buf[PREFIX_SIZE + 10] = index_being_pushed >> 16; 320 | post_buf[PREFIX_SIZE + 11] = index_being_pushed >> 24; 321 | 322 | post_buf[PREFIX_SIZE + 12] = index_size_pushed; 323 | post_buf[PREFIX_SIZE + 13] = index_size_pushed >> 8; 324 | post_buf[PREFIX_SIZE + 14] = index_size_pushed >> 16; 325 | post_buf[PREFIX_SIZE + 15] = index_size_pushed >> 24; 326 | 327 | /* 328 | * Firstly use the prefix area for the key to implement MAC-SHA3. 329 | */ 330 | memcpy(&post_buf[PREFIX_SIZE - param_key_size], param_sha3_key, param_key_size); 331 | FIPS202_SHA3_224(&post_buf[PREFIX_SIZE - param_key_size], 332 | param_key_size + 16 + size, 333 | &post_buf[PREFIX_SIZE + 16 + size]); 334 | 335 | /* 336 | * Next use the prefix area for the HTTP header. 337 | */ 338 | uint32_t header_size = snprintf((char *)post_buf, PREFIX_SIZE, 339 | "POST %s HTTP/1.1\r\n" 340 | "Host: %s:%s\r\n" 341 | "Connection: close\r\n" 342 | "Content-Type: application/octet-stream\r\n" 343 | "Content-Length: %d\r\n" 344 | "\r\n", param_web_path, param_web_server, 345 | param_web_port, 16 + size + SIGNATURE_SIZE); 346 | 347 | /* 348 | * Move the header up to meet the data. 349 | */ 350 | for (j = 0; j < header_size; j++) 351 | post_buf[PREFIX_SIZE - j - 1] = post_buf[header_size - j - 1]; 352 | 353 | /* 354 | * Data ready to send. 355 | */ 356 | if (write(s, &post_buf[PREFIX_SIZE - header_size], 357 | header_size + 16 + size + SIGNATURE_SIZE) < 0) { 358 | close(s); 359 | continue; 360 | } 361 | 362 | char recv_buf[128]; 363 | int r; 364 | /* Search for the end of the response headers */ 365 | int end = 0; 366 | int headers_end = 0; 367 | int i; 368 | do { 369 | r = read(s, recv_buf + end, 127 - end); 370 | if (r > 0) { 371 | end += r; 372 | if (end >= 4) { 373 | for (i = 0; i < end - 4; i++) { 374 | if (recv_buf[i + 0] == '\r' && 375 | recv_buf[i + 1] == '\n' && 376 | recv_buf[i + 2] == '\r' && 377 | recv_buf[i + 3] == '\n') { 378 | headers_end = i + 4; 379 | break; 380 | } 381 | } 382 | if (headers_end) 383 | break; 384 | /* Keep the last three bytes as they might have 385 | * contained part of the end of the headers. */ 386 | for (i = 0; i < 3; i++) 387 | recv_buf[i] = recv_buf[end - 3 + i]; 388 | end = 3; 389 | } 390 | } 391 | } while (r > 0); 392 | 393 | if (headers_end) { 394 | /* Shift down. */ 395 | end -= headers_end; 396 | for (i = 0; i < end; i++) 397 | recv_buf[i] = recv_buf[headers_end + i]; 398 | /* Keep reading the body. */ 399 | while (end < 20) { 400 | r = read(s, recv_buf + end, 127 - end); 401 | if (r < 0) 402 | break; 403 | end += r; 404 | }; 405 | 406 | /* Accept larger responses, for future extension. There is a 407 | * magic number that indicates a successful response which is 408 | * checked. */ 409 | if (end >= 20) { 410 | uint32_t recv_magic = recv_buf[0] | 411 | (recv_buf[1] << 8) | 412 | (recv_buf[2] << 16) | 413 | (recv_buf[3] << 24); 414 | uint32_t recv_sec = recv_buf[4] | 415 | (recv_buf[5] << 8) | 416 | (recv_buf[6] << 16) | 417 | (recv_buf[7] << 24); 418 | uint32_t recv_usec = recv_buf[8] | 419 | (recv_buf[9] << 8) | 420 | (recv_buf[10] << 16) | 421 | (recv_buf[11] << 24); 422 | uint32_t recv_index = recv_buf[12] | 423 | (recv_buf[13] << 8) | 424 | (recv_buf[14] << 16) | 425 | (recv_buf[15] << 24); 426 | uint32_t recv_size = recv_buf[16] | 427 | (recv_buf[17] << 8) | 428 | (recv_buf[18] << 16) | 429 | (recv_buf[19] << 24); 430 | 431 | uint32_t magic = param_sensor_id ^ time; 432 | if (recv_magic == magic) { 433 | /* 434 | * Update the clock using the server response time. 435 | */ 436 | ds3231_note_time(recv_sec); 437 | 438 | /* Log the server time in it's response. This gives time 439 | * stamps to the events logged to help synchronize the 440 | * RTC counter to the real time. While the server could 441 | * log the times to synchronize to the RTC counter, this 442 | * gives some resilience against server data loss and 443 | * allows the sectors recorded to stand on their own. 444 | * 445 | * The event time-stamp is close enough to the received 446 | * time, and includes the posted time too to allow 447 | * matching with the server recorded times and also to 448 | * give the round-trip time to send and receive the post 449 | * which might help estimate the accuracy. Re-use the 450 | * post_buf to build this event. 451 | * 452 | * Skip logging this event if there was another POST 453 | * event logged in the last 60 seconds. This limits the 454 | * storage space used when a lot of sectors are posted 455 | * one after the other, and one every 60 seconds seems 456 | * adequate for the purpose of synchronizing the times. 457 | * 458 | */ 459 | if (recv_sec > last_recv_sec + 60) { 460 | post_buf[PREFIX_SIZE + 0] = time; 461 | post_buf[PREFIX_SIZE + 1] = time >> 8; 462 | post_buf[PREFIX_SIZE + 2] = time >> 16; 463 | post_buf[PREFIX_SIZE + 3] = time >> 24; 464 | 465 | post_buf[PREFIX_SIZE + 4] = recv_sec; 466 | post_buf[PREFIX_SIZE + 5] = recv_sec >> 8; 467 | post_buf[PREFIX_SIZE + 6] = recv_sec >> 16; 468 | post_buf[PREFIX_SIZE + 7] = recv_sec >> 24; 469 | 470 | post_buf[PREFIX_SIZE + 8] = recv_usec; 471 | post_buf[PREFIX_SIZE + 9] = recv_usec >> 8; 472 | post_buf[PREFIX_SIZE + 10] = recv_usec >> 16; 473 | post_buf[PREFIX_SIZE + 11] = recv_usec >> 24; 474 | 475 | while (1) { 476 | uint32_t new_segment = dbuf_append(last_segment, 477 | DBUF_EVENT_POST_TIME, 478 | &post_buf[PREFIX_SIZE], 479 | 12, 0); 480 | if (new_segment == last_segment) { 481 | last_recv_sec = recv_sec; 482 | break; 483 | } 484 | last_segment = new_segment; 485 | } 486 | } 487 | 488 | /* The server response is used to set the buffer indexes 489 | * known to have been received. This allows the server 490 | * to request data be re-sent, or to skip over data 491 | * already received when restarted. 492 | */ 493 | if (recv_index != index_being_pushed) { 494 | if (recv_index > index_being_pushed && 495 | index_being_pushed_next_index == 0xffffffff) { 496 | /* Looks like a bad request from the server for 497 | * an index beyond those stored on the 498 | * device. Need to catch this or the device will 499 | * continue sending data back and not stop. 500 | */ 501 | index_size_pushed = index_size_being_pushed; 502 | } else { 503 | index_being_pushed = recv_index; 504 | /* Ignore the size in this case, to avoid 505 | * getting and checking the new index size. 506 | * The server will move it along again. 507 | */ 508 | index_size_being_pushed = 0; 509 | index_size_pushed = 0; 510 | index_being_pushed_sealed = false; 511 | index_being_pushed_next_index = 0xffffffff; 512 | } 513 | } else { 514 | if (recv_size > index_size_being_pushed) { 515 | recv_size = index_size_being_pushed; 516 | } 517 | index_size_pushed = recv_size; 518 | } 519 | blink_white(); 520 | hold_off_time = 0; 521 | } 522 | } 523 | } 524 | 525 | /* 526 | * At this point the server is expected to close the connection, so 527 | * wait briefly for it to do so before giving up. While here consume 528 | * any excess input to avoid a connection reset. 529 | */ 530 | const struct timeval timeout5 = { 5, 0 }; /* 5 second timeout */ 531 | setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout5, sizeof(timeout5)); 532 | size_t len; 533 | for (len = 0; len < 4096; len++) { 534 | char c; 535 | int res = read(s, &c, 1); 536 | if (res != 1) break; 537 | } 538 | 539 | close(s); 540 | } 541 | } 542 | } 543 | 544 | void init_post() 545 | { 546 | if (param_web_server && param_web_path && param_sensor_id && 547 | param_key_size == 287 && param_sha3_key) { 548 | /* Only run the post task if there is a station interface. */ 549 | uint8_t mode = sdk_wifi_get_opmode(); 550 | if (mode != STATION_MODE && mode != STATIONAP_MODE) { 551 | return; 552 | } 553 | xTaskCreate(&post_data, "OAQ Push", 448, NULL, 1, &post_data_task); 554 | } 555 | } 556 | -------------------------------------------------------------------------------- /flash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Write the memory resident buffers to flash. 3 | * 4 | * Copyright (C) 2016, 2017 OurAirQuality.org 5 | * 6 | * Licensed under the Apache License, Version 2.0, January 2004 (the 7 | * "License"); you may not use this file except in compliance with the 8 | * License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/ 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT 16 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS WITH THE SOFTWARE. 20 | * 21 | * The buffers are written to the flash in a ring buffer. Even after being 22 | * posted to the server they remain until overwritten. This supports a recovery 23 | * option in case of loss at the server, and other options for local access. 24 | * 25 | * In operation the code only needs to know the head and tail of the flash 26 | * sector ring. At start-up it needs to be able to find this position as 27 | * reliably as possible. A 32 bit monotonically increasing index is used to 28 | * allow this point to be found. This index is not expected to wrap in practical 29 | * usage which avoids handling wrapping of this index. To protect this index 30 | * against interrupted writes and bad sectors it is written twice, the second 31 | * time with each bit inverted. This index is located in the first 8 bytes of 32 | * each flash sector. This index increases for each complete sector successfully 33 | * written, and if a write fails then it is retried at the next sector using the 34 | * same index. A server receiving two sectors with the same index would use the 35 | * most recent sector and would need to deal with the complexity of sector 36 | * number wrapping, but the node attempts to invalidate a bad sector written so 37 | * it might never be sent to the server. 38 | * 39 | * The sectors do not have a length encoding, rather the entire sector is always 40 | * posted to the server. Unused bytes are filled with ones, so the node might 41 | * omit trailing ones when sending a sector. 42 | * 43 | * A new sector is started each time the node restarts, but to minimize 44 | * unnecessary writes of unused sectors a sector is not initialized until used. 45 | * 46 | */ 47 | 48 | #include "espressif/esp_common.h" 49 | 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include "FreeRTOS.h" 57 | #include "task.h" 58 | #include "semphr.h" 59 | #include "sysparam.h" 60 | 61 | #include "buffer.h" 62 | #include "leds.h" 63 | #include "push.h" 64 | 65 | /* 66 | * For a 32Mbit flash, or 4MB, there are 1024 flash sectors. The first 256 are 67 | * reserved here for the user code, but it might be possible to use more. The 68 | * last 5 sectors are reserved for the SDK code. DEFAULT_SYSPARAM_SECTORS (which 69 | * is 4) are used for the system parameters. That leaves 759 sectors to use to 70 | * store the data buffers. 71 | * 72 | * Note the current esp-open-rtos SDK binary uses only 4 sector for parameters, 73 | * but recent SDKs also want a sector for RF-cal data and that defaults to the 74 | * fifth sector from the end. So to be consistent with both this code allows for 75 | * 5 reserved sectors at the end of the flash. 76 | */ 77 | #define BUFFER_FLASH_FIRST_SECTOR 256 78 | #define BUFFER_FLASH_NUM_SECTORS (1024 - BUFFER_FLASH_FIRST_SECTOR - 5 - DEFAULT_SYSPARAM_SECTORS) 79 | 80 | /* 81 | * Read and decode a sector index, filling the index on success and returning 1, 82 | * otherwise returning 0. 83 | */ 84 | static uint32_t decode_flash_sector_index(uint16_t sector, uint32_t *index) 85 | { 86 | uint32_t addr = sector * 4096; 87 | uint32_t data[2]; 88 | sdk_SpiFlashOpResult res; 89 | res = sdk_spi_flash_read(addr, data, 8); 90 | if (res != SPI_FLASH_RESULT_OK) { 91 | return 0; 92 | } 93 | 94 | if (data[0] != (data[1] ^ 0xffffffff)) 95 | return 0; 96 | 97 | *index = data[0]; 98 | return 1; 99 | } 100 | 101 | /* 102 | * Find the sector with the largest valid index, returning 1 on success or 0 on 103 | * failure, and filling the sector and index on success. 104 | */ 105 | static int find_most_recent_sector(uint16_t *most_recent_sector, 106 | uint32_t *largest_index) 107 | { 108 | uint16_t sector; 109 | /* Sector zero is never used for storage of the buffers, so can use zero to 110 | * flag 'no found valid sectors'. */ 111 | *most_recent_sector = 0; 112 | /* Find the first valid sector index. */ 113 | *largest_index = 0; 114 | for (sector = BUFFER_FLASH_FIRST_SECTOR; 115 | sector < BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS; 116 | sector++) { 117 | uint32_t index; 118 | if (decode_flash_sector_index(sector, &index) && 119 | index >= *largest_index) { 120 | *most_recent_sector = sector; 121 | *largest_index = index; 122 | } 123 | } 124 | 125 | if (!*most_recent_sector) { 126 | /* No valid sectors. */ 127 | return 0; 128 | } 129 | 130 | /* Scan forward a little looking for another sector with the same index 131 | * which might happen if there was a bad sector. */ 132 | int32_t i; 133 | sector = *most_recent_sector + 1; 134 | for (i = 0; i < 128; i++, sector++) { 135 | if (sector >= BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS) 136 | sector = BUFFER_FLASH_FIRST_SECTOR; 137 | uint32_t index; 138 | if (decode_flash_sector_index(sector, &index) && 139 | index == *largest_index) { 140 | *most_recent_sector = sector; 141 | } 142 | } 143 | 144 | return 1; 145 | } 146 | 147 | /* The head of the flash sector ring buffer. */ 148 | static uint16_t flash_sector; 149 | /* Flag to support lazy initialization of the current sector. */ 150 | static uint8_t flash_sector_initialized; 151 | 152 | /* For signaling and waiting for data to store to flash */ 153 | TaskHandle_t flash_data_task = NULL; 154 | 155 | /* For protecting access to the flash state. */ 156 | SemaphoreHandle_t flash_state_sem = NULL; 157 | static uint8_t flash_buf[4096]; 158 | 159 | 160 | /* Check if a flash sector is erased, returning 1 if erased and 0 if 161 | * not. */ 162 | static int flash_sector_erased(uint16_t sector) 163 | { 164 | uint32_t addr = sector * 4096; 165 | int i; 166 | 167 | for (i = 0; i < 4096; i += 16) { 168 | uint32_t data[4]; 169 | sdk_SpiFlashOpResult res; 170 | res = sdk_spi_flash_read(addr + i, data, 16); 171 | if (res != SPI_FLASH_RESULT_OK) { 172 | return 0; 173 | } 174 | if (data[0] != 0xffffffff || data[1] != 0xffffffff || 175 | data[2] != 0xffffffff || data[3] != 0xffffffff) { 176 | return 0; 177 | } 178 | } 179 | 180 | return 1; 181 | } 182 | 183 | /* Compare a flash sector to the contents of a buffer, returning 1 if 184 | * equal and 0 if not. */ 185 | static int check_flash_sector(uint16_t sector, uint32_t *buf) 186 | { 187 | uint32_t addr = sector * 4096; 188 | int i; 189 | 190 | for (i = 0; i < 4096; i += 16, buf += 4) { 191 | uint32_t data[4]; 192 | sdk_SpiFlashOpResult res; 193 | res = sdk_spi_flash_read(addr + i, data, 16); 194 | if (res != SPI_FLASH_RESULT_OK) { 195 | return 0; 196 | } 197 | if (data[0] != buf[0] || data[1] != buf[1] || 198 | data[2] != buf[2] || data[3] != buf[3]) { 199 | return 0; 200 | } 201 | } 202 | 203 | return 1; 204 | } 205 | 206 | /* Log failures. Perhaps log an event for these. */ 207 | static uint32_t flash_write_failures = 0; 208 | static uint32_t flash_index_invalidate_failures = 0; 209 | 210 | /* Handle a failure to erase or write to the current flash_sector. */ 211 | static void handle_flash_write_failure() 212 | { 213 | flash_write_failures++; 214 | /* If the index is invalid then just move on. */ 215 | uint32_t flash_index; 216 | if (decode_flash_sector_index(flash_sector, &flash_index)) { 217 | /* If the index decodes as valid then attempt to erase the sector to at 218 | * least invalidate the index. */ 219 | sdk_spi_flash_erase_sector(flash_sector); 220 | taskYIELD(); 221 | if (decode_flash_sector_index(flash_sector, &flash_index)) { 222 | /* Log the failure. */ 223 | flash_index_invalidate_failures++; 224 | } 225 | } 226 | flash_sector++; 227 | if (flash_sector >= BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS) 228 | flash_sector = BUFFER_FLASH_FIRST_SECTOR; 229 | flash_sector_initialized = 0; 230 | } 231 | 232 | /* A flag to note if new data might be available to help avoid a full check each 233 | * time. Set when new data is written and cleared when no data to post is 234 | * found. */ 235 | static volatile uint32_t maybe_flash_to_post = 1; 236 | 237 | void flash_data(void *pvParameters) 238 | { 239 | /* 240 | * Delay starting to write to flash as the power can bounce on and off if 241 | * the battery is low, and that would consume flash sectors quickly and 242 | * might even damage them? 243 | * 244 | * This delay also gives a brief period to allow use of the device without 245 | * it modifying the flash. 246 | */ 247 | vTaskDelay(180000 / portTICK_PERIOD_MS); 248 | 249 | while (1) { 250 | xTaskNotifyWait(0, 0, NULL, 120000 / portTICK_PERIOD_MS); 251 | 252 | /* Try to flush all the pending buffers before waiting again. */ 253 | while (1) { 254 | uint32_t start; 255 | uint32_t size = get_buffer_to_write(flash_buf, &start); 256 | 257 | if (size == 0) 258 | break; 259 | 260 | uint32_t index = flash_buf[0] | flash_buf[1] << 8 | flash_buf[2] << 16 | flash_buf[3] << 24 ; 261 | 262 | xSemaphoreTake(flash_state_sem, portMAX_DELAY); 263 | 264 | if (flash_sector_initialized) { 265 | /* Rewrite to the current flash_sector? */ 266 | uint32_t flash_index; 267 | if (decode_flash_sector_index(flash_sector, &flash_index) && 268 | flash_index == index) { 269 | /* Rewrite to the current flash_sector. Firstly try just 270 | * writing from the start position. */ 271 | 272 | /* Round to a word aligned range. */ 273 | uint32_t aligned_end = (size + 3) & 0xfffffffc; 274 | uint32_t aligned_start = start & 0xfffffffc; 275 | uint32_t aligned_size = aligned_end - aligned_start; 276 | 277 | sdk_SpiFlashOpResult res; 278 | uint32_t dest_addr = (uint32_t)flash_sector * 4096 + aligned_start; 279 | res = sdk_spi_flash_write(dest_addr, (uint32_t *)(flash_buf + aligned_start), aligned_size); 280 | taskYIELD(); 281 | if (res == SPI_FLASH_RESULT_OK && 282 | check_flash_sector(flash_sector, (uint32_t *)flash_buf)) { 283 | maybe_flash_to_post = 1; 284 | xSemaphoreGive(flash_state_sem); 285 | note_buffer_written(index, size); 286 | continue; 287 | } 288 | 289 | handle_flash_write_failure(); 290 | } else { 291 | /* Either the flash index is bad or the index being written 292 | * is more recent, so move on to the next flash sector. */ 293 | flash_sector++; 294 | if (flash_sector >= BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS) 295 | flash_sector = BUFFER_FLASH_FIRST_SECTOR; 296 | flash_sector_initialized = 0; 297 | } 298 | } 299 | 300 | /* At an uninitialized flash sector, with a full buffer to write. */ 301 | 302 | /* Retry a limited number of times on write failures. */ 303 | int retries = 0; 304 | while (1) { 305 | /* Firstly check if it is erased. */ 306 | if (!flash_sector_erased(flash_sector)) { 307 | /* Erase the flash_sector. */ 308 | sdk_SpiFlashOpResult res; 309 | res = sdk_spi_flash_erase_sector(flash_sector); 310 | taskYIELD(); 311 | if (res != SPI_FLASH_RESULT_OK || 312 | !flash_sector_erased(flash_sector)) { 313 | /* Just fall through and try the write, it might still 314 | * work. */ 315 | } 316 | } 317 | /* Write the sector. */ 318 | sdk_SpiFlashOpResult res; 319 | uint32_t dest_addr = (uint32_t)flash_sector * 4096; 320 | res = sdk_spi_flash_write(dest_addr, (uint32_t *)flash_buf, size); 321 | taskYIELD(); 322 | if (res != SPI_FLASH_RESULT_OK || 323 | !check_flash_sector(flash_sector, (uint32_t *)flash_buf)) { 324 | handle_flash_write_failure(); 325 | if (++retries > 8) { 326 | /* Give up, consider it written. */ 327 | break; 328 | } 329 | continue; 330 | } 331 | /* Success. */ 332 | flash_sector_initialized = 1; 333 | break; 334 | } 335 | maybe_flash_to_post = 1; 336 | xSemaphoreGive(flash_state_sem); 337 | note_buffer_written(index, size); 338 | /* Signal the HTTP-Post thread to re-check. */ 339 | if (post_data_task) 340 | xTaskNotify(post_data_task, 0, eNoAction); 341 | } 342 | } 343 | } 344 | 345 | /* 346 | * Return 0 if there is no data to post otherwise non-zero. The caller is the 347 | * only task that is expected to reset this, and the flash_data_task the only 348 | * task to set it. Data might come in after the call, so there might in fact be 349 | * data to post, but if so then the caller is expected to be signaled by the 350 | * post_data_sem and will try again. 351 | */ 352 | uint32_t maybe_buffer_to_post() 353 | { 354 | xSemaphoreTake(flash_state_sem, portMAX_DELAY); 355 | uint32_t maybe = maybe_flash_to_post; 356 | xSemaphoreGive(flash_state_sem); 357 | return maybe; 358 | } 359 | 360 | void clear_maybe_buffer_to_post() 361 | { 362 | xSemaphoreTake(flash_state_sem, portMAX_DELAY); 363 | maybe_flash_to_post = 0; 364 | xSemaphoreGive(flash_state_sem); 365 | return; 366 | } 367 | 368 | 369 | /* 370 | * Request the current length of the buffer with the given index or the first 371 | * buffer with an index less than that requested to make it easy to get both the 372 | * current index and size. Returns the oldest buffer and its size if no buffers 373 | * match, when requesting an old index no longer in the flash. Used by the web 374 | * interface to give the content-length of a response, and the data post thread 375 | * to search for more data. The buffer might grow while posting, but the 376 | * response will only send the amount indicated here. This could also be used to 377 | * support requesting a range, allowing the web client to probe if there is more 378 | * data to download. 379 | * 380 | * All sectors but the current head are sealed and no more data is written to 381 | * them, and it is useful to know if the returned index is sealed in which case 382 | * it can not grow to have more data. 383 | * 384 | * The first sector with an index is the head. It is useful to know if a 385 | * returned index is the head, to know if it can be advanced to find more data. 386 | * 387 | * The next_index helps stepping forward because there might be gaps in the 388 | * index sequence and otherwise it would take some more iteration to find the 389 | * next in the sequence. If there is no next_index then 0xffffffff is returned - 390 | * the search for the next index should start from there as there might be a gap. 391 | */ 392 | uint32_t get_buffer_size(uint32_t requested_index, uint32_t *index, uint32_t *next_index, bool *sealed) 393 | { 394 | xSemaphoreTake(flash_state_sem, portMAX_DELAY); 395 | 396 | /* Low sectors hold code and not data, so zero can represent invalid. */ 397 | uint32_t last_sector = 0; 398 | /* The index of the last_sector. */ 399 | uint32_t last_index = 0xffffffff; 400 | 401 | *next_index = 0xffffffff; 402 | 403 | if (flash_sector_initialized) { 404 | if (decode_flash_sector_index(flash_sector, index)) { 405 | last_sector = flash_sector; 406 | last_index = *index; 407 | if (*index <= requested_index) { 408 | sdk_SpiFlashOpResult res; 409 | res = sdk_spi_flash_read(flash_sector * 4096, (uint32_t *)flash_buf, 4096); 410 | if (res == SPI_FLASH_RESULT_OK) { 411 | uint32_t size = 0; 412 | for (size = 4096; size > 0; size--) { 413 | if (flash_buf[size - 1] != 0xff) 414 | break; 415 | } 416 | *sealed = false; 417 | xSemaphoreGive(flash_state_sem); 418 | return size; 419 | } 420 | } 421 | } 422 | } 423 | 424 | /* Search backwards from the current head flash_sector looking for the first 425 | * index requested. */ 426 | uint32_t sector = flash_sector - 1; 427 | if (sector < BUFFER_FLASH_FIRST_SECTOR) { 428 | sector = BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS - 1; 429 | } 430 | 431 | while (1) { 432 | if (decode_flash_sector_index(sector, index) && 433 | /* Skip if the index increases (an error), or if this is not the 434 | * most recent write for this index which might occur if a flash 435 | * write failed and the sector was re-written. */ 436 | *index < last_index) { 437 | if (*index <= requested_index) { 438 | sdk_SpiFlashOpResult res; 439 | memset(flash_buf, 0xa5, 4096); 440 | res = sdk_spi_flash_read(sector * 4096, (uint32_t *)flash_buf, 4096); 441 | if (res == SPI_FLASH_RESULT_OK) { 442 | uint32_t size; 443 | for (size = 4096; size > 0; size--) { 444 | if (flash_buf[size - 1] != 0xff) 445 | break; 446 | } 447 | *next_index = last_index; 448 | *sealed = (sector != flash_sector); 449 | xSemaphoreGive(flash_state_sem); 450 | return size; 451 | } 452 | /* TODO a flash read failure above is not handled well, it would 453 | * be better to skip this index rather than just moving on to 454 | * the next sector which might have the same index and thus be a 455 | * bad write. At least this skips noting it as a valid 456 | * last_sector. */ 457 | } else { 458 | /* This must be the first time this index number has been found 459 | * so note this sector as it will be used if nothing else is 460 | * found. */ 461 | *next_index = last_index; 462 | last_sector = sector; 463 | last_index = *index; 464 | } 465 | } 466 | sector--; 467 | if (sector < BUFFER_FLASH_FIRST_SECTOR) { 468 | sector = BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS - 1; 469 | } 470 | if (sector == flash_sector) { 471 | /* Wrapped. */ 472 | break; 473 | } 474 | } 475 | 476 | if (last_sector != 0) { 477 | /* Found something, so return it and it's size. */ 478 | sdk_SpiFlashOpResult res; 479 | res = sdk_spi_flash_read(last_sector * 4096, (uint32_t *)flash_buf, 4096); 480 | if (res == SPI_FLASH_RESULT_OK) { 481 | uint32_t size = 0; 482 | for (size = 4096; size > 0; size--) { 483 | if (flash_buf[size - 1] != 0xff) 484 | break; 485 | } 486 | *index = last_index; 487 | *sealed = (last_sector != flash_sector); 488 | xSemaphoreGive(flash_state_sem); 489 | return size; 490 | } 491 | } 492 | 493 | xSemaphoreGive(flash_state_sem); 494 | 495 | *index = 0; 496 | *sealed = false; 497 | return 0; 498 | } 499 | 500 | 501 | /* 502 | * Return a range of the buffer with the given index. If the buffer index is no 503 | * longer available then return false, otherwise success, which can happen if 504 | * reading from the tail of the FIFO. The http response will be truncated on 505 | * such a failure and less than the length probe at the start of the response, 506 | * the http response will send a response with a content-length and the client 507 | * can detect the truncated response. 508 | * 509 | * The sector that the last request used is cached, so avoid a search if the 510 | * index has not changed. 511 | */ 512 | static uint32_t last_get_buffer_range_sector = 0; 513 | static uint32_t last_get_buffer_range_index = 0xffffffff; 514 | bool get_buffer_range(uint32_t index, uint32_t start, uint32_t end, uint8_t *buf) 515 | { 516 | uint32_t i; 517 | 518 | xSemaphoreTake(flash_state_sem, portMAX_DELAY); 519 | 520 | if (last_get_buffer_range_sector && index == last_get_buffer_range_index) { 521 | sdk_SpiFlashOpResult res; 522 | res = sdk_spi_flash_read(last_get_buffer_range_sector * 4096, 523 | (uint32_t *)flash_buf, 4096); 524 | if (res == SPI_FLASH_RESULT_OK) { 525 | for (i = 0; i < end - start; i++) 526 | buf[i] = flash_buf[start + i]; 527 | xSemaphoreGive(flash_state_sem); 528 | return true; 529 | } 530 | } 531 | 532 | if (flash_sector_initialized) { 533 | if (decode_flash_sector_index(flash_sector, &i) && i == index) { 534 | sdk_SpiFlashOpResult res; 535 | res = sdk_spi_flash_read(flash_sector * 4096, (uint32_t *)flash_buf, 4096); 536 | if (res == SPI_FLASH_RESULT_OK) { 537 | for (i = 0; i < end - start; i++) 538 | buf[i] = flash_buf[start + i]; 539 | last_get_buffer_range_sector = flash_sector; 540 | last_get_buffer_range_index = index; 541 | xSemaphoreGive(flash_state_sem); 542 | return true; 543 | } 544 | } 545 | } 546 | 547 | /* Search backwards from the current head flash_sector looking for the first 548 | * index not posted. */ 549 | uint32_t sector = flash_sector - 1; 550 | if (sector < BUFFER_FLASH_FIRST_SECTOR) { 551 | sector = BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS - 1; 552 | } 553 | 554 | while (1) { 555 | if (decode_flash_sector_index(sector, &i) && i == index) { 556 | sdk_SpiFlashOpResult res; 557 | res = sdk_spi_flash_read(sector * 4096, (uint32_t *)flash_buf, 4096); 558 | if (res == SPI_FLASH_RESULT_OK) { 559 | for (i = 0; i < end - start; i++) 560 | buf[i] = flash_buf[start + i]; 561 | last_get_buffer_range_sector = sector; 562 | last_get_buffer_range_index = index; 563 | xSemaphoreGive(flash_state_sem); 564 | return true; 565 | } 566 | } 567 | sector--; 568 | if (sector < BUFFER_FLASH_FIRST_SECTOR) { 569 | sector = BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS - 1; 570 | } 571 | if (sector == flash_sector) { 572 | /* Wrapped. */ 573 | break; 574 | } 575 | } 576 | 577 | last_get_buffer_range_sector = 0; 578 | last_get_buffer_range_index = 0xffffffff; 579 | xSemaphoreGive(flash_state_sem); 580 | return false; 581 | } 582 | 583 | 584 | 585 | /* Erase all the flash data and reinitialize. 586 | * TODO check how other code interacts with this? 587 | */ 588 | bool erase_flash_data() { 589 | /* 590 | * Disable buffer logging during this operation. 591 | */ 592 | bool logging = set_buffer_logging(0); 593 | reset_dbuf(); 594 | 595 | xSemaphoreTake(flash_state_sem, portMAX_DELAY); 596 | uint32_t i; 597 | bool success = true; 598 | 599 | for (i = 0; i < BUFFER_FLASH_NUM_SECTORS; i++) { 600 | uint32_t flash_sector = i + BUFFER_FLASH_FIRST_SECTOR; 601 | if (!flash_sector_erased(flash_sector)) { 602 | /* Erase the flash_sector. */ 603 | sdk_SpiFlashOpResult res; 604 | res = sdk_spi_flash_erase_sector(flash_sector); 605 | taskYIELD(); 606 | if (res != SPI_FLASH_RESULT_OK || 607 | !flash_sector_erased(flash_sector)) { 608 | success = false; 609 | } 610 | } 611 | } 612 | /* No valid sectors, start at the first sector. */ 613 | flash_sector = BUFFER_FLASH_FIRST_SECTOR; 614 | flash_sector_initialized = 0; 615 | maybe_flash_to_post = 0; 616 | last_get_buffer_range_sector = 0; 617 | last_get_buffer_range_index = 0xffffffff; 618 | 619 | // TODO reset the push task. 620 | 621 | xSemaphoreGive(flash_state_sem); 622 | set_buffer_logging(logging); 623 | return success; 624 | } 625 | 626 | 627 | uint32_t init_flash() 628 | { 629 | uint32_t flash_index; 630 | 631 | /* Recover the head sector and index. */ 632 | if (find_most_recent_sector(&flash_sector, &flash_index)) { 633 | /* Start the head at the next sector. */ 634 | flash_sector++; 635 | if (flash_sector >= BUFFER_FLASH_FIRST_SECTOR + BUFFER_FLASH_NUM_SECTORS) 636 | flash_sector = BUFFER_FLASH_FIRST_SECTOR; 637 | flash_index++; 638 | } else { 639 | /* No valid sectors, start at the first sector. */ 640 | flash_sector = BUFFER_FLASH_FIRST_SECTOR; 641 | flash_index = 0; 642 | } 643 | flash_sector_initialized = 0; 644 | 645 | flash_state_sem = xSemaphoreCreateMutex(); 646 | 647 | return flash_index; 648 | } 649 | --------------------------------------------------------------------------------