PurpleAirCSVDataLogger module
+PurpleAirCSVDataLogger module
Copyright 2023 carlkidcrypto, All rights reserved. A python class designed to use the PurpleAirAPI for requesting sensor(s) data. Data will be inserted into CSV files.
@@ -100,12 +101,12 @@ single request rather than individual requests in succession.”- -class PurpleAirCSVDataLogger.PurpleAirCSVDataLogger(PurpleAirAPIReadKey, PurpleAirAPIWriteKey, path_to_save_csv_files_in) +class PurpleAirCSVDataLogger.PurpleAirCSVDataLogger(PurpleAirApiReadKey=None, PurpleAirApiWriteKey=None, PurpleAirApiIpv4Address=None, path_to_save_csv_files_in=None)
Bases:
PurpleAirDataLogger
The logger class. For now we will insert data into a CSV file.
- -store_sensor_data(single_sensor_data_dict) +store_sensor_data(single_sensor_data_dict)
Insert the sensor data into CSV files.
- Parameters: diff --git a/docs/html/PurpleAirCSVDataLoggerConstants.html b/docs/html/PurpleAirCSVDataLoggerConstants.html index 4efb018..f5cc03d 100644 --- a/docs/html/PurpleAirCSVDataLoggerConstants.html +++ b/docs/html/PurpleAirCSVDataLoggerConstants.html @@ -4,18 +4,18 @@ -
PurpleAirCSVDataLoggerConstants module — PurpleAir Data Logger(s) V1.2.1 documentation +PurpleAirCSVDataLoggerConstants module — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + @@ -68,6 +68,7 @@- PurpleAirDataLogger module
+- PurpleAirDataLoggerHelpers module
- PurpleAirPSQLDataLogger module
- PurpleAirPSQLQueryStatements module
- PurpleAirSQLiteDataLogger module
@@ -102,114 +103,114 @@- PurpleAirCSVDataLoggerConstants module
+PurpleAirCSVDataLoggerConstants module
Copyright 2022 carlkidcrypto, All rights reserved. A file containing CSVDataLogger constants.
- -PurpleAirCSVDataLoggerConstants.ENVIRONMENTAL_FIELDS_FILE_NAME = 'environmental_fields.csv' +PurpleAirCSVDataLoggerConstants.ENVIRONMENTAL_FIELDS_FILE_NAME = 'environmental_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.ENVIRONMENTAL_FIELDS_HEADER = 'data_time_stamp,sensor_index,humidity,humidity_a,humidity_b,temperature,temperature_a,temperature_b,pressure,pressure_a,pressure_b' +PurpleAirCSVDataLoggerConstants.ENVIRONMENTAL_FIELDS_HEADER = 'data_time_stamp,sensor_index,humidity,humidity_a,humidity_b,temperature,temperature_a,temperature_b,pressure,pressure_a,pressure_b'
Standard CSV header for -
- -PurpleAirCSVDataLoggerConstants.MISCELLANEOUS_FIELDS_FILE_NAME = 'miscellaneous_fields.csv' +PurpleAirCSVDataLoggerConstants.MISCELLANEOUS_FIELDS_FILE_NAME = 'miscellaneous_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.MISCELLANEOUS_FIELDS_HEADER = 'data_time_stamp,sensor_index,voc,voc_a,voc_b,ozone1,analog_input' +PurpleAirCSVDataLoggerConstants.MISCELLANEOUS_FIELDS_HEADER = 'data_time_stamp,sensor_index,voc,voc_a,voc_b,ozone1,analog_input'
Standard CSV header for -
- -PurpleAirCSVDataLoggerConstants.PARTICLE_COUNT_FIELDS_FILE_NAME = 'particle_count_fields.csv' +PurpleAirCSVDataLoggerConstants.PARTICLE_COUNT_FIELDS_FILE_NAME = 'particle_count_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.PARTICLE_COUNT_FIELDS_HEADER = 'data_time_stamp,sensor_index,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,5.0_um_count_a,5.0_um_count_b,10.0_um_count,10.0_um_count_a,10.0_um_count_b' +PurpleAirCSVDataLoggerConstants.PARTICLE_COUNT_FIELDS_HEADER = 'data_time_stamp,sensor_index,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,5.0_um_count_a,5.0_um_count_b,10.0_um_count,10.0_um_count_a,10.0_um_count_b'
Standard CSV header for -
- -PurpleAirCSVDataLoggerConstants.PM10_0_FIELDS_FILE_NAME = 'pm10.0_fields.csv' +PurpleAirCSVDataLoggerConstants.PM10_0_FIELDS_FILE_NAME = 'pm10.0_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.PM10_0_FIELDS_HEADER = 'data_time_stamp,sensor_index,pm10.0,pm10.0_a,pm10.0_b,pm10.0_atm,pm10.0_atm_a,pm10.0_atm_b,pm10.0_cf_1,pm10.0_cf_1_a,pm10.0_cf_1_b' +PurpleAirCSVDataLoggerConstants.PM10_0_FIELDS_HEADER = 'data_time_stamp,sensor_index,pm10.0,pm10.0_a,pm10.0_b,pm10.0_atm,pm10.0_atm_a,pm10.0_atm_b,pm10.0_cf_1,pm10.0_cf_1_a,pm10.0_cf_1_b'
Standard CSV header for -
- -PurpleAirCSVDataLoggerConstants.PM1_0_FIELDS_FILE_NAME = 'pm1.0_fields.csv' +PurpleAirCSVDataLoggerConstants.PM1_0_FIELDS_FILE_NAME = 'pm1.0_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.PM1_0_FIELDS_HEADER = 'data_time_stamp,sensor_index,pm1.0,pm1.0_a,pm1.0_b,pm1.0_atm,pm1.0_atm_a,pm1.0_atm_b,pm1.0_cf_1,pm1.0_cf_1_a,pm1.0_cf_1_b' +PurpleAirCSVDataLoggerConstants.PM1_0_FIELDS_HEADER = 'data_time_stamp,sensor_index,pm1.0,pm1.0_a,pm1.0_b,pm1.0_atm,pm1.0_atm_a,pm1.0_atm_b,pm1.0_cf_1,pm1.0_cf_1_a,pm1.0_cf_1_b'
Standard CSV header for -
- -PurpleAirCSVDataLoggerConstants.PM2_5_FIELDS_FILE_NAME = 'pm2.5_fields.csv' +PurpleAirCSVDataLoggerConstants.PM2_5_FIELDS_FILE_NAME = 'pm2.5_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.PM2_5_FIELDS_HEADER = 'data_time_stamp,sensor_index,pm2.5_alt,pm2.5_alt_a,pm2.5_alt_b,pm2.5,pm2.5_a,pm2.5_b,pm2.5_atm,pm2.5_atm_a,pm2.5_atm_b,pm2.5_cf_1,pm2.5_cf_1_a,pm2.5_cf_1_b' +PurpleAirCSVDataLoggerConstants.PM2_5_FIELDS_HEADER = 'data_time_stamp,sensor_index,pm2.5_alt,pm2.5_alt_a,pm2.5_alt_b,pm2.5,pm2.5_a,pm2.5_b,pm2.5_atm,pm2.5_atm_a,pm2.5_atm_b,pm2.5_cf_1,pm2.5_cf_1_a,pm2.5_cf_1_b'
Standard CSV header for -
- -PurpleAirCSVDataLoggerConstants.PM2_5_PSEUDO_AVERAGE_FIELDS_FILE_NAME = 'pm2.5_pseudo_average_fields.csv' +PurpleAirCSVDataLoggerConstants.PM2_5_PSEUDO_AVERAGE_FIELDS_FILE_NAME = 'pm2.5_pseudo_average_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.PM2_5_PSEUDO_AVERAGE_FIELDS_HEADER = 'data_time_stamp,sensor_index,pm2.5_10minute,pm2.5_10minute_a,pm2.5_10minute_b,pm2.5_30minute,pm2.5_30minute_a,pm2.5_30minute_b,pm2.5_60minute,pm2.5_60minute_a,pm2.5_60minute_b,pm2.5_6hour,pm2.5_6hour_a,pm2.5_6hour_b,pm2.5_24hour,pm2.5_24hour_a,pm2.5_24hour_b,pm2.5_1week,pm2.5_1week_a,pm2.5_1week_b' +PurpleAirCSVDataLoggerConstants.PM2_5_PSEUDO_AVERAGE_FIELDS_HEADER = 'data_time_stamp,sensor_index,pm2.5_10minute,pm2.5_10minute_a,pm2.5_10minute_b,pm2.5_30minute,pm2.5_30minute_a,pm2.5_30minute_b,pm2.5_60minute,pm2.5_60minute_a,pm2.5_60minute_b,pm2.5_6hour,pm2.5_6hour_a,pm2.5_6hour_b,pm2.5_24hour,pm2.5_24hour_a,pm2.5_24hour_b,pm2.5_1week,pm2.5_1week_a,pm2.5_1week_b'
Standard CSV header for -
- -PurpleAirCSVDataLoggerConstants.STATION_INFORMATION_AND_STATUS_FIELDS_FILE_NAME = 'station_information_and_status_fields.csv' +PurpleAirCSVDataLoggerConstants.STATION_INFORMATION_AND_STATUS_FIELDS_FILE_NAME = 'station_information_and_status_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.STATION_INFORMATION_AND_STATUS_FIELDS_HEADER = 'data_time_stamp,sensor_index,name,icon,model,hardware,location_type,private,latitude,longitude,altitude,position_rating,led_brightness,firmware_version,firmware_upgrade,rssi,uptime,pa_latency,memory,last_seen,last_modified,date_created,channel_state,channel_flags,channel_flags_manual,channel_flags_auto,confidence,confidence_manual,confidence_auto' +PurpleAirCSVDataLoggerConstants.STATION_INFORMATION_AND_STATUS_FIELDS_HEADER = 'data_time_stamp,sensor_index,name,icon,model,hardware,location_type,private,latitude,longitude,altitude,position_rating,led_brightness,firmware_version,firmware_upgrade,rssi,uptime,pa_latency,memory,last_seen,last_modified,date_created,channel_state,channel_flags,channel_flags_manual,channel_flags_auto,confidence,confidence_manual,confidence_auto'
Standard CSV header for -
- -PurpleAirCSVDataLoggerConstants.THINGSPEAK_FIELDS_FILE_NAME = 'thingspeak_fields.csv' +PurpleAirCSVDataLoggerConstants.THINGSPEAK_FIELDS_FILE_NAME = 'thingspeak_fields.csv'
Standard file name for -
- -PurpleAirCSVDataLoggerConstants.THINGSPEAK_FIELDS_HEADER = 'data_time_stamp,sensor_index,primary_id_a,primary_key_a,secondary_id_a,secondary_key_a,primary_id_b,primary_key_b,secondary_id_b,secondary_key_b' +PurpleAirCSVDataLoggerConstants.THINGSPEAK_FIELDS_HEADER = 'data_time_stamp,sensor_index,primary_id_a,primary_key_a,secondary_id_a,secondary_key_a,primary_id_b,primary_key_b,secondary_id_b,secondary_key_b'
Standard CSV header for -
PurpleAirDataLogger module — PurpleAir Data Logger(s) V1.2.1 documentation +PurpleAirDataLogger module — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + - + @@ -55,9 +55,9 @@ -PurpleAirDataLoggerError
+generate_common_arg_parser()
- PurpleAirDataLoggerHelpers module
- PurpleAirPSQLDataLogger module
- PurpleAirPSQLQueryStatements module
- PurpleAirSQLiteDataLogger module
@@ -92,26 +92,26 @@- @@ -171,7 +161,7 @@PurpleAirDataLogger module
+PurpleAirDataLogger module
Copyright 2023 carlkidcrypto, All rights reserved. A python base Data Logger class.
- -class PurpleAirDataLogger.PurpleAirDataLogger(PurpleAirAPIReadKey, PurpleAirAPIWriteKey) +class PurpleAirDataLogger.PurpleAirDataLogger(PurpleAirApiReadKey=None, PurpleAirApiWriteKey=None, PurpleAirApiIpv4Address=None)
Bases:
object
The Base Data Logger class. Will define common methods used by other data loggers. For example, PSQLDataLogger, CSVDataLogger, or SQLiteDataLogger. Inheritors of this class will only need to define their own ‘store_sensor_data’ method.
- -property send_request_every_x_seconds +property send_request_every_x_seconds
Return the current value of send_request_every_x_seconds. This value is how often we send requests to the Purple Air API. (PAA)
- -store_sensor_data(single_sensor_data_dict) +store_sensor_data(single_sensor_data_dict)
Insert the sensor data into the database.
- Parameters: @@ -126,7 +126,7 @@
- -validate_parameters_and_run(paa_multiple_sensor_request_json_file=None, paa_single_sensor_request_json_file=None, paa_group_sensor_request_json_file=None) +validate_parameters_and_run(paa_multiple_sensor_request_json_file=None, paa_single_sensor_request_json_file=None, paa_group_sensor_request_json_file=None, paa_local_sensor_request_json_file=None) None
A method to choose what run method to execute based on what config file is being used. This shall be considered the main entry point for and PurpleAirDataLogger.
-
@@ -138,6 +138,8 @@
the parameters to send a multiple sensor request(s).
+paa_group_sensor_request_json_file (str) – The path to a json file containing the parameters to send a group sensor request(s).
paa_local_sensor_request_json_file (str) – The path to a json file containing +the parameters to send a local sensor request(s).
- -exception PurpleAirDataLogger.PurpleAirDataLoggerError(message_string) +exception PurpleAirDataLogger.PurpleAirDataLoggerError(message_string)
Bases:
Exception
Custom Exception for our PurpleAirDataLogger class.
- -PurpleAirDataLogger.generate_common_arg_parser(argparse_description='') -
A function to generate the common arguments that all data loggers need
--
-
- Parameters: -
argparse_description (str) – A description for the argument parser that will be return
-
-
:return An instance of argparse with the common arguments added.
-
-
-
diff --git a/docs/html/PurpleAirPSQLQueryStatements.html b/docs/html/PurpleAirPSQLQueryStatements.html index 01f1a41..7c2962e 100644 --- a/docs/html/PurpleAirPSQLQueryStatements.html +++ b/docs/html/PurpleAirPSQLQueryStatements.html @@ -4,18 +4,18 @@ -PurpleAirPSQLQueryStatements module — PurpleAir Data Logger(s) V1.2.1 documentation +PurpleAirPSQLQueryStatements module — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + @@ -48,6 +48,7 @@- PurpleAirCSVDataLogger module
- PurpleAirCSVDataLoggerConstants module
- PurpleAirDataLogger module
+- PurpleAirDataLoggerHelpers module
- PurpleAirPSQLDataLogger module
- PurpleAirPSQLQueryStatements module
CREATE_ENVIRONMENTAL_FIELDS_TABLE
@@ -107,156 +108,156 @@
- -PurpleAirPSQLQueryStatements.CREATE_ENVIRONMENTAL_FIELDS_TABLE = '\n CREATE TABLE IF NOT EXISTS environmental_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n humidity INT NULL,\n humidity_a INT NULL,\n humidity_b INT NULL,\n temperature INT NULL,\n temperature_a INT NULL,\n temperature_b INT NULL,\n pressure FLOAT NULL,\n pressure_a FLOAT NULL,\n pressure_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_ENVIRONMENTAL_FIELDS_TABLE = '\n CREATE TABLE IF NOT EXISTS environmental_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n humidity INT NULL,\n humidity_a INT NULL,\n humidity_b INT NULL,\n temperature INT NULL,\n temperature_a INT NULL,\n temperature_b INT NULL,\n pressure FLOAT NULL,\n pressure_a FLOAT NULL,\n pressure_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
PSQL statement for environmental_fields table
- -PurpleAirPSQLQueryStatements.CREATE_MISCELLANEOUS_FIELDS = '\n CREATE TABLE IF NOT EXISTS miscellaneous_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n voc FLOAT NULL,\n voc_a FLOAT NULL,\n voc_b FLOAT NULL,\n ozone1 FLOAT NULL,\n analog_input FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_MISCELLANEOUS_FIELDS = '\n CREATE TABLE IF NOT EXISTS miscellaneous_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n voc FLOAT NULL,\n voc_a FLOAT NULL,\n voc_b FLOAT NULL,\n ozone1 FLOAT NULL,\n analog_input FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
PSQL statement for miscellaneous_fields table
- -PurpleAirPSQLQueryStatements.CREATE_PARTICLE_COUNT_FIELDS = '\n CREATE TABLE IF NOT EXISTS particle_count_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n um_count_0_3 FLOAT NULL,\n um_count_a_0_3 FLOAT NULL,\n um_count_b_0_3 FLOAT NULL,\n um_count_0_5 FLOAT NULL,\n um_count_a_0_5 FLOAT NULL,\n um_count_b_0_5 FLOAT NULL,\n um_count_1_0 FLOAT NULL,\n um_count_a_1_0 FLOAT NULL,\n um_count_b_1_0 FLOAT NULL,\n um_count_2_5 FLOAT NULL,\n um_count_a_2_5 FLOAT NULL,\n um_count_b_2_5 FLOAT NULL,\n um_count_5_0 FLOAT NULL,\n um_count_a_5_0 FLOAT NULL,\n um_count_b_5_0 FLOAT NULL,\n um_count_10_0 FLOAT NULL,\n um_count_a_10_0 FLOAT NULL,\n um_count_b_10_0 FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_PARTICLE_COUNT_FIELDS = '\n CREATE TABLE IF NOT EXISTS particle_count_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n um_count_0_3 FLOAT NULL,\n um_count_a_0_3 FLOAT NULL,\n um_count_b_0_3 FLOAT NULL,\n um_count_0_5 FLOAT NULL,\n um_count_a_0_5 FLOAT NULL,\n um_count_b_0_5 FLOAT NULL,\n um_count_1_0 FLOAT NULL,\n um_count_a_1_0 FLOAT NULL,\n um_count_b_1_0 FLOAT NULL,\n um_count_2_5 FLOAT NULL,\n um_count_a_2_5 FLOAT NULL,\n um_count_b_2_5 FLOAT NULL,\n um_count_5_0 FLOAT NULL,\n um_count_a_5_0 FLOAT NULL,\n um_count_b_5_0 FLOAT NULL,\n um_count_10_0 FLOAT NULL,\n um_count_a_10_0 FLOAT NULL,\n um_count_b_10_0 FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: we can not start column names with numbers. So 0_3_um_count becomes um_count_0_3 PSQL statement for particle_count_fields table
- -PurpleAirPSQLQueryStatements.CREATE_PM10_0_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm10_0_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n pm10_0 FLOAT NULL,\n pm10_0_a FLOAT NULL,\n pm10_0_b FLOAT NULL,\n pm10_0_atm FLOAT NULL,\n pm10_0_atm_a FLOAT NULL,\n pm10_0_atm_b FLOAT NULL,\n pm10_0_cf_1 FLOAT NULL,\n pm10_0_cf_1_a FLOAT NULL,\n pm10_0_cf_1_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_PM10_0_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm10_0_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n pm10_0 FLOAT NULL,\n pm10_0_a FLOAT NULL,\n pm10_0_b FLOAT NULL,\n pm10_0_atm FLOAT NULL,\n pm10_0_atm_a FLOAT NULL,\n pm10_0_atm_b FLOAT NULL,\n pm10_0_cf_1 FLOAT NULL,\n pm10_0_cf_1_a FLOAT NULL,\n pm10_0_cf_1_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: Since we can’t have decimals in variable names, we do pm10_0 instead of pm10.0 PSQL statement for pm10_0_fields table
- -PurpleAirPSQLQueryStatements.CREATE_PM1_0_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm1_0_fields(\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n pm1_0 FLOAT NULL,\n pm1_0_a FLOAT NULL,\n pm1_0_b FLOAT NULL,\n pm1_0_atm FLOAT NULL,\n pm1_0_atm_a FLOAT NULL,\n pm1_0_atm_b FLOAT NULL,\n pm1_0_cf_1 FLOAT NULL,\n pm1_0_cf_1_a FLOAT NULL,\n pm1_0_cf_1_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_PM1_0_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm1_0_fields(\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n pm1_0 FLOAT NULL,\n pm1_0_a FLOAT NULL,\n pm1_0_b FLOAT NULL,\n pm1_0_atm FLOAT NULL,\n pm1_0_atm_a FLOAT NULL,\n pm1_0_atm_b FLOAT NULL,\n pm1_0_cf_1 FLOAT NULL,\n pm1_0_cf_1_a FLOAT NULL,\n pm1_0_cf_1_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: Since we can’t have decimals in variable names, we do pm1_0 instead of pm1.0 PSQL statement for pm1_0_fields table
- -PurpleAirPSQLQueryStatements.CREATE_PM2_5_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm2_5_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n pm2_5_alt FLOAT NULL,\n pm2_5_alt_a FLOAT NULL,\n pm2_5_alt_b FLOAT NULL,\n pm2_5 FLOAT NULL,\n pm2_5_a FLOAT NULL,\n pm2_5_b FLOAT NULL,\n pm2_5_atm FLOAT NULL,\n pm2_5_atm_a FLOAT NULL,\n pm2_5_atm_b FLOAT NULL,\n pm2_5_cf_1 FLOAT NULL,\n pm2_5_cf_1_a FLOAT NULL,\n pm2_5_cf_1_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_PM2_5_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm2_5_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n pm2_5_alt FLOAT NULL,\n pm2_5_alt_a FLOAT NULL,\n pm2_5_alt_b FLOAT NULL,\n pm2_5 FLOAT NULL,\n pm2_5_a FLOAT NULL,\n pm2_5_b FLOAT NULL,\n pm2_5_atm FLOAT NULL,\n pm2_5_atm_a FLOAT NULL,\n pm2_5_atm_b FLOAT NULL,\n pm2_5_cf_1 FLOAT NULL,\n pm2_5_cf_1_a FLOAT NULL,\n pm2_5_cf_1_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: Since we can’t have decimals in variable names, we do pm2_5 instead of pm2.5 PSQL statement for pm2_5_fields table
- -PurpleAirPSQLQueryStatements.CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm2_5_pseudo_average_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n pm2_5_10minute FLOAT NULL,\n pm2_5_10minute_a FLOAT NULL,\n pm2_5_10minute_b FLOAT NULL,\n pm2_5_30minute FLOAT NULL,\n pm2_5_30minute_a FLOAT NULL,\n pm2_5_30minute_b FLOAT NULL,\n pm2_5_60minute FLOAT NULL,\n pm2_5_60minute_a FLOAT NULL,\n pm2_5_60minute_b FLOAT NULL,\n pm2_5_6hour FLOAT NULL,\n pm2_5_6hour_a FLOAT NULL,\n pm2_5_6hour_b FLOAT NULL,\n pm2_5_24hour FLOAT NULL,\n pm2_5_24hour_a FLOAT NULL,\n pm2_5_24hour_b FLOAT NULL,\n pm2_5_1week FLOAT NULL,\n pm2_5_1week_a FLOAT NULL,\n pm2_5_1week_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm2_5_pseudo_average_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n pm2_5_10minute FLOAT NULL,\n pm2_5_10minute_a FLOAT NULL,\n pm2_5_10minute_b FLOAT NULL,\n pm2_5_30minute FLOAT NULL,\n pm2_5_30minute_a FLOAT NULL,\n pm2_5_30minute_b FLOAT NULL,\n pm2_5_60minute FLOAT NULL,\n pm2_5_60minute_a FLOAT NULL,\n pm2_5_60minute_b FLOAT NULL,\n pm2_5_6hour FLOAT NULL,\n pm2_5_6hour_a FLOAT NULL,\n pm2_5_6hour_b FLOAT NULL,\n pm2_5_24hour FLOAT NULL,\n pm2_5_24hour_a FLOAT NULL,\n pm2_5_24hour_b FLOAT NULL,\n pm2_5_1week FLOAT NULL,\n pm2_5_1week_a FLOAT NULL,\n pm2_5_1week_b FLOAT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: Since we can’t have decimals in variable names, we do pm2_5 instead of pm2.5 PSQL statement for pm2_5_pseudo_average_fields table
- -PurpleAirPSQLQueryStatements.CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE = '\n CREATE TABLE IF NOT EXISTS station_information_and_status_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n name TEXT NULL,\n icon INT NULL,\n model TEXT NULL,\n hardware TEXT NULL,\n location_type INT NULL,\n private INT NULL,\n latitude FLOAT NULL,\n longitude FLOAT NULL,\n altitude FLOAT NULL,\n position_rating INT NULL,\n led_brightness INT NULL,\n firmware_version TEXT NULL,\n firmware_upgrade TEXT NULL,\n rssi INT NULL,\n uptime INT NULL,\n pa_latency INT NULL,\n memory INT NULL,\n last_seen TIMESTAMPTZ NULL,\n last_modified TIMESTAMPTZ NULL,\n date_created TIMESTAMPTZ NULL,\n channel_state INT NULL,\n channel_flags INT NULL,\n channel_flags_manual INT NULL,\n channel_flags_auto INT NULL,\n confidence INT NULL,\n confidence_manual INT NULL,\n confidence_auto INT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE = '\n CREATE TABLE IF NOT EXISTS station_information_and_status_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n name TEXT NULL,\n icon INT NULL,\n model TEXT NULL,\n hardware TEXT NULL,\n location_type INT NULL,\n private INT NULL,\n latitude FLOAT NULL,\n longitude FLOAT NULL,\n altitude FLOAT NULL,\n position_rating INT NULL,\n led_brightness INT NULL,\n firmware_version TEXT NULL,\n firmware_upgrade TEXT NULL,\n rssi INT NULL,\n uptime INT NULL,\n pa_latency INT NULL,\n memory INT NULL,\n last_seen TIMESTAMPTZ NULL,\n last_modified TIMESTAMPTZ NULL,\n date_created TIMESTAMPTZ NULL,\n channel_state INT NULL,\n channel_flags INT NULL,\n channel_flags_manual INT NULL,\n channel_flags_auto INT NULL,\n confidence INT NULL,\n confidence_manual INT NULL,\n confidence_auto INT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
PSQL statement for station_information_and_status_fields table
- -PurpleAirPSQLQueryStatements.CREATE_THINGSPEAK_FIELDS = '\n CREATE TABLE IF NOT EXISTS thingspeak_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n primary_id_a INT NULL,\n primary_key_a TEXT NULL,\n secondary_id_a INT NULL,\n secondary_key_a TEXT NULL,\n primary_id_b INT NULL,\n primary_key_b TEXT NULL,\n secondary_id_b INT NULL,\n secondary_key_b TEXT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirPSQLQueryStatements.CREATE_THINGSPEAK_FIELDS = '\n CREATE TABLE IF NOT EXISTS thingspeak_fields (\n data_time_stamp TIMESTAMPTZ NOT NULL,\n sensor_index INT NOT NULL,\n primary_id_a INT NULL,\n primary_key_a TEXT NULL,\n secondary_id_a INT NULL,\n secondary_key_a TEXT NULL,\n primary_id_b INT NULL,\n primary_key_b TEXT NULL,\n secondary_id_b INT NULL,\n secondary_key_b TEXT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note TO SELF MAY END UP GETTING RID OF THIS TABLE. I SEE NO USE FOR IT. PSQL statement for thingspeak_fields table
- -PurpleAirPSQLQueryStatements.PSQL_CREATE_CONTINUOUS_AGGREGATE_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE = "\n SELECT add_continuous_aggregate_policy('sensor_index_and_name_1hour_aggregate',\n start_offset => INTERVAL '3 h',\n end_offset => INTERVAL '1 h',\n schedule_interval => INTERVAL '1 h',\n if_not_exists => true);\n " +PurpleAirPSQLQueryStatements.PSQL_CREATE_CONTINUOUS_AGGREGATE_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE = "\n SELECT add_continuous_aggregate_policy('sensor_index_and_name_1hour_aggregate',\n start_offset => INTERVAL '3 h',\n end_offset => INTERVAL '1 h',\n schedule_interval => INTERVAL '1 h',\n if_not_exists => true);\n "
PSQL statement to add a TimescaleDB continuous refresh policy on the sensor_index_and_name_1hour_aggregate materialized view Documentation can be found here: https://docs.timescale.com/timescaledb/latest/how-to-guides/continuous-aggregates/refresh-policies/
- -PurpleAirPSQLQueryStatements.PSQL_CREATE_DATA_RETENTION_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE = "\n SELECT add_retention_policy('sensor_index_and_name_1hour_aggregate',\n INTERVAL '8 hours',\n if_not_exists => true); \n " +PurpleAirPSQLQueryStatements.PSQL_CREATE_DATA_RETENTION_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE = "\n SELECT add_retention_policy('sensor_index_and_name_1hour_aggregate',\n INTERVAL '8 hours',\n if_not_exists => true); \n "
PSQL statement to add a TimescaleDB data retention policy on the sensor_index_and_name_1hour_aggregate materialized view Documentation can be found here: https://docs.timescale.com/timescaledb/latest/how-to-guides/data-retention/data-retention-with-continuous-aggregates/
- -PurpleAirPSQLQueryStatements.PSQL_CREATE_MATERIALIZED_VIEW_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE = "\n CREATE MATERIALIZED VIEW IF NOT EXISTS sensor_index_and_name_1hour_aggregate(data_time_stamp, sensor_index, name)\n WITH (timescaledb.continuous) AS\n\t SELECT time_bucket('1h', data_time_stamp), sensor_index, name\n\t FROM station_information_and_status_fields\n\t GROUP BY time_bucket('1h', data_time_stamp), sensor_index, name\n WITH NO DATA;\n " +PurpleAirPSQLQueryStatements.PSQL_CREATE_MATERIALIZED_VIEW_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE = "\n CREATE MATERIALIZED VIEW IF NOT EXISTS sensor_index_and_name_1hour_aggregate(data_time_stamp, sensor_index, name)\n WITH (timescaledb.continuous) AS\n\t SELECT time_bucket('1h', data_time_stamp), sensor_index, name\n\t FROM station_information_and_status_fields\n\t GROUP BY time_bucket('1h', data_time_stamp), sensor_index, name\n WITH NO DATA;\n "
PSQL statement to create a TimescaleDB Materialized View Documentation can be found here: https://docs.timescale.com/api/latest/continuous-aggregates/create_materialized_view/
- -PurpleAirPSQLQueryStatements.PSQL_DROP_ALL_TABLES = '\n DROP TABLE station_information_and_status_fields CASCADE;\n DROP TABLE environmental_fields CASCADE;\n DROP TABLE miscellaneous_fields CASCADE;\n DROP TABLE pm1_0_fields CASCADE;\n DROP TABLE pm2_5_fields CASCADE;\n DROP TABLE pm2_5_pseudo_average_fields CASCADE;\n DROP TABLE pm10_0_fields CASCADE;\n DROP TABLE particle_count_fields CASCADE;\n DROP TABLE thingspeak_fields CASCADE;\n ' +PurpleAirPSQLQueryStatements.PSQL_DROP_ALL_TABLES = '\n DROP TABLE station_information_and_status_fields CASCADE;\n DROP TABLE environmental_fields CASCADE;\n DROP TABLE miscellaneous_fields CASCADE;\n DROP TABLE pm1_0_fields CASCADE;\n DROP TABLE pm2_5_fields CASCADE;\n DROP TABLE pm2_5_pseudo_average_fields CASCADE;\n DROP TABLE pm10_0_fields CASCADE;\n DROP TABLE particle_count_fields CASCADE;\n DROP TABLE thingspeak_fields CASCADE;\n '
PSQL statement to drop all tables in the database
- -PurpleAirPSQLQueryStatements.PSQL_GET_LIST_OF_ACTIVE_COMPRESSION_POLICIES = "\n SELECT hypertable_name FROM timescaledb_information.jobs\n WHERE proc_name='policy_compression';\n " +PurpleAirPSQLQueryStatements.PSQL_GET_LIST_OF_ACTIVE_COMPRESSION_POLICIES = "\n SELECT hypertable_name FROM timescaledb_information.jobs\n WHERE proc_name='policy_compression';\n "
PSQL statement to see active TimescaleDB compression policies Documentation can be found here: https://docs.timescale.com/timescaledb/latest/how-to-guides/compression/about-compression/
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS = '\n INSERT INTO environmental_fields\n (\n data_time_stamp,\n sensor_index,\n humidity,\n humidity_a,\n humidity_b,\n temperature,\n temperature_a,\n temperature_b,\n pressure,\n pressure_a,\n pressure_b\n ) \n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:humidity AS INT),\n CAST(:humidity_a AS INT),\n CAST(:humidity_b AS INT),\n CAST(:temperature AS INT),\n CAST(:temperature_a AS INT),\n CAST(:temperature_b AS INT),\n CAST(:pressure AS FLOAT),\n CAST(:pressure_a AS FLOAT),\n CAST(:pressure_b AS FLOAT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS = '\n INSERT INTO environmental_fields\n (\n data_time_stamp,\n sensor_index,\n humidity,\n humidity_a,\n humidity_b,\n temperature,\n temperature_a,\n temperature_b,\n pressure,\n pressure_a,\n pressure_b\n ) \n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:humidity AS INT),\n CAST(:humidity_a AS INT),\n CAST(:humidity_b AS INT),\n CAST(:temperature AS INT),\n CAST(:temperature_a AS INT),\n CAST(:temperature_b AS INT),\n CAST(:pressure AS FLOAT),\n CAST(:pressure_a AS FLOAT),\n CAST(:pressure_b AS FLOAT)\n )'
PSQL insert statement for environmental_fields
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_MISCELLANEOUS_FIELDS = '\n INSERT INTO miscellaneous_fields\n (\n data_time_stamp,\n sensor_index,\n voc,\n voc_a,\n voc_b,\n ozone1,\n analog_input\n ) \n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:voc AS FLOAT),\n CAST(:voc_a AS FLOAT),\n CAST(:voc_b AS FLOAT),\n CAST(:ozone1 AS FLOAT),\n CAST(:analog_input AS FLOAT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_MISCELLANEOUS_FIELDS = '\n INSERT INTO miscellaneous_fields\n (\n data_time_stamp,\n sensor_index,\n voc,\n voc_a,\n voc_b,\n ozone1,\n analog_input\n ) \n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:voc AS FLOAT),\n CAST(:voc_a AS FLOAT),\n CAST(:voc_b AS FLOAT),\n CAST(:ozone1 AS FLOAT),\n CAST(:analog_input AS FLOAT)\n )'
PSQL insert statement for miscellaneous_fields
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS = '\n INSERT INTO particle_count_fields \n (\n data_time_stamp,\n sensor_index,\n um_count_0_3,\n um_count_a_0_3,\n um_count_b_0_3,\n um_count_0_5,\n um_count_a_0_5,\n um_count_b_0_5,\n um_count_1_0,\n um_count_a_1_0,\n um_count_b_1_0,\n um_count_2_5,\n um_count_a_2_5,\n um_count_b_2_5,\n um_count_5_0,\n um_count_a_5_0,\n um_count_b_5_0,\n um_count_10_0,\n um_count_a_10_0,\n um_count_b_10_0\n )\n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:um_count_0_3 AS FLOAT),\n CAST(:um_count_a_0_3 AS FLOAT),\n CAST(:um_count_b_0_3 AS FLOAT),\n CAST(:um_count_0_5 AS FLOAT),\n CAST(:um_count_a_0_5 AS FLOAT),\n CAST(:um_count_b_0_5 AS FLOAT),\n CAST(:um_count_1_0 AS FLOAT),\n CAST(:um_count_a_1_0 AS FLOAT),\n CAST(:um_count_b_1_0 AS FLOAT),\n CAST(:um_count_2_5 AS FLOAT),\n CAST(:um_count_a_2_5 AS FLOAT),\n CAST(:um_count_b_2_5 AS FLOAT),\n CAST(:um_count_5_0 AS FLOAT),\n CAST(:um_count_a_5_0 AS FLOAT),\n CAST(:um_count_b_5_0 AS FLOAT),\n CAST(:um_count_10_0 AS FLOAT),\n CAST(:um_count_a_10_0 AS FLOAT),\n CAST(:um_count_b_10_0 AS FLOAT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS = '\n INSERT INTO particle_count_fields \n (\n data_time_stamp,\n sensor_index,\n um_count_0_3,\n um_count_a_0_3,\n um_count_b_0_3,\n um_count_0_5,\n um_count_a_0_5,\n um_count_b_0_5,\n um_count_1_0,\n um_count_a_1_0,\n um_count_b_1_0,\n um_count_2_5,\n um_count_a_2_5,\n um_count_b_2_5,\n um_count_5_0,\n um_count_a_5_0,\n um_count_b_5_0,\n um_count_10_0,\n um_count_a_10_0,\n um_count_b_10_0\n )\n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:um_count_0_3 AS FLOAT),\n CAST(:um_count_a_0_3 AS FLOAT),\n CAST(:um_count_b_0_3 AS FLOAT),\n CAST(:um_count_0_5 AS FLOAT),\n CAST(:um_count_a_0_5 AS FLOAT),\n CAST(:um_count_b_0_5 AS FLOAT),\n CAST(:um_count_1_0 AS FLOAT),\n CAST(:um_count_a_1_0 AS FLOAT),\n CAST(:um_count_b_1_0 AS FLOAT),\n CAST(:um_count_2_5 AS FLOAT),\n CAST(:um_count_a_2_5 AS FLOAT),\n CAST(:um_count_b_2_5 AS FLOAT),\n CAST(:um_count_5_0 AS FLOAT),\n CAST(:um_count_a_5_0 AS FLOAT),\n CAST(:um_count_b_5_0 AS FLOAT),\n CAST(:um_count_10_0 AS FLOAT),\n CAST(:um_count_a_10_0 AS FLOAT),\n CAST(:um_count_b_10_0 AS FLOAT)\n )'
PSQL insert statement for particle_count_fields
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM10_0_FIELDS = '\n INSERT INTO pm10_0_fields\n (\n data_time_stamp,\n sensor_index,\n pm10_0,\n pm10_0_a,\n pm10_0_b,\n pm10_0_atm,\n pm10_0_atm_a,\n pm10_0_atm_b,\n pm10_0_cf_1,\n pm10_0_cf_1_a,\n pm10_0_cf_1_b\n ) \n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:pm10_0 AS FLOAT),\n CAST(:pm10_0_a AS FLOAT),\n CAST(:pm10_0_b AS FLOAT),\n CAST(:pm10_0_atm AS FLOAT),\n CAST(:pm10_0_atm_a AS FLOAT),\n CAST(:pm10_0_atm_b AS FLOAT),\n CAST(:pm10_0_cf_1 AS FLOAT),\n CAST(:pm10_0_cf_1_a AS FLOAT),\n CAST(:pm10_0_cf_1_b AS FLOAT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM10_0_FIELDS = '\n INSERT INTO pm10_0_fields\n (\n data_time_stamp,\n sensor_index,\n pm10_0,\n pm10_0_a,\n pm10_0_b,\n pm10_0_atm,\n pm10_0_atm_a,\n pm10_0_atm_b,\n pm10_0_cf_1,\n pm10_0_cf_1_a,\n pm10_0_cf_1_b\n ) \n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:pm10_0 AS FLOAT),\n CAST(:pm10_0_a AS FLOAT),\n CAST(:pm10_0_b AS FLOAT),\n CAST(:pm10_0_atm AS FLOAT),\n CAST(:pm10_0_atm_a AS FLOAT),\n CAST(:pm10_0_atm_b AS FLOAT),\n CAST(:pm10_0_cf_1 AS FLOAT),\n CAST(:pm10_0_cf_1_a AS FLOAT),\n CAST(:pm10_0_cf_1_b AS FLOAT)\n )'
PSQL insert statement for pm10_0_fields
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM1_0_FIELDS = '\n INSERT INTO pm1_0_fields\n (\n data_time_stamp,\n sensor_index,\n pm1_0,\n pm1_0_a,\n pm1_0_b,\n pm1_0_atm,\n pm1_0_atm_a,\n pm1_0_atm_b,\n pm1_0_cf_1,\n pm1_0_cf_1_a,\n pm1_0_cf_1_b\n )\n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:pm1_0 AS FLOAT),\n CAST(:pm1_0_a AS FLOAT),\n CAST(:pm1_0_b AS FLOAT),\n CAST(:pm1_0_atm AS FLOAT),\n CAST(:pm1_0_atm_a AS FLOAT),\n CAST(:pm1_0_atm_b AS FLOAT),\n CAST(:pm1_0_cf_1 AS FLOAT),\n CAST(:pm1_0_cf_1_a AS FLOAT),\n CAST(:pm1_0_cf_1_b AS FLOAT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM1_0_FIELDS = '\n INSERT INTO pm1_0_fields\n (\n data_time_stamp,\n sensor_index,\n pm1_0,\n pm1_0_a,\n pm1_0_b,\n pm1_0_atm,\n pm1_0_atm_a,\n pm1_0_atm_b,\n pm1_0_cf_1,\n pm1_0_cf_1_a,\n pm1_0_cf_1_b\n )\n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:pm1_0 AS FLOAT),\n CAST(:pm1_0_a AS FLOAT),\n CAST(:pm1_0_b AS FLOAT),\n CAST(:pm1_0_atm AS FLOAT),\n CAST(:pm1_0_atm_a AS FLOAT),\n CAST(:pm1_0_atm_b AS FLOAT),\n CAST(:pm1_0_cf_1 AS FLOAT),\n CAST(:pm1_0_cf_1_a AS FLOAT),\n CAST(:pm1_0_cf_1_b AS FLOAT)\n )'
PSQL insert statement for pm1_0_fields
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM2_5_FIELDS = '\n INSERT INTO pm2_5_fields\n (\n data_time_stamp,\n sensor_index,\n pm2_5_alt,\n pm2_5_alt_a,\n pm2_5_alt_b,\n pm2_5,\n pm2_5_a,\n pm2_5_b,\n pm2_5_atm,\n pm2_5_atm_a,\n pm2_5_atm_b,\n pm2_5_cf_1,\n pm2_5_cf_1_a,\n pm2_5_cf_1_b\n ) \n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:pm2_5_alt AS FLOAT),\n CAST(:pm2_5_alt_a AS FLOAT),\n CAST(:pm2_5_alt_b AS FLOAT),\n CAST(:pm2_5 AS FLOAT),\n CAST(:pm2_5_a AS FLOAT),\n CAST(:pm2_5_b AS FLOAT),\n CAST(:pm2_5_atm AS FLOAT),\n CAST(:pm2_5_atm_a AS FLOAT),\n CAST(:pm2_5_atm_b AS FLOAT),\n CAST(:pm2_5_cf_1 AS FLOAT),\n CAST(:pm2_5_cf_1_a AS FLOAT),\n CAST(:pm2_5_cf_1_b AS FLOAT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM2_5_FIELDS = '\n INSERT INTO pm2_5_fields\n (\n data_time_stamp,\n sensor_index,\n pm2_5_alt,\n pm2_5_alt_a,\n pm2_5_alt_b,\n pm2_5,\n pm2_5_a,\n pm2_5_b,\n pm2_5_atm,\n pm2_5_atm_a,\n pm2_5_atm_b,\n pm2_5_cf_1,\n pm2_5_cf_1_a,\n pm2_5_cf_1_b\n ) \n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:pm2_5_alt AS FLOAT),\n CAST(:pm2_5_alt_a AS FLOAT),\n CAST(:pm2_5_alt_b AS FLOAT),\n CAST(:pm2_5 AS FLOAT),\n CAST(:pm2_5_a AS FLOAT),\n CAST(:pm2_5_b AS FLOAT),\n CAST(:pm2_5_atm AS FLOAT),\n CAST(:pm2_5_atm_a AS FLOAT),\n CAST(:pm2_5_atm_b AS FLOAT),\n CAST(:pm2_5_cf_1 AS FLOAT),\n CAST(:pm2_5_cf_1_a AS FLOAT),\n CAST(:pm2_5_cf_1_b AS FLOAT)\n )'
PSQL insert statement for pm2_5_fields
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS = '\n INSERT INTO pm2_5_pseudo_average_fields \n (\n data_time_stamp,\n sensor_index,\n pm2_5_10minute,\n pm2_5_10minute_a,\n pm2_5_10minute_b,\n pm2_5_30minute,\n pm2_5_30minute_a,\n pm2_5_30minute_b,\n pm2_5_60minute,\n pm2_5_60minute_a,\n pm2_5_60minute_b,\n pm2_5_6hour,\n pm2_5_6hour_a,\n pm2_5_6hour_b,\n pm2_5_24hour,\n pm2_5_24hour_a,\n pm2_5_24hour_b,\n pm2_5_1week,\n pm2_5_1week_a,\n pm2_5_1week_b\n )\n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:pm2_5_10minute AS FLOAT),\n CAST(:pm2_5_10minute_a AS FLOAT),\n CAST(:pm2_5_10minute_b AS FLOAT),\n CAST(:pm2_5_30minute AS FLOAT),\n CAST(:pm2_5_30minute_a AS FLOAT),\n CAST(:pm2_5_30minute_b AS FLOAT),\n CAST(:pm2_5_60minute AS FLOAT),\n CAST(:pm2_5_60minute_a AS FLOAT),\n CAST(:pm2_5_60minute_b AS FLOAT),\n CAST(:pm2_5_6hour AS FLOAT),\n CAST(:pm2_5_6hour_a AS FLOAT),\n CAST(:pm2_5_6hour_b AS FLOAT),\n CAST(:pm2_5_24hour AS FLOAT),\n CAST(:pm2_5_24hour_a AS FLOAT),\n CAST(:pm2_5_24hour_b AS FLOAT),\n CAST(:pm2_5_1week AS FLOAT),\n CAST(:pm2_5_1week_a AS FLOAT),\n CAST(:pm2_5_1week_b AS FLOAT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS = '\n INSERT INTO pm2_5_pseudo_average_fields \n (\n data_time_stamp,\n sensor_index,\n pm2_5_10minute,\n pm2_5_10minute_a,\n pm2_5_10minute_b,\n pm2_5_30minute,\n pm2_5_30minute_a,\n pm2_5_30minute_b,\n pm2_5_60minute,\n pm2_5_60minute_a,\n pm2_5_60minute_b,\n pm2_5_6hour,\n pm2_5_6hour_a,\n pm2_5_6hour_b,\n pm2_5_24hour,\n pm2_5_24hour_a,\n pm2_5_24hour_b,\n pm2_5_1week,\n pm2_5_1week_a,\n pm2_5_1week_b\n )\n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:pm2_5_10minute AS FLOAT),\n CAST(:pm2_5_10minute_a AS FLOAT),\n CAST(:pm2_5_10minute_b AS FLOAT),\n CAST(:pm2_5_30minute AS FLOAT),\n CAST(:pm2_5_30minute_a AS FLOAT),\n CAST(:pm2_5_30minute_b AS FLOAT),\n CAST(:pm2_5_60minute AS FLOAT),\n CAST(:pm2_5_60minute_a AS FLOAT),\n CAST(:pm2_5_60minute_b AS FLOAT),\n CAST(:pm2_5_6hour AS FLOAT),\n CAST(:pm2_5_6hour_a AS FLOAT),\n CAST(:pm2_5_6hour_b AS FLOAT),\n CAST(:pm2_5_24hour AS FLOAT),\n CAST(:pm2_5_24hour_a AS FLOAT),\n CAST(:pm2_5_24hour_b AS FLOAT),\n CAST(:pm2_5_1week AS FLOAT),\n CAST(:pm2_5_1week_a AS FLOAT),\n CAST(:pm2_5_1week_b AS FLOAT)\n )'
PSQL insert statement for pm2_5_pseudo_average_fields
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS = '\n INSERT INTO station_information_and_status_fields\n (\n data_time_stamp,\n sensor_index,\n name,\n icon,\n model,\n hardware,\n location_type,\n private,\n latitude,\n longitude,\n altitude,\n position_rating,\n led_brightness,\n firmware_version,\n firmware_upgrade,\n rssi,\n uptime,\n pa_latency,\n memory,\n last_seen,\n last_modified,\n date_created,\n channel_state,\n channel_flags,\n channel_flags_manual,\n channel_flags_auto,\n confidence,\n confidence_manual,\n confidence_auto\n )\n VALUES \n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:name AS TEXT),\n CAST(:icon AS INT),\n CAST(:model AS TEXT),\n CAST(:hardware AS TEXT),\n CAST(:location_type AS INT),\n CAST(:private AS INT),\n CAST(:latitude AS FLOAT),\n CAST(:longitude AS FLOAT),\n CAST(:altitude AS FLOAT),\n CAST(:position_rating AS INT),\n CAST(:led_brightness AS INT),\n CAST(:firmware_version AS TEXT),\n CAST(:firmware_upgrade AS TEXT),\n CAST(:rssi AS INT),\n CAST(:uptime AS INT),\n CAST(:pa_latency AS INT),\n CAST(:memory AS INT),\n CAST(:last_seen AS TIMESTAMPTZ),\n CAST(:last_modified AS TIMESTAMPTZ),\n CAST(:date_created AS TIMESTAMPTZ),\n CAST(:channel_state AS INT),\n CAST(:channel_flags AS INT),\n CAST(:channel_flags_manual AS INT),\n CAST(:channel_flags_auto AS INT),\n CAST(:confidence AS INT),\n CAST(:confidence_manual AS INT),\n CAST(:confidence_auto AS INT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS = '\n INSERT INTO station_information_and_status_fields\n (\n data_time_stamp,\n sensor_index,\n name,\n icon,\n model,\n hardware,\n location_type,\n private,\n latitude,\n longitude,\n altitude,\n position_rating,\n led_brightness,\n firmware_version,\n firmware_upgrade,\n rssi,\n uptime,\n pa_latency,\n memory,\n last_seen,\n last_modified,\n date_created,\n channel_state,\n channel_flags,\n channel_flags_manual,\n channel_flags_auto,\n confidence,\n confidence_manual,\n confidence_auto\n )\n VALUES \n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:name AS TEXT),\n CAST(:icon AS INT),\n CAST(:model AS TEXT),\n CAST(:hardware AS TEXT),\n CAST(:location_type AS INT),\n CAST(:private AS INT),\n CAST(:latitude AS FLOAT),\n CAST(:longitude AS FLOAT),\n CAST(:altitude AS FLOAT),\n CAST(:position_rating AS INT),\n CAST(:led_brightness AS INT),\n CAST(:firmware_version AS TEXT),\n CAST(:firmware_upgrade AS TEXT),\n CAST(:rssi AS INT),\n CAST(:uptime AS INT),\n CAST(:pa_latency AS INT),\n CAST(:memory AS INT),\n CAST(:last_seen AS TIMESTAMPTZ),\n CAST(:last_modified AS TIMESTAMPTZ),\n CAST(:date_created AS TIMESTAMPTZ),\n CAST(:channel_state AS INT),\n CAST(:channel_flags AS INT),\n CAST(:channel_flags_manual AS INT),\n CAST(:channel_flags_auto AS INT),\n CAST(:confidence AS INT),\n CAST(:confidence_manual AS INT),\n CAST(:confidence_auto AS INT)\n )'
PSQL insert statement for station_information_and_status_fields
- -PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_THINGSPEAK_FIELDS = '\n INSERT INTO thingspeak_fields\n (\n data_time_stamp,\n sensor_index,\n primary_id_a,\n primary_key_a,\n secondary_id_a,\n secondary_key_a,\n primary_id_b,\n primary_key_b,\n secondary_id_b,\n secondary_key_b\n )\n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:primary_id_a AS INT),\n CAST(:primary_key_a AS TEXT),\n CAST(:secondary_id_a AS INT),\n CAST(:secondary_key_a AS TEXT),\n CAST(:primary_id_b AS INT),\n CAST(:primary_key_b AS TEXT),\n CAST(:secondary_id_b AS INT),\n CAST(:secondary_key_b AS TEXT)\n )' +PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_THINGSPEAK_FIELDS = '\n INSERT INTO thingspeak_fields\n (\n data_time_stamp,\n sensor_index,\n primary_id_a,\n primary_key_a,\n secondary_id_a,\n secondary_key_a,\n primary_id_b,\n primary_key_b,\n secondary_id_b,\n secondary_key_b\n )\n VALUES\n (\n CAST(:data_time_stamp AS TIMESTAMPTZ),\n CAST(:sensor_index AS INT),\n CAST(:primary_id_a AS INT),\n CAST(:primary_key_a AS TEXT),\n CAST(:secondary_id_a AS INT),\n CAST(:secondary_key_a AS TEXT),\n CAST(:primary_id_b AS INT),\n CAST(:primary_key_b AS TEXT),\n CAST(:secondary_id_b AS INT),\n CAST(:secondary_key_b AS TEXT)\n )'
PSQL insert statement for thingspeak_fields
- PurpleAirCSVDataLogger module
- PurpleAirCSVDataLoggerConstants module
- PurpleAirDataLogger module +
- PurpleAirDataLoggerHelpers module
- PurpleAirPSQLDataLogger module
- PurpleAirPSQLQueryStatements module
- PurpleAirSQLiteDataLogger module
-
@@ -88,7 +89,7 @@
- -class PurpleAirSQLiteDataLogger.PurpleAirSQLiteDataLogger(PurpleAirAPIReadKey, PurpleAirAPIWriteKey, sqlite_data_base_name) +class PurpleAirSQLiteDataLogger.PurpleAirSQLiteDataLogger(PurpleAirAPIReadKey, PurpleAirAPIWriteKey, sqlite_data_base_name)
Bases:
PurpleAirDataLogger
The logger class. For now we will insert data into a SQLite3 database file.
- -store_sensor_data(single_sensor_data_dict) +store_sensor_data(single_sensor_data_dict)
Insert the sensor data into the database.
- Parameters: diff --git a/docs/html/PurpleAirSQLiteQueryStatements.html b/docs/html/PurpleAirSQLiteQueryStatements.html index 546e6fb..7253fdb 100644 --- a/docs/html/PurpleAirSQLiteQueryStatements.html +++ b/docs/html/PurpleAirSQLiteQueryStatements.html @@ -4,18 +4,18 @@ -
PurpleAirSQLiteQueryStatements module — PurpleAir Data Logger(s) V1.2.1 documentation +PurpleAirSQLiteQueryStatements module — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + @@ -47,6 +47,7 @@- PurpleAirCSVDataLogger module
- PurpleAirCSVDataLoggerConstants module
- PurpleAirDataLogger module
+- PurpleAirDataLoggerHelpers module
- PurpleAirPSQLDataLogger module
- PurpleAirPSQLQueryStatements module
- PurpleAirSQLiteDataLogger module
@@ -102,128 +103,128 @@- PurpleAirSQLiteQueryStatements module
+PurpleAirSQLiteQueryStatements module
Copyright 2022 carlkidcrypto, All rights reserved. A file containing SQLITE statements defined as constants. Generate the SQLITE query strings. For simplicity our table names will match what the PurpleAir documentation says. We will do the same for table column names.
- -PurpleAirSQLiteQueryStatements.CREATE_ENVIRONMENTAL_FIELDS_TABLE = '\n CREATE TABLE IF NOT EXISTS environmental_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n humidity INTEGER NULL,\n humidity_a INTEGER NULL,\n humidity_b INTEGER NULL,\n temperature INTEGER NULL,\n temperature_a INTEGER NULL,\n temperature_b INTEGER NULL,\n pressure REAL NULL,\n pressure_a REAL NULL,\n pressure_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_ENVIRONMENTAL_FIELDS_TABLE = '\n CREATE TABLE IF NOT EXISTS environmental_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n humidity INTEGER NULL,\n humidity_a INTEGER NULL,\n humidity_b INTEGER NULL,\n temperature INTEGER NULL,\n temperature_a INTEGER NULL,\n temperature_b INTEGER NULL,\n pressure REAL NULL,\n pressure_a REAL NULL,\n pressure_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
SQLITE statement for environmental_fields table
- -PurpleAirSQLiteQueryStatements.CREATE_MISCELLANEOUS_FIELDS = '\n CREATE TABLE IF NOT EXISTS miscellaneous_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n voc REAL NULL,\n voc_a REAL NULL,\n voc_b REAL NULL,\n ozone1 REAL NULL,\n analog_input REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_MISCELLANEOUS_FIELDS = '\n CREATE TABLE IF NOT EXISTS miscellaneous_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n voc REAL NULL,\n voc_a REAL NULL,\n voc_b REAL NULL,\n ozone1 REAL NULL,\n analog_input REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
SQLITE statement for miscellaneous_fields table
- -PurpleAirSQLiteQueryStatements.CREATE_PARTICLE_COUNT_FIELDS = '\n CREATE TABLE IF NOT EXISTS particle_count_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n um_count_0_3 REAL NULL,\n um_count_a_0_3 REAL NULL,\n um_count_b_0_3 REAL NULL,\n um_count_0_5 REAL NULL,\n um_count_a_0_5 REAL NULL,\n um_count_b_0_5 REAL NULL,\n um_count_1_0 REAL NULL,\n um_count_a_1_0 REAL NULL,\n um_count_b_1_0 REAL NULL,\n um_count_2_5 REAL NULL,\n um_count_a_2_5 REAL NULL,\n um_count_b_2_5 REAL NULL,\n um_count_5_0 REAL NULL,\n um_count_a_5_0 REAL NULL,\n um_count_b_5_0 REAL NULL,\n um_count_10_0 REAL NULL,\n um_count_a_10_0 REAL NULL,\n um_count_b_10_0 REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_PARTICLE_COUNT_FIELDS = '\n CREATE TABLE IF NOT EXISTS particle_count_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n um_count_0_3 REAL NULL,\n um_count_a_0_3 REAL NULL,\n um_count_b_0_3 REAL NULL,\n um_count_0_5 REAL NULL,\n um_count_a_0_5 REAL NULL,\n um_count_b_0_5 REAL NULL,\n um_count_1_0 REAL NULL,\n um_count_a_1_0 REAL NULL,\n um_count_b_1_0 REAL NULL,\n um_count_2_5 REAL NULL,\n um_count_a_2_5 REAL NULL,\n um_count_b_2_5 REAL NULL,\n um_count_5_0 REAL NULL,\n um_count_a_5_0 REAL NULL,\n um_count_b_5_0 REAL NULL,\n um_count_10_0 REAL NULL,\n um_count_a_10_0 REAL NULL,\n um_count_b_10_0 REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: we can not start column names with numbers. So 0_3_um_count becomes um_count_0_3 SQLITE statement for particle_count_fields table
- -PurpleAirSQLiteQueryStatements.CREATE_PM10_0_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm10_0_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n pm10_0 REAL NULL,\n pm10_0_a REAL NULL,\n pm10_0_b REAL NULL,\n pm10_0_atm REAL NULL,\n pm10_0_atm_a REAL NULL,\n pm10_0_atm_b REAL NULL,\n pm10_0_cf_1 REAL NULL,\n pm10_0_cf_1_a REAL NULL,\n pm10_0_cf_1_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_PM10_0_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm10_0_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n pm10_0 REAL NULL,\n pm10_0_a REAL NULL,\n pm10_0_b REAL NULL,\n pm10_0_atm REAL NULL,\n pm10_0_atm_a REAL NULL,\n pm10_0_atm_b REAL NULL,\n pm10_0_cf_1 REAL NULL,\n pm10_0_cf_1_a REAL NULL,\n pm10_0_cf_1_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: Since we can’t have decimals in variable names, we do pm10_0 instead of pm10.0 SQLITE statement for pm10_0_fields table
- -PurpleAirSQLiteQueryStatements.CREATE_PM1_0_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm1_0_fields(\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n pm1_0 REAL NULL,\n pm1_0_a REAL NULL,\n pm1_0_b REAL NULL,\n pm1_0_atm REAL NULL,\n pm1_0_atm_a REAL NULL,\n pm1_0_atm_b REAL NULL,\n pm1_0_cf_1 REAL NULL,\n pm1_0_cf_1_a REAL NULL,\n pm1_0_cf_1_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_PM1_0_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm1_0_fields(\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n pm1_0 REAL NULL,\n pm1_0_a REAL NULL,\n pm1_0_b REAL NULL,\n pm1_0_atm REAL NULL,\n pm1_0_atm_a REAL NULL,\n pm1_0_atm_b REAL NULL,\n pm1_0_cf_1 REAL NULL,\n pm1_0_cf_1_a REAL NULL,\n pm1_0_cf_1_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: Since we can’t have decimals in variable names, we do pm1_0 instead of pm1.0 SQLITE statement for pm1_0_fields table
- -PurpleAirSQLiteQueryStatements.CREATE_PM2_5_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm2_5_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n pm2_5_alt REAL NULL,\n pm2_5_alt_a REAL NULL,\n pm2_5_alt_b REAL NULL,\n pm2_5 REAL NULL,\n pm2_5_a REAL NULL,\n pm2_5_b REAL NULL,\n pm2_5_atm REAL NULL,\n pm2_5_atm_a REAL NULL,\n pm2_5_atm_b REAL NULL,\n pm2_5_cf_1 REAL NULL,\n pm2_5_cf_1_a REAL NULL,\n pm2_5_cf_1_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_PM2_5_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm2_5_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n pm2_5_alt REAL NULL,\n pm2_5_alt_a REAL NULL,\n pm2_5_alt_b REAL NULL,\n pm2_5 REAL NULL,\n pm2_5_a REAL NULL,\n pm2_5_b REAL NULL,\n pm2_5_atm REAL NULL,\n pm2_5_atm_a REAL NULL,\n pm2_5_atm_b REAL NULL,\n pm2_5_cf_1 REAL NULL,\n pm2_5_cf_1_a REAL NULL,\n pm2_5_cf_1_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: Since we can’t have decimals in variable names, we do pm2_5 instead of pm2.5 SQLITE statement for pm2_5_fields table
- -PurpleAirSQLiteQueryStatements.CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm2_5_pseudo_average_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n pm2_5_10minute REAL NULL,\n pm2_5_10minute_a REAL NULL,\n pm2_5_10minute_b REAL NULL,\n pm2_5_30minute REAL NULL,\n pm2_5_30minute_a REAL NULL,\n pm2_5_30minute_b REAL NULL,\n pm2_5_60minute REAL NULL,\n pm2_5_60minute_a REAL NULL,\n pm2_5_60minute_b REAL NULL,\n pm2_5_6hour REAL NULL,\n pm2_5_6hour_a REAL NULL,\n pm2_5_6hour_b REAL NULL,\n pm2_5_24hour REAL NULL,\n pm2_5_24hour_a REAL NULL,\n pm2_5_24hour_b REAL NULL,\n pm2_5_1week REAL NULL,\n pm2_5_1week_a REAL NULL,\n pm2_5_1week_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS = '\n CREATE TABLE IF NOT EXISTS pm2_5_pseudo_average_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n pm2_5_10minute REAL NULL,\n pm2_5_10minute_a REAL NULL,\n pm2_5_10minute_b REAL NULL,\n pm2_5_30minute REAL NULL,\n pm2_5_30minute_a REAL NULL,\n pm2_5_30minute_b REAL NULL,\n pm2_5_60minute REAL NULL,\n pm2_5_60minute_a REAL NULL,\n pm2_5_60minute_b REAL NULL,\n pm2_5_6hour REAL NULL,\n pm2_5_6hour_a REAL NULL,\n pm2_5_6hour_b REAL NULL,\n pm2_5_24hour REAL NULL,\n pm2_5_24hour_a REAL NULL,\n pm2_5_24hour_b REAL NULL,\n pm2_5_1week REAL NULL,\n pm2_5_1week_a REAL NULL,\n pm2_5_1week_b REAL NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note: Since we can’t have decimals in variable names, we do pm2_5 instead of pm2.5 SQLITE statement for pm2_5_pseudo_average_fields table
- -PurpleAirSQLiteQueryStatements.CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE = '\n CREATE TABLE IF NOT EXISTS station_information_and_status_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n name TEXT NULL,\n icon INTEGER NULL,\n model TEXT NULL,\n hardware TEXT NULL,\n location_type INTEGER NULL,\n private INTEGER NULL,\n latitude REAL NULL,\n longitude REAL NULL,\n altitude REAL NULL,\n position_rating INTEGER NULL,\n led_brightness INTEGER NULL,\n firmware_version TEXT NULL,\n firmware_upgrade TEXT NULL,\n rssi INTEGER NULL,\n uptime INTEGER NULL,\n pa_latency INTEGER NULL,\n memory INTEGER NULL,\n last_seen TEXT NULL,\n last_modified TEXT NULL,\n date_created TEXT NULL,\n channel_state INTEGER NULL,\n channel_flags INTEGER NULL,\n channel_flags_manual INTEGER NULL,\n channel_flags_auto INTEGER NULL,\n confidence INTEGER NULL,\n confidence_manual INTEGER NULL,\n confidence_auto INTEGER NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE = '\n CREATE TABLE IF NOT EXISTS station_information_and_status_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n name TEXT NULL,\n icon INTEGER NULL,\n model TEXT NULL,\n hardware TEXT NULL,\n location_type INTEGER NULL,\n private INTEGER NULL,\n latitude REAL NULL,\n longitude REAL NULL,\n altitude REAL NULL,\n position_rating INTEGER NULL,\n led_brightness INTEGER NULL,\n firmware_version TEXT NULL,\n firmware_upgrade TEXT NULL,\n rssi INTEGER NULL,\n uptime INTEGER NULL,\n pa_latency INTEGER NULL,\n memory INTEGER NULL,\n last_seen TEXT NULL,\n last_modified TEXT NULL,\n date_created TEXT NULL,\n channel_state INTEGER NULL,\n channel_flags INTEGER NULL,\n channel_flags_manual INTEGER NULL,\n channel_flags_auto INTEGER NULL,\n confidence INTEGER NULL,\n confidence_manual INTEGER NULL,\n confidence_auto INTEGER NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
SQLITE statement for station_information_and_status_fields table
- -PurpleAirSQLiteQueryStatements.CREATE_THINGSPEAK_FIELDS = '\n CREATE TABLE IF NOT EXISTS thingspeak_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n primary_id_a INTEGER NULL,\n primary_key_a TEXT NULL,\n secondary_id_a INTEGER NULL,\n secondary_key_a TEXT NULL,\n primary_id_b INTEGER NULL,\n primary_key_b TEXT NULL,\n secondary_id_b INTEGER NULL,\n secondary_key_b TEXT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))' +PurpleAirSQLiteQueryStatements.CREATE_THINGSPEAK_FIELDS = '\n CREATE TABLE IF NOT EXISTS thingspeak_fields (\n data_time_stamp TEXT NOT NULL,\n sensor_index INTEGER NOT NULL,\n primary_id_a INTEGER NULL,\n primary_key_a TEXT NULL,\n secondary_id_a INTEGER NULL,\n secondary_key_a TEXT NULL,\n primary_id_b INTEGER NULL,\n primary_key_b TEXT NULL,\n secondary_id_b INTEGER NULL,\n secondary_key_b TEXT NULL,\n PRIMARY KEY(data_time_stamp, sensor_index))'
Note TO SELF MAY END UP GETTING RID OF THIS TABLE. I SEE NO USE FOR IT. SQLITE statement for thingspeak_fields table
- -PurpleAirSQLiteQueryStatements.SQLITE_DROP_ALL_TABLES = '\n DROP TABLE station_information_and_status_fields;\n DROP TABLE environmental_fields;\n DROP TABLE miscellaneous_fields;\n DROP TABLE pm1_0_fields;\n DROP TABLE pm2_5_fields;\n DROP TABLE pm2_5_pseudo_average_fields;\n DROP TABLE pm10_0_fields;\n DROP TABLE particle_count_fields;\n DROP TABLE thingspeak_fields;\n ' +PurpleAirSQLiteQueryStatements.SQLITE_DROP_ALL_TABLES = '\n DROP TABLE station_information_and_status_fields;\n DROP TABLE environmental_fields;\n DROP TABLE miscellaneous_fields;\n DROP TABLE pm1_0_fields;\n DROP TABLE pm2_5_fields;\n DROP TABLE pm2_5_pseudo_average_fields;\n DROP TABLE pm10_0_fields;\n DROP TABLE particle_count_fields;\n DROP TABLE thingspeak_fields;\n '
SQLITE statement to drop all tables in the database
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS = '\n INSERT INTO environmental_fields\n (\n data_time_stamp,\n sensor_index,\n humidity,\n humidity_a,\n humidity_b,\n temperature,\n temperature_a,\n temperature_b,\n pressure,\n pressure_a,\n pressure_b\n ) \n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS = '\n INSERT INTO environmental_fields\n (\n data_time_stamp,\n sensor_index,\n humidity,\n humidity_a,\n humidity_b,\n temperature,\n temperature_a,\n temperature_b,\n pressure,\n pressure_a,\n pressure_b\n ) \n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for environmental_fields
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_MISCELLANEOUS_FIELDS = '\n INSERT INTO miscellaneous_fields\n (\n data_time_stamp,\n sensor_index,\n voc,\n voc_a,\n voc_b,\n ozone1,\n analog_input\n ) \n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_MISCELLANEOUS_FIELDS = '\n INSERT INTO miscellaneous_fields\n (\n data_time_stamp,\n sensor_index,\n voc,\n voc_a,\n voc_b,\n ozone1,\n analog_input\n ) \n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for miscellaneous_fields
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS = '\n INSERT INTO particle_count_fields \n (\n data_time_stamp,\n sensor_index,\n um_count_0_3,\n um_count_a_0_3,\n um_count_b_0_3,\n um_count_0_5,\n um_count_a_0_5,\n um_count_b_0_5,\n um_count_1_0,\n um_count_a_1_0,\n um_count_b_1_0,\n um_count_2_5,\n um_count_a_2_5,\n um_count_b_2_5,\n um_count_5_0,\n um_count_a_5_0,\n um_count_b_5_0,\n um_count_10_0,\n um_count_a_10_0,\n um_count_b_10_0\n )\n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS = '\n INSERT INTO particle_count_fields \n (\n data_time_stamp,\n sensor_index,\n um_count_0_3,\n um_count_a_0_3,\n um_count_b_0_3,\n um_count_0_5,\n um_count_a_0_5,\n um_count_b_0_5,\n um_count_1_0,\n um_count_a_1_0,\n um_count_b_1_0,\n um_count_2_5,\n um_count_a_2_5,\n um_count_b_2_5,\n um_count_5_0,\n um_count_a_5_0,\n um_count_b_5_0,\n um_count_10_0,\n um_count_a_10_0,\n um_count_b_10_0\n )\n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for particle_count_fields
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM10_0_FIELDS = '\n INSERT INTO pm10_0_fields\n (\n data_time_stamp,\n sensor_index,\n pm10_0,\n pm10_0_a,\n pm10_0_b,\n pm10_0_atm,\n pm10_0_atm_a,\n pm10_0_atm_b,\n pm10_0_cf_1,\n pm10_0_cf_1_a,\n pm10_0_cf_1_b\n ) \n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM10_0_FIELDS = '\n INSERT INTO pm10_0_fields\n (\n data_time_stamp,\n sensor_index,\n pm10_0,\n pm10_0_a,\n pm10_0_b,\n pm10_0_atm,\n pm10_0_atm_a,\n pm10_0_atm_b,\n pm10_0_cf_1,\n pm10_0_cf_1_a,\n pm10_0_cf_1_b\n ) \n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for pm10_0_fields
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM1_0_FIELDS = '\n INSERT INTO pm1_0_fields\n (\n data_time_stamp,\n sensor_index,\n pm1_0,\n pm1_0_a,\n pm1_0_b,\n pm1_0_atm,\n pm1_0_atm_a,\n pm1_0_atm_b,\n pm1_0_cf_1,\n pm1_0_cf_1_a,\n pm1_0_cf_1_b\n )\n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM1_0_FIELDS = '\n INSERT INTO pm1_0_fields\n (\n data_time_stamp,\n sensor_index,\n pm1_0,\n pm1_0_a,\n pm1_0_b,\n pm1_0_atm,\n pm1_0_atm_a,\n pm1_0_atm_b,\n pm1_0_cf_1,\n pm1_0_cf_1_a,\n pm1_0_cf_1_b\n )\n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for pm1_0_fields
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM2_5_FIELDS = '\n INSERT INTO pm2_5_fields\n (\n data_time_stamp,\n sensor_index,\n pm2_5_alt,\n pm2_5_alt_a,\n pm2_5_alt_b,\n pm2_5,\n pm2_5_a,\n pm2_5_b,\n pm2_5_atm,\n pm2_5_atm_a,\n pm2_5_atm_b,\n pm2_5_cf_1,\n pm2_5_cf_1_a,\n pm2_5_cf_1_b\n ) \n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM2_5_FIELDS = '\n INSERT INTO pm2_5_fields\n (\n data_time_stamp,\n sensor_index,\n pm2_5_alt,\n pm2_5_alt_a,\n pm2_5_alt_b,\n pm2_5,\n pm2_5_a,\n pm2_5_b,\n pm2_5_atm,\n pm2_5_atm_a,\n pm2_5_atm_b,\n pm2_5_cf_1,\n pm2_5_cf_1_a,\n pm2_5_cf_1_b\n ) \n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for pm2_5_fields
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS = '\n INSERT INTO pm2_5_pseudo_average_fields \n (\n data_time_stamp,\n sensor_index,\n pm2_5_10minute,\n pm2_5_10minute_a,\n pm2_5_10minute_b,\n pm2_5_30minute,\n pm2_5_30minute_a,\n pm2_5_30minute_b,\n pm2_5_60minute,\n pm2_5_60minute_a,\n pm2_5_60minute_b,\n pm2_5_6hour,\n pm2_5_6hour_a,\n pm2_5_6hour_b,\n pm2_5_24hour,\n pm2_5_24hour_a,\n pm2_5_24hour_b,\n pm2_5_1week,\n pm2_5_1week_a,\n pm2_5_1week_b\n )\n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS = '\n INSERT INTO pm2_5_pseudo_average_fields \n (\n data_time_stamp,\n sensor_index,\n pm2_5_10minute,\n pm2_5_10minute_a,\n pm2_5_10minute_b,\n pm2_5_30minute,\n pm2_5_30minute_a,\n pm2_5_30minute_b,\n pm2_5_60minute,\n pm2_5_60minute_a,\n pm2_5_60minute_b,\n pm2_5_6hour,\n pm2_5_6hour_a,\n pm2_5_6hour_b,\n pm2_5_24hour,\n pm2_5_24hour_a,\n pm2_5_24hour_b,\n pm2_5_1week,\n pm2_5_1week_a,\n pm2_5_1week_b\n )\n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for pm2_5_pseudo_average_fields
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS = '\n INSERT INTO station_information_and_status_fields\n (\n data_time_stamp,\n sensor_index,\n name,\n icon,\n model,\n hardware,\n location_type,\n private,\n latitude,\n longitude,\n altitude,\n position_rating,\n led_brightness,\n firmware_version,\n firmware_upgrade,\n rssi,\n uptime,\n pa_latency,\n memory,\n last_seen,\n last_modified,\n date_created,\n channel_state,\n channel_flags,\n channel_flags_manual,\n channel_flags_auto,\n confidence,\n confidence_manual,\n confidence_auto\n )\n VALUES \n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS = '\n INSERT INTO station_information_and_status_fields\n (\n data_time_stamp,\n sensor_index,\n name,\n icon,\n model,\n hardware,\n location_type,\n private,\n latitude,\n longitude,\n altitude,\n position_rating,\n led_brightness,\n firmware_version,\n firmware_upgrade,\n rssi,\n uptime,\n pa_latency,\n memory,\n last_seen,\n last_modified,\n date_created,\n channel_state,\n channel_flags,\n channel_flags_manual,\n channel_flags_auto,\n confidence,\n confidence_manual,\n confidence_auto\n )\n VALUES \n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for station_information_and_status_fields
- -PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_THINGSPEAK_FIELDS = '\n INSERT INTO thingspeak_fields\n (\n data_time_stamp,\n sensor_index,\n primary_id_a,\n primary_key_a,\n secondary_id_a,\n secondary_key_a,\n primary_id_b,\n primary_key_b,\n secondary_id_b,\n secondary_key_b\n )\n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )' +PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_THINGSPEAK_FIELDS = '\n INSERT INTO thingspeak_fields\n (\n data_time_stamp,\n sensor_index,\n primary_id_a,\n primary_key_a,\n secondary_id_a,\n secondary_key_a,\n primary_id_b,\n primary_key_b,\n secondary_id_b,\n secondary_key_b\n )\n VALUES\n (\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?,\n ?\n )'
SQLITE insert statement for thingspeak_fields
Index — PurpleAir Data Logger(s) V1.2.1 documentation +Index — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + @@ -73,7 +73,9 @@Index
C | E + | F | G + | L | M | P | S @@ -84,6 +86,8 @@Index
C
++ - - CREATE_PM2_5_FIELDS (in module PurpleAirPSQLQueryStatements)
-
@@ -155,10 +159,18 @@
E
F
+
++ + G
+-
@@ -167,6 +179,22 @@
G
L
+
++ + + M
--
@@ -183,6 +211,8 @@
- PurpleAirCSVDataLoggerConstants
- PurpleAirDataLogger + +
- PurpleAirDataLoggerHelpers
- PurpleAirPSQLDataLogger @@ -241,10 +271,10 @@
- PSQL_INSERT_STATEMENT_PM2_5_FIELDS (in module PurpleAirPSQLQueryStatements) -
M
P
+ - PSQL_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS (in module PurpleAirPSQLQueryStatements)
- PSQL_INSERT_STATEMENT_THINGSPEAK_FIELDS (in module PurpleAirPSQLQueryStatements)
@@ -277,6 +307,13 @@
P
- PurpleAirDataLoggerError
-
+ PurpleAirDataLoggerHelpers
+
+
-
+
- module + +
+ -
PurpleAirPSQLDataLogger
-
@@ -371,6 +408,10 @@
- PurpleAirCSVDataLoggerConstants module
- PurpleAirDataLogger module +
- PurpleAirDataLoggerHelpers module
- PurpleAirPSQLDataLogger module
- PurpleAirPSQLQueryStatements module
- PurpleAirSQLiteDataLogger module @@ -89,7 +90,7 @@
- diff --git a/docs/html/modules.html b/docs/html/modules.html index 30bab78..671aacb 100644 --- a/docs/html/modules.html +++ b/docs/html/modules.html @@ -4,18 +4,18 @@ -
- PurpleAirCSVDataLogger module
- PurpleAirCSVDataLoggerConstants module
- PurpleAirDataLogger module +
- PurpleAirDataLoggerHelpers module
- PurpleAirPSQLDataLogger module
- PurpleAirPSQLQueryStatements module
- PurpleAirSQLiteDataLogger module @@ -81,7 +82,7 @@
- PurpleAirDataLoggerHelpers module
- PurpleAirPSQLDataLogger module
-
diff --git a/docs/html/objects.inv b/docs/html/objects.inv
index 55bd320..e4471e4 100644
Binary files a/docs/html/objects.inv and b/docs/html/objects.inv differ
diff --git a/docs/html/py-modindex.html b/docs/html/py-modindex.html
index 06f43e2..b837a07 100644
--- a/docs/html/py-modindex.html
+++ b/docs/html/py-modindex.html
@@ -3,18 +3,18 @@
-
Python Module Index — PurpleAir Data Logger(s) V1.2.1 documentation +Python Module Index — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + @@ -100,6 +100,11 @@Python Module Index
PurpleAirDataLogger
V
diff --git a/docs/html/index.html b/docs/html/index.html index 2b2d1b8..d4bbbe3 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -4,18 +4,18 @@ -+ Welcome to PurpleAir Data Logger(s)’s documentation! — PurpleAir Data Logger(s) V1.2.1 documentation +Welcome to PurpleAir Data Logger(s)’s documentation! — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + @@ -71,7 +71,7 @@- Welcome to PurpleAir Data Logger(s)’s documentation!
+Welcome to PurpleAir Data Logger(s)’s documentation!
-
@@ -79,6 +79,7 @@
Welcome to PurpleAir Data Logger(s)’s documentation!PurpleAirCSVDataLogger module
Welcome to PurpleAir Data Logger(s)’s documentation!
- Indices and tables
+Indices and tables
purpleair_data_logger — PurpleAir Data Logger(s) V1.2.1 documentation +purpleair_data_logger — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + @@ -48,6 +48,7 @@- purpleair_data_logger
+purpleair_data_logger
+ +
+ + + PurpleAirDataLoggerHelpers
+ diff --git a/docs/html/search.html b/docs/html/search.html index a2ab6b9..33b67dd 100644 --- a/docs/html/search.html +++ b/docs/html/search.html @@ -3,7 +3,7 @@ - Search — PurpleAir Data Logger(s) V1.2.1 documentation +Search — PurpleAir Data Logger(s) V1.3.0a1 documentation @@ -11,11 +11,11 @@ - - - - - + + + + + diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js index 605ce20..6add198 100644 --- a/docs/html/searchindex.js +++ b/docs/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["PurpleAirCSVDataLogger", "PurpleAirCSVDataLoggerConstants", "PurpleAirDataLogger", "PurpleAirPSQLDataLogger", "PurpleAirPSQLQueryStatements", "PurpleAirSQLiteDataLogger", "PurpleAirSQLiteQueryStatements", "index", "modules"], "filenames": ["PurpleAirCSVDataLogger.rst", "PurpleAirCSVDataLoggerConstants.rst", "PurpleAirDataLogger.rst", "PurpleAirPSQLDataLogger.rst", "PurpleAirPSQLQueryStatements.rst", "PurpleAirSQLiteDataLogger.rst", "PurpleAirSQLiteQueryStatements.rst", "index.rst", "modules.rst"], "titles": ["PurpleAirCSVDataLogger module", "PurpleAirCSVDataLoggerConstants module", "PurpleAirDataLogger module", "PurpleAirPSQLDataLogger module", "PurpleAirPSQLQueryStatements module", "PurpleAirSQLiteDataLogger module", "PurpleAirSQLiteQueryStatements module", "Welcome to PurpleAir Data Logger(s)\u2019s documentation!", "purpleair_data_logger"], "terms": {"copyright": [0, 1, 2, 3, 4, 5, 6], "2023": [0, 2, 3, 5], "carlkidcrypto": [0, 1, 2, 3, 4, 5, 6], "all": [0, 1, 2, 3, 4, 5, 6], "right": [0, 1, 2, 3, 4, 5, 6], "reserv": [0, 1, 2, 3, 4, 5, 6], "A": [0, 1, 2, 3, 4, 5, 6], "python": [0, 2, 3, 5], "class": [0, 2, 3, 5], "design": [0, 3, 5], "us": [0, 2, 3, 4, 5, 6], "purpleairapi": [0, 3, 5], "request": [0, 2, 3, 5], "sensor": [0, 2, 3, 5], "": [0, 2, 3, 5], "data": [0, 2, 3, 4, 5], "insert": [0, 2, 3, 4, 5, 6], "csv": [0, 1], "file": [0, 1, 2, 4, 5, 6], "For": [0, 2, 3, 4, 5, 6], "best": [0, 3, 5], "practic": [0, 3, 5], "from": [0, 3, 4, 5], "purpleair": [0, 3, 4, 5, 6], "The": [0, 2, 3, 5], "individu": [0, 3, 5], "updat": [0, 3, 5], "less": [0, 3, 5], "than": [0, 3, 5], "everi": [0, 3, 5], "30": [0, 3, 5], "second": [0, 3, 5], "As": [0, 3, 5], "courtesi": [0, 3, 5], "we": [0, 2, 3, 4, 5, 6], "ask": [0, 3, 5], "you": [0, 3, 5], "limit": [0, 3, 5], "number": [0, 3, 4, 5, 6], "more": [0, 3, 5], "onc": [0, 3, 5], "1": [0, 1, 3, 4, 5], "10": [0, 1, 3, 5], "minut": [0, 3, 5], "assum": [0, 3, 5], "ar": [0, 3, 5], "onli": [0, 2, 3, 5], "api": [0, 2, 3, 4, 5], "obtain": [0, 3, 5], "If": [0, 2, 3, 5], "retriev": [0, 3, 5], "multipl": [0, 2, 3, 5], "pleas": [0, 3, 5], "send": [0, 2, 3, 5], "singl": [0, 2, 3, 5], "rather": [0, 3, 5], "success": [0, 3, 5], "purpleairapireadkei": [0, 2, 3, 5], "purpleairapiwritekei": [0, 2, 3, 5], "path_to_save_csv_files_in": 0, "base": [0, 2, 3, 5], "purpleairdatalogg": [0, 3, 5, 7, 8], "logger": [0, 2, 3, 5], "now": [0, 3, 5], "store_sensor_data": [0, 2, 3, 5, 8], "single_sensor_data_dict": [0, 2, 3, 5], "paramet": [0, 2, 3, 5], "dict": [0, 2, 3, 5], "dictionari": [0, 2, 3, 5], "contain": [0, 1, 2, 3, 4, 5, 6], "field": [0, 2, 3, 5], "doesn": [0, 2, 3, 5], "t": [0, 2, 3, 4, 5, 6], "support": [0, 2, 3, 5], "certain": [0, 2, 3, 5], "make": [0, 2, 3, 5], "sure": [0, 2, 3, 5], "i": [0, 2, 3, 4, 5, 6], "null": [0, 2, 3, 4, 5, 6], "part": [0, 2, 3, 5], "thi": [0, 2, 3, 4, 5, 6], "method": [0, 2, 3, 5], "doe": [0, 2, 3, 5], "type": [0, 2, 3, 5], "error": [0, 2, 3, 5], "check": [0, 2, 3, 5], "That": [0, 2, 3, 5], "upto": [0, 2, 3, 5], "caller": [0, 2, 3, 5], "2022": [1, 4, 6], "csvdatalogg": [1, 2], "constant": [1, 4, 6], "environmental_fields_file_nam": [1, 8], "environmental_field": [1, 4, 6], "standard": 1, "name": [1, 4, 6], "environmental_fields_head": [1, 8], "data_time_stamp": [1, 4, 6], "sensor_index": [1, 4, 6], "humid": [1, 4, 6], "humidity_a": [1, 4, 6], "humidity_b": [1, 4, 6], "temperatur": [1, 4, 6], "temperature_a": [1, 4, 6], "temperature_b": [1, 4, 6], "pressur": [1, 4, 6], "pressure_a": [1, 4, 6], "pressure_b": [1, 4, 6], "header": 1, "miscellaneous_fields_file_nam": [1, 8], "miscellaneous_field": [1, 4, 6], "miscellaneous_fields_head": [1, 8], "voc": [1, 4, 6], "voc_a": [1, 4, 6], "voc_b": [1, 4, 6], "ozone1": [1, 4, 6], "analog_input": [1, 4, 6], "particle_count_fields_file_nam": [1, 8], "particle_count_field": [1, 4, 6], "particle_count_fields_head": [1, 8], "0": [1, 4, 6], "3_um_count": 1, "3_um_count_a": 1, "3_um_count_b": 1, "5_um_count": 1, "5_um_count_a": 1, "5_um_count_b": 1, "0_um_count": 1, "0_um_count_a": 1, "0_um_count_b": 1, "2": 1, "5": [1, 4, 6], "pm10_0_fields_file_nam": [1, 8], "pm10": [1, 4, 6], "0_field": 1, "pm10_0_fields_head": [1, 8], "0_a": 1, "0_b": 1, "0_atm": 1, "0_atm_a": 1, "0_atm_b": 1, "0_cf_1": 1, "0_cf_1_a": 1, "0_cf_1_b": 1, "pm1_0_fields_file_nam": [1, 8], "pm1": [1, 4, 6], "pm1_0_fields_head": [1, 8], "pm2_5_fields_file_nam": [1, 8], "pm2": [1, 4, 6], "5_field": 1, "pm2_5_fields_head": [1, 8], "5_alt": 1, "5_alt_a": 1, "5_alt_b": 1, "5_a": 1, "5_b": 1, "5_atm": 1, "5_atm_a": 1, "5_atm_b": 1, "5_cf_1": 1, "5_cf_1_a": 1, "5_cf_1_b": 1, "pm2_5_pseudo_average_fields_file_nam": [1, 8], "5_pseudo_average_field": 1, "pm2_5_pseudo_average_fields_head": [1, 8], "5_10minut": 1, "5_10minute_a": 1, "5_10minute_b": 1, "5_30minut": 1, "5_30minute_a": 1, "5_30minute_b": 1, "5_60minut": 1, "5_60minute_a": 1, "5_60minute_b": 1, "5_6hour": 1, "5_6hour_a": 1, "5_6hour_b": 1, "5_24hour": 1, "5_24hour_a": 1, "5_24hour_b": 1, "5_1week": 1, "5_1week_a": 1, "5_1week_b": 1, "station_information_and_status_fields_file_nam": [1, 8], "station_information_and_status_field": [1, 4, 6], "station_information_and_status_fields_head": [1, 8], "icon": [1, 4, 6], "model": [1, 4, 6], "hardwar": [1, 4, 6], "location_typ": [1, 4, 6], "privat": [1, 4, 6], "latitud": [1, 4, 6], "longitud": [1, 4, 6], "altitud": [1, 4, 6], "position_r": [1, 4, 6], "led_bright": [1, 4, 6], "firmware_vers": [1, 4, 6], "firmware_upgrad": [1, 4, 6], "rssi": [1, 4, 6], "uptim": [1, 4, 6], "pa_lat": [1, 4, 6], "memori": [1, 4, 6], "last_seen": [1, 4, 6], "last_modifi": [1, 4, 6], "date_cr": [1, 4, 6], "channel_st": [1, 4, 6], "channel_flag": [1, 4, 6], "channel_flags_manu": [1, 4, 6], "channel_flags_auto": [1, 4, 6], "confid": [1, 4, 6], "confidence_manu": [1, 4, 6], "confidence_auto": [1, 4, 6], "thingspeak_fields_file_nam": [1, 8], "thingspeak_field": [1, 4, 6], "thingspeak_fields_head": [1, 8], "primary_id_a": [1, 4, 6], "primary_key_a": [1, 4, 6], "secondary_id_a": [1, 4, 6], "secondary_key_a": [1, 4, 6], "primary_id_b": [1, 4, 6], "primary_key_b": [1, 4, 6], "secondary_id_b": [1, 4, 6], "secondary_key_b": [1, 4, 6], "object": 2, "Will": 2, "defin": [2, 4, 6], "common": 2, "other": 2, "exampl": 2, "psqldatalogg": 2, "sqlitedatalogg": 2, "inheritor": 2, "need": 2, "own": 2, "properti": [2, 3], "send_request_every_x_second": [2, 8], "return": [2, 3], "current": 2, "valu": [2, 4, 6], "how": [2, 4], "often": 2, "purpl": 2, "air": 2, "paa": 2, "databas": [2, 3, 4, 5, 6], "validate_parameters_and_run": [2, 8], "paa_multiple_sensor_request_json_fil": 2, "none": 2, "paa_single_sensor_request_json_fil": 2, "paa_group_sensor_request_json_fil": 2, "choos": 2, "what": [2, 4, 6], "run": 2, "execut": 2, "config": 2, "being": 2, "shall": 2, "consid": 2, "main": 2, "entri": 2, "point": 2, "str": 2, "path": 2, "json": 2, "group": [2, 4], "except": 2, "purpleairdataloggererror": [2, 8], "message_str": 2, "custom": 2, "our": [2, 4, 6], "generate_common_arg_pars": [2, 8], "argparse_descript": 2, "function": 2, "gener": [2, 4, 6], "argument": 2, "descript": 2, "parser": 2, "an": 2, "instanc": 2, "argpars": 2, "ad": 2, "psql": [3, 4], "psql_db_conn": 3, "ingest": 3, "timescaledb": [3, 4], "postgresql": 3, "Then": 3, "grafana": 3, "visual": 3, "said": 3, "get_acceptable_table_names_string_list": [3, 8], "getter": 3, "simpli": 3, "content": 3, "acceptable_table_names_string_list": 3, "list": 3, "tabl": [3, 4, 6], "datalogg": 3, "know": 3, "about": [3, 4], "statement": [4, 6], "queri": [4, 6], "string": [4, 6], "simplic": [4, 6], "match": [4, 6], "document": [4, 6], "sai": [4, 6], "do": [4, 6], "same": [4, 6], "column": [4, 6], "create_environmental_fields_t": [4, 6, 8], "n": [4, 6], "creat": [4, 6], "IF": [4, 6], "NOT": [4, 6], "exist": [4, 6], "timestamptz": 4, "int": 4, "float": 4, "primari": [4, 6], "kei": [4, 6], "create_miscellaneous_field": [4, 6, 8], "create_particle_count_field": [4, 6, 8], "um_count_0_3": [4, 6], "um_count_a_0_3": [4, 6], "um_count_b_0_3": [4, 6], "um_count_0_5": [4, 6], "um_count_a_0_5": [4, 6], "um_count_b_0_5": [4, 6], "um_count_1_0": [4, 6], "um_count_a_1_0": [4, 6], "um_count_b_1_0": [4, 6], "um_count_2_5": [4, 6], "um_count_a_2_5": [4, 6], "um_count_b_2_5": [4, 6], "um_count_5_0": [4, 6], "um_count_a_5_0": [4, 6], "um_count_b_5_0": [4, 6], "um_count_10_0": [4, 6], "um_count_a_10_0": [4, 6], "um_count_b_10_0": [4, 6], "note": [4, 6], "can": [4, 6], "start": [4, 6], "so": [4, 6], "0_3_um_count": [4, 6], "becom": [4, 6], "create_pm10_0_field": [4, 6, 8], "pm10_0_field": [4, 6], "pm10_0": [4, 6], "pm10_0_a": [4, 6], "pm10_0_b": [4, 6], "pm10_0_atm": [4, 6], "pm10_0_atm_a": [4, 6], "pm10_0_atm_b": [4, 6], "pm10_0_cf_1": [4, 6], "pm10_0_cf_1_a": [4, 6], "pm10_0_cf_1_b": [4, 6], "sinc": [4, 6], "have": [4, 6], "decim": [4, 6], "variabl": [4, 6], "instead": [4, 6], "create_pm1_0_field": [4, 6, 8], "pm1_0_field": [4, 6], "pm1_0": [4, 6], "pm1_0_a": [4, 6], "pm1_0_b": [4, 6], "pm1_0_atm": [4, 6], "pm1_0_atm_a": [4, 6], "pm1_0_atm_b": [4, 6], "pm1_0_cf_1": [4, 6], "pm1_0_cf_1_a": [4, 6], "pm1_0_cf_1_b": [4, 6], "create_pm2_5_field": [4, 6, 8], "pm2_5_field": [4, 6], "pm2_5_alt": [4, 6], "pm2_5_alt_a": [4, 6], "pm2_5_alt_b": [4, 6], "pm2_5": [4, 6], "pm2_5_a": [4, 6], "pm2_5_b": [4, 6], "pm2_5_atm": [4, 6], "pm2_5_atm_a": [4, 6], "pm2_5_atm_b": [4, 6], "pm2_5_cf_1": [4, 6], "pm2_5_cf_1_a": [4, 6], "pm2_5_cf_1_b": [4, 6], "create_pm2_5_pseudo_average_field": [4, 6, 8], "pm2_5_pseudo_average_field": [4, 6], "pm2_5_10minut": [4, 6], "pm2_5_10minute_a": [4, 6], "pm2_5_10minute_b": [4, 6], "pm2_5_30minut": [4, 6], "pm2_5_30minute_a": [4, 6], "pm2_5_30minute_b": [4, 6], "pm2_5_60minut": [4, 6], "pm2_5_60minute_a": [4, 6], "pm2_5_60minute_b": [4, 6], "pm2_5_6hour": [4, 6], "pm2_5_6hour_a": [4, 6], "pm2_5_6hour_b": [4, 6], "pm2_5_24hour": [4, 6], "pm2_5_24hour_a": [4, 6], "pm2_5_24hour_b": [4, 6], "pm2_5_1week": [4, 6], "pm2_5_1week_a": [4, 6], "pm2_5_1week_b": [4, 6], "create_station_information_and_status_fields_t": [4, 6, 8], "text": [4, 6], "create_thingspeak_field": [4, 6, 8], "TO": [4, 6], "self": [4, 6], "mai": [4, 6], "end": [4, 6], "up": [4, 6], "get": [4, 6], "rid": [4, 6], "OF": [4, 6], "see": [4, 6], "NO": [4, 6], "FOR": [4, 6], "IT": [4, 6], "psql_create_continuous_aggregate_policy_on_sensor_index_and_name_1hour_aggreg": [4, 8], "select": 4, "add_continuous_aggregate_polici": 4, "sensor_index_and_name_1hour_aggreg": 4, "start_offset": 4, "interv": 4, "3": 4, "h": 4, "end_offset": 4, "schedule_interv": 4, "if_not_exist": 4, "true": 4, "add": 4, "continu": 4, "refresh": 4, "polici": 4, "materi": 4, "view": 4, "found": 4, "here": 4, "http": 4, "doc": 4, "timescal": 4, "com": 4, "latest": 4, "guid": 4, "aggreg": 4, "psql_create_data_retention_policy_on_sensor_index_and_name_1hour_aggreg": [4, 8], "add_retention_polici": 4, "8": 4, "hour": 4, "retent": 4, "psql_create_materialized_view_sensor_index_and_name_1hour_aggreg": [4, 8], "WITH": 4, "AS": 4, "time_bucket": 4, "1h": 4, "BY": 4, "create_materialized_view": 4, "psql_drop_all_t": [4, 8], "drop": [4, 6], "cascad": 4, "psql_get_list_of_active_compression_polici": [4, 8], "hypertable_nam": 4, "timescaledb_inform": 4, "job": 4, "where": 4, "proc_nam": 4, "policy_compress": 4, "activ": 4, "compress": 4, "psql_insert_statement_environmental_field": [4, 8], "INTO": [4, 6], "cast": 4, "psql_insert_statement_miscellaneous_field": [4, 8], "psql_insert_statement_particle_count_field": [4, 8], "psql_insert_statement_pm10_0_field": [4, 8], "psql_insert_statement_pm1_0_field": [4, 8], "psql_insert_statement_pm2_5_field": [4, 8], "psql_insert_statement_pm2_5_pseudo_average_field": [4, 8], "psql_insert_statement_station_information_and_status_field": [4, 8], "psql_insert_statement_thingspeak_field": [4, 8], "sqlite3": 5, "sqlite_data_base_nam": 5, "sqlite": 6, "integ": 6, "real": 6, "sqlite_drop_all_t": [6, 8], "sqlite_insert_statement_environmental_field": [6, 8], "sqlite_insert_statement_miscellaneous_field": [6, 8], "sqlite_insert_statement_particle_count_field": [6, 8], "sqlite_insert_statement_pm10_0_field": [6, 8], "sqlite_insert_statement_pm1_0_field": [6, 8], "sqlite_insert_statement_pm2_5_field": [6, 8], "sqlite_insert_statement_pm2_5_pseudo_average_field": [6, 8], "sqlite_insert_statement_station_information_and_status_field": [6, 8], "sqlite_insert_statement_thingspeak_field": [6, 8], "purpleair_data_logg": 7, "purpleaircsvdatalogg": [7, 8], "modul": [7, 8], "purpleaircsvdataloggerconst": [7, 8], "purpleairpsqldatalogg": [7, 8], "purpleairpsqlquerystat": [7, 8], "purpleairsqlitedatalogg": [7, 8], "purpleairsqlitequerystat": [7, 8], "index": 7, "search": 7, "page": 7}, "objects": {"": [[0, 0, 0, "-", "PurpleAirCSVDataLogger"], [1, 0, 0, "-", "PurpleAirCSVDataLoggerConstants"], [2, 0, 0, "-", "PurpleAirDataLogger"], [3, 0, 0, "-", "PurpleAirPSQLDataLogger"], [4, 0, 0, "-", "PurpleAirPSQLQueryStatements"], [5, 0, 0, "-", "PurpleAirSQLiteDataLogger"], [6, 0, 0, "-", "PurpleAirSQLiteQueryStatements"]], "PurpleAirCSVDataLogger": [[0, 1, 1, "", "PurpleAirCSVDataLogger"]], "PurpleAirCSVDataLogger.PurpleAirCSVDataLogger": [[0, 2, 1, "", "store_sensor_data"]], "PurpleAirCSVDataLoggerConstants": [[1, 3, 1, "", "ENVIRONMENTAL_FIELDS_FILE_NAME"], [1, 3, 1, "", "ENVIRONMENTAL_FIELDS_HEADER"], [1, 3, 1, "", "MISCELLANEOUS_FIELDS_FILE_NAME"], [1, 3, 1, "", "MISCELLANEOUS_FIELDS_HEADER"], [1, 3, 1, "", "PARTICLE_COUNT_FIELDS_FILE_NAME"], [1, 3, 1, "", "PARTICLE_COUNT_FIELDS_HEADER"], [1, 3, 1, "", "PM10_0_FIELDS_FILE_NAME"], [1, 3, 1, "", "PM10_0_FIELDS_HEADER"], [1, 3, 1, "", "PM1_0_FIELDS_FILE_NAME"], [1, 3, 1, "", "PM1_0_FIELDS_HEADER"], [1, 3, 1, "", "PM2_5_FIELDS_FILE_NAME"], [1, 3, 1, "", "PM2_5_FIELDS_HEADER"], [1, 3, 1, "", "PM2_5_PSEUDO_AVERAGE_FIELDS_FILE_NAME"], [1, 3, 1, "", "PM2_5_PSEUDO_AVERAGE_FIELDS_HEADER"], [1, 3, 1, "", "STATION_INFORMATION_AND_STATUS_FIELDS_FILE_NAME"], [1, 3, 1, "", "STATION_INFORMATION_AND_STATUS_FIELDS_HEADER"], [1, 3, 1, "", "THINGSPEAK_FIELDS_FILE_NAME"], [1, 3, 1, "", "THINGSPEAK_FIELDS_HEADER"]], "PurpleAirDataLogger": [[2, 1, 1, "", "PurpleAirDataLogger"], [2, 5, 1, "", "PurpleAirDataLoggerError"], [2, 6, 1, "", "generate_common_arg_parser"]], "PurpleAirDataLogger.PurpleAirDataLogger": [[2, 4, 1, "", "send_request_every_x_seconds"], [2, 2, 1, "", "store_sensor_data"], [2, 2, 1, "", "validate_parameters_and_run"]], "PurpleAirPSQLDataLogger": [[3, 1, 1, "", "PurpleAirPSQLDataLogger"]], "PurpleAirPSQLDataLogger.PurpleAirPSQLDataLogger": [[3, 4, 1, "", "get_acceptable_table_names_string_list"], [3, 2, 1, "", "store_sensor_data"]], "PurpleAirPSQLQueryStatements": [[4, 3, 1, "", "CREATE_ENVIRONMENTAL_FIELDS_TABLE"], [4, 3, 1, "", "CREATE_MISCELLANEOUS_FIELDS"], [4, 3, 1, "", "CREATE_PARTICLE_COUNT_FIELDS"], [4, 3, 1, "", "CREATE_PM10_0_FIELDS"], [4, 3, 1, "", "CREATE_PM1_0_FIELDS"], [4, 3, 1, "", "CREATE_PM2_5_FIELDS"], [4, 3, 1, "", "CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS"], [4, 3, 1, "", "CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE"], [4, 3, 1, "", "CREATE_THINGSPEAK_FIELDS"], [4, 3, 1, "", "PSQL_CREATE_CONTINUOUS_AGGREGATE_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"], [4, 3, 1, "", "PSQL_CREATE_DATA_RETENTION_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"], [4, 3, 1, "", "PSQL_CREATE_MATERIALIZED_VIEW_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"], [4, 3, 1, "", "PSQL_DROP_ALL_TABLES"], [4, 3, 1, "", "PSQL_GET_LIST_OF_ACTIVE_COMPRESSION_POLICIES"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_MISCELLANEOUS_FIELDS"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_PM10_0_FIELDS"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_PM1_0_FIELDS"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_PM2_5_FIELDS"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS"], [4, 3, 1, "", "PSQL_INSERT_STATEMENT_THINGSPEAK_FIELDS"]], "PurpleAirSQLiteDataLogger": [[5, 1, 1, "", "PurpleAirSQLiteDataLogger"]], "PurpleAirSQLiteDataLogger.PurpleAirSQLiteDataLogger": [[5, 2, 1, "", "store_sensor_data"]], "PurpleAirSQLiteQueryStatements": [[6, 3, 1, "", "CREATE_ENVIRONMENTAL_FIELDS_TABLE"], [6, 3, 1, "", "CREATE_MISCELLANEOUS_FIELDS"], [6, 3, 1, "", "CREATE_PARTICLE_COUNT_FIELDS"], [6, 3, 1, "", "CREATE_PM10_0_FIELDS"], [6, 3, 1, "", "CREATE_PM1_0_FIELDS"], [6, 3, 1, "", "CREATE_PM2_5_FIELDS"], [6, 3, 1, "", "CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS"], [6, 3, 1, "", "CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE"], [6, 3, 1, "", "CREATE_THINGSPEAK_FIELDS"], [6, 3, 1, "", "SQLITE_DROP_ALL_TABLES"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_MISCELLANEOUS_FIELDS"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_PM10_0_FIELDS"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_PM1_0_FIELDS"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_PM2_5_FIELDS"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS"], [6, 3, 1, "", "SQLITE_INSERT_STATEMENT_THINGSPEAK_FIELDS"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:data", "4": "py:property", "5": "py:exception", "6": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "data", "Python data"], "4": ["py", "property", "Python property"], "5": ["py", "exception", "Python exception"], "6": ["py", "function", "Python function"]}, "titleterms": {"purpleaircsvdatalogg": 0, "modul": [0, 1, 2, 3, 4, 5, 6], "purpleaircsvdataloggerconst": 1, "purpleairdatalogg": 2, "purpleairpsqldatalogg": 3, "purpleairpsqlquerystat": 4, "purpleairsqlitedatalogg": 5, "purpleairsqlitequerystat": 6, "welcom": 7, "purpleair": 7, "data": 7, "logger": 7, "": 7, "document": 7, "content": 7, "indic": 7, "tabl": 7, "purpleair_data_logg": 8}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 57}, "alltitles": {"PurpleAirCSVDataLogger module": [[0, "module-PurpleAirCSVDataLogger"]], "PurpleAirCSVDataLoggerConstants module": [[1, "module-PurpleAirCSVDataLoggerConstants"]], "PurpleAirDataLogger module": [[2, "module-PurpleAirDataLogger"]], "PurpleAirPSQLDataLogger module": [[3, "module-PurpleAirPSQLDataLogger"]], "PurpleAirPSQLQueryStatements module": [[4, "module-PurpleAirPSQLQueryStatements"]], "PurpleAirSQLiteDataLogger module": [[5, "module-PurpleAirSQLiteDataLogger"]], "PurpleAirSQLiteQueryStatements module": [[6, "module-PurpleAirSQLiteQueryStatements"]], "Welcome to PurpleAir Data Logger(s)\u2019s documentation!": [[7, "welcome-to-purpleair-data-logger-s-s-documentation"]], "Contents:": [[7, null]], "Indices and tables": [[7, "indices-and-tables"]], "purpleair_data_logger": [[8, "purpleair-data-logger"]]}, "indexentries": {"purpleaircsvdatalogger": [[0, "module-PurpleAirCSVDataLogger"]], "purpleaircsvdatalogger (class in purpleaircsvdatalogger)": [[0, "PurpleAirCSVDataLogger.PurpleAirCSVDataLogger"]], "module": [[0, "module-PurpleAirCSVDataLogger"], [1, "module-PurpleAirCSVDataLoggerConstants"], [2, "module-PurpleAirDataLogger"], [3, "module-PurpleAirPSQLDataLogger"], [4, "module-PurpleAirPSQLQueryStatements"], [5, "module-PurpleAirSQLiteDataLogger"], [6, "module-PurpleAirSQLiteQueryStatements"]], "store_sensor_data() (purpleaircsvdatalogger.purpleaircsvdatalogger method)": [[0, "PurpleAirCSVDataLogger.PurpleAirCSVDataLogger.store_sensor_data"]], "environmental_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.ENVIRONMENTAL_FIELDS_FILE_NAME"]], "environmental_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.ENVIRONMENTAL_FIELDS_HEADER"]], "miscellaneous_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.MISCELLANEOUS_FIELDS_FILE_NAME"]], "miscellaneous_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.MISCELLANEOUS_FIELDS_HEADER"]], "particle_count_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PARTICLE_COUNT_FIELDS_FILE_NAME"]], "particle_count_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PARTICLE_COUNT_FIELDS_HEADER"]], "pm10_0_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PM10_0_FIELDS_FILE_NAME"]], "pm10_0_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PM10_0_FIELDS_HEADER"]], "pm1_0_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PM1_0_FIELDS_FILE_NAME"]], "pm1_0_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PM1_0_FIELDS_HEADER"]], "pm2_5_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PM2_5_FIELDS_FILE_NAME"]], "pm2_5_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PM2_5_FIELDS_HEADER"]], "pm2_5_pseudo_average_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PM2_5_PSEUDO_AVERAGE_FIELDS_FILE_NAME"]], "pm2_5_pseudo_average_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.PM2_5_PSEUDO_AVERAGE_FIELDS_HEADER"]], "purpleaircsvdataloggerconstants": [[1, "module-PurpleAirCSVDataLoggerConstants"]], "station_information_and_status_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.STATION_INFORMATION_AND_STATUS_FIELDS_FILE_NAME"]], "station_information_and_status_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.STATION_INFORMATION_AND_STATUS_FIELDS_HEADER"]], "thingspeak_fields_file_name (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.THINGSPEAK_FIELDS_FILE_NAME"]], "thingspeak_fields_header (in module purpleaircsvdataloggerconstants)": [[1, "PurpleAirCSVDataLoggerConstants.THINGSPEAK_FIELDS_HEADER"]], "purpleairdatalogger": [[2, "module-PurpleAirDataLogger"]], "purpleairdatalogger (class in purpleairdatalogger)": [[2, "PurpleAirDataLogger.PurpleAirDataLogger"]], "purpleairdataloggererror": [[2, "PurpleAirDataLogger.PurpleAirDataLoggerError"]], "generate_common_arg_parser() (in module purpleairdatalogger)": [[2, "PurpleAirDataLogger.generate_common_arg_parser"]], "send_request_every_x_seconds (purpleairdatalogger.purpleairdatalogger property)": [[2, "PurpleAirDataLogger.PurpleAirDataLogger.send_request_every_x_seconds"]], "store_sensor_data() (purpleairdatalogger.purpleairdatalogger method)": [[2, "PurpleAirDataLogger.PurpleAirDataLogger.store_sensor_data"]], "validate_parameters_and_run() (purpleairdatalogger.purpleairdatalogger method)": [[2, "PurpleAirDataLogger.PurpleAirDataLogger.validate_parameters_and_run"]], "purpleairpsqldatalogger": [[3, "module-PurpleAirPSQLDataLogger"]], "purpleairpsqldatalogger (class in purpleairpsqldatalogger)": [[3, "PurpleAirPSQLDataLogger.PurpleAirPSQLDataLogger"]], "get_acceptable_table_names_string_list (purpleairpsqldatalogger.purpleairpsqldatalogger property)": [[3, "PurpleAirPSQLDataLogger.PurpleAirPSQLDataLogger.get_acceptable_table_names_string_list"]], "store_sensor_data() (purpleairpsqldatalogger.purpleairpsqldatalogger method)": [[3, "PurpleAirPSQLDataLogger.PurpleAirPSQLDataLogger.store_sensor_data"]], "create_environmental_fields_table (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_ENVIRONMENTAL_FIELDS_TABLE"]], "create_miscellaneous_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_MISCELLANEOUS_FIELDS"]], "create_particle_count_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_PARTICLE_COUNT_FIELDS"]], "create_pm10_0_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_PM10_0_FIELDS"]], "create_pm1_0_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_PM1_0_FIELDS"]], "create_pm2_5_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_PM2_5_FIELDS"]], "create_pm2_5_pseudo_average_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS"]], "create_station_information_and_status_fields_table (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE"]], "create_thingspeak_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.CREATE_THINGSPEAK_FIELDS"]], "psql_create_continuous_aggregate_policy_on_sensor_index_and_name_1hour_aggregate (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_CREATE_CONTINUOUS_AGGREGATE_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"]], "psql_create_data_retention_policy_on_sensor_index_and_name_1hour_aggregate (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_CREATE_DATA_RETENTION_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"]], "psql_create_materialized_view_sensor_index_and_name_1hour_aggregate (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_CREATE_MATERIALIZED_VIEW_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"]], "psql_drop_all_tables (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_DROP_ALL_TABLES"]], "psql_get_list_of_active_compression_policies (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_GET_LIST_OF_ACTIVE_COMPRESSION_POLICIES"]], "psql_insert_statement_environmental_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS"]], "psql_insert_statement_miscellaneous_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_MISCELLANEOUS_FIELDS"]], "psql_insert_statement_particle_count_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS"]], "psql_insert_statement_pm10_0_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM10_0_FIELDS"]], "psql_insert_statement_pm1_0_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM1_0_FIELDS"]], "psql_insert_statement_pm2_5_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM2_5_FIELDS"]], "psql_insert_statement_pm2_5_pseudo_average_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS"]], "psql_insert_statement_station_information_and_status_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS"]], "psql_insert_statement_thingspeak_fields (in module purpleairpsqlquerystatements)": [[4, "PurpleAirPSQLQueryStatements.PSQL_INSERT_STATEMENT_THINGSPEAK_FIELDS"]], "purpleairpsqlquerystatements": [[4, "module-PurpleAirPSQLQueryStatements"]], "purpleairsqlitedatalogger": [[5, "module-PurpleAirSQLiteDataLogger"]], "purpleairsqlitedatalogger (class in purpleairsqlitedatalogger)": [[5, "PurpleAirSQLiteDataLogger.PurpleAirSQLiteDataLogger"]], "store_sensor_data() (purpleairsqlitedatalogger.purpleairsqlitedatalogger method)": [[5, "PurpleAirSQLiteDataLogger.PurpleAirSQLiteDataLogger.store_sensor_data"]], "create_environmental_fields_table (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_ENVIRONMENTAL_FIELDS_TABLE"]], "create_miscellaneous_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_MISCELLANEOUS_FIELDS"]], "create_particle_count_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_PARTICLE_COUNT_FIELDS"]], "create_pm10_0_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_PM10_0_FIELDS"]], "create_pm1_0_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_PM1_0_FIELDS"]], "create_pm2_5_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_PM2_5_FIELDS"]], "create_pm2_5_pseudo_average_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS"]], "create_station_information_and_status_fields_table (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE"]], "create_thingspeak_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.CREATE_THINGSPEAK_FIELDS"]], "purpleairsqlitequerystatements": [[6, "module-PurpleAirSQLiteQueryStatements"]], "sqlite_drop_all_tables (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_DROP_ALL_TABLES"]], "sqlite_insert_statement_environmental_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS"]], "sqlite_insert_statement_miscellaneous_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_MISCELLANEOUS_FIELDS"]], "sqlite_insert_statement_particle_count_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS"]], "sqlite_insert_statement_pm10_0_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM10_0_FIELDS"]], "sqlite_insert_statement_pm1_0_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM1_0_FIELDS"]], "sqlite_insert_statement_pm2_5_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM2_5_FIELDS"]], "sqlite_insert_statement_pm2_5_pseudo_average_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS"]], "sqlite_insert_statement_station_information_and_status_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS"]], "sqlite_insert_statement_thingspeak_fields (in module purpleairsqlitequerystatements)": [[6, "PurpleAirSQLiteQueryStatements.SQLITE_INSERT_STATEMENT_THINGSPEAK_FIELDS"]]}}) \ No newline at end of file +Search.setIndex({"docnames": ["PurpleAirCSVDataLogger", "PurpleAirCSVDataLoggerConstants", "PurpleAirDataLogger", "PurpleAirDataLoggerHelpers", "PurpleAirPSQLDataLogger", "PurpleAirPSQLQueryStatements", "PurpleAirSQLiteDataLogger", "PurpleAirSQLiteQueryStatements", "index", "modules"], "filenames": ["PurpleAirCSVDataLogger.rst", "PurpleAirCSVDataLoggerConstants.rst", "PurpleAirDataLogger.rst", "PurpleAirDataLoggerHelpers.rst", "PurpleAirPSQLDataLogger.rst", "PurpleAirPSQLQueryStatements.rst", "PurpleAirSQLiteDataLogger.rst", "PurpleAirSQLiteQueryStatements.rst", "index.rst", "modules.rst"], "titles": ["PurpleAirCSVDataLogger module", "PurpleAirCSVDataLoggerConstants module", "PurpleAirDataLogger module", "PurpleAirDataLoggerHelpers module", "PurpleAirPSQLDataLogger module", "PurpleAirPSQLQueryStatements module", "PurpleAirSQLiteDataLogger module", "PurpleAirSQLiteQueryStatements module", "Welcome to PurpleAir Data Logger(s)\u2019s documentation!", "purpleair_data_logger"], "terms": {"copyright": [0, 1, 2, 3, 4, 5, 6, 7], "2023": [0, 2, 3, 4, 6], "carlkidcrypto": [0, 1, 2, 3, 4, 5, 6, 7], "all": [0, 1, 2, 3, 4, 5, 6, 7], "right": [0, 1, 2, 3, 4, 5, 6, 7], "reserv": [0, 1, 2, 3, 4, 5, 6, 7], "A": [0, 1, 2, 3, 4, 5, 6, 7], "python": [0, 2, 4, 6], "class": [0, 2, 4, 6], "design": [0, 4, 6], "us": [0, 2, 3, 4, 5, 6, 7], "purpleairapi": [0, 4, 6], "request": [0, 2, 3, 4, 6], "sensor": [0, 2, 3, 4, 6], "": [0, 2, 4, 6], "data": [0, 2, 3, 4, 5, 6], "insert": [0, 2, 3, 4, 5, 6, 7], "csv": [0, 1], "file": [0, 1, 2, 3, 5, 6, 7], "For": [0, 2, 4, 5, 6, 7], "best": [0, 4, 6], "practic": [0, 4, 6], "from": [0, 3, 4, 5, 6], "purpleair": [0, 4, 5, 6, 7], "The": [0, 2, 3, 4, 6], "individu": [0, 4, 6], "updat": [0, 4, 6], "less": [0, 4, 6], "than": [0, 4, 6], "everi": [0, 4, 6], "30": [0, 4, 6], "second": [0, 4, 6], "As": [0, 4, 6], "courtesi": [0, 4, 6], "we": [0, 2, 3, 4, 5, 6, 7], "ask": [0, 4, 6], "you": [0, 4, 6], "limit": [0, 4, 6], "number": [0, 4, 5, 6, 7], "more": [0, 4, 6], "onc": [0, 4, 6], "1": [0, 1, 4, 5, 6], "10": [0, 1, 4, 6], "minut": [0, 4, 6], "assum": [0, 4, 6], "ar": [0, 4, 6], "onli": [0, 2, 4, 6], "api": [0, 2, 4, 5, 6], "obtain": [0, 4, 6], "If": [0, 2, 4, 6], "retriev": [0, 4, 6], "multipl": [0, 2, 3, 4, 6], "pleas": [0, 4, 6], "send": [0, 2, 4, 6], "singl": [0, 2, 3, 4, 6], "rather": [0, 4, 6], "success": [0, 4, 6], "purpleairapireadkei": [0, 2, 4, 6], "none": [0, 2, 3], "purpleairapiwritekei": [0, 2, 4, 6], "purpleairapiipv4address": [0, 2], "path_to_save_csv_files_in": 0, "base": [0, 2, 4, 6], "purpleairdatalogg": [0, 3, 4, 6, 8, 9], "logger": [0, 2, 3, 4, 6], "now": [0, 4, 6], "store_sensor_data": [0, 2, 3, 4, 6, 9], "single_sensor_data_dict": [0, 2, 4, 6], "paramet": [0, 2, 3, 4, 6], "dict": [0, 2, 3, 4, 6], "dictionari": [0, 2, 3, 4, 6], "contain": [0, 1, 2, 3, 4, 5, 6, 7], "field": [0, 2, 3, 4, 6], "doesn": [0, 2, 4, 6], "t": [0, 2, 4, 5, 6, 7], "support": [0, 2, 4, 6], "certain": [0, 2, 4, 6], "make": [0, 2, 3, 4, 6], "sure": [0, 2, 3, 4, 6], "i": [0, 2, 4, 5, 6, 7], "null": [0, 2, 4, 5, 6, 7], "part": [0, 2, 4, 6], "thi": [0, 2, 3, 4, 5, 6, 7], "method": [0, 2, 3, 4, 6], "doe": [0, 2, 4, 6], "type": [0, 2, 3, 4, 6], "error": [0, 2, 4, 6], "check": [0, 2, 4, 6], "That": [0, 2, 4, 6], "upto": [0, 2, 4, 6], "caller": [0, 2, 4, 6], "2022": [1, 5, 7], "csvdatalogg": [1, 2], "constant": [1, 3, 5, 7], "environmental_fields_file_nam": [1, 9], "environmental_field": [1, 5, 7], "standard": 1, "name": [1, 5, 7], "environmental_fields_head": [1, 9], "data_time_stamp": [1, 5, 7], "sensor_index": [1, 5, 7], "humid": [1, 5, 7], "humidity_a": [1, 5, 7], "humidity_b": [1, 5, 7], "temperatur": [1, 5, 7], "temperature_a": [1, 5, 7], "temperature_b": [1, 5, 7], "pressur": [1, 5, 7], "pressure_a": [1, 5, 7], "pressure_b": [1, 5, 7], "header": 1, "miscellaneous_fields_file_nam": [1, 9], "miscellaneous_field": [1, 5, 7], "miscellaneous_fields_head": [1, 9], "voc": [1, 5, 7], "voc_a": [1, 5, 7], "voc_b": [1, 5, 7], "ozone1": [1, 5, 7], "analog_input": [1, 5, 7], "particle_count_fields_file_nam": [1, 9], "particle_count_field": [1, 5, 7], "particle_count_fields_head": [1, 9], "0": [1, 5, 7], "3_um_count": 1, "3_um_count_a": 1, "3_um_count_b": 1, "5_um_count": 1, "5_um_count_a": 1, "5_um_count_b": 1, "0_um_count": 1, "0_um_count_a": 1, "0_um_count_b": 1, "2": 1, "5": [1, 5, 7], "pm10_0_fields_file_nam": [1, 9], "pm10": [1, 5, 7], "0_field": 1, "pm10_0_fields_head": [1, 9], "0_a": 1, "0_b": 1, "0_atm": 1, "0_atm_a": 1, "0_atm_b": 1, "0_cf_1": 1, "0_cf_1_a": 1, "0_cf_1_b": 1, "pm1_0_fields_file_nam": [1, 9], "pm1": [1, 5, 7], "pm1_0_fields_head": [1, 9], "pm2_5_fields_file_nam": [1, 9], "pm2": [1, 5, 7], "5_field": 1, "pm2_5_fields_head": [1, 9], "5_alt": 1, "5_alt_a": 1, "5_alt_b": 1, "5_a": 1, "5_b": 1, "5_atm": 1, "5_atm_a": 1, "5_atm_b": 1, "5_cf_1": 1, "5_cf_1_a": 1, "5_cf_1_b": 1, "pm2_5_pseudo_average_fields_file_nam": [1, 9], "5_pseudo_average_field": 1, "pm2_5_pseudo_average_fields_head": [1, 9], "5_10minut": 1, "5_10minute_a": 1, "5_10minute_b": 1, "5_30minut": 1, "5_30minute_a": 1, "5_30minute_b": 1, "5_60minut": 1, "5_60minute_a": 1, "5_60minute_b": 1, "5_6hour": 1, "5_6hour_a": 1, "5_6hour_b": 1, "5_24hour": 1, "5_24hour_a": 1, "5_24hour_b": 1, "5_1week": 1, "5_1week_a": 1, "5_1week_b": 1, "station_information_and_status_fields_file_nam": [1, 9], "station_information_and_status_field": [1, 5, 7], "station_information_and_status_fields_head": [1, 9], "icon": [1, 5, 7], "model": [1, 5, 7], "hardwar": [1, 5, 7], "location_typ": [1, 5, 7], "privat": [1, 5, 7], "latitud": [1, 5, 7], "longitud": [1, 5, 7], "altitud": [1, 5, 7], "position_r": [1, 5, 7], "led_bright": [1, 5, 7], "firmware_vers": [1, 5, 7], "firmware_upgrad": [1, 5, 7], "rssi": [1, 5, 7], "uptim": [1, 5, 7], "pa_lat": [1, 5, 7], "memori": [1, 5, 7], "last_seen": [1, 5, 7], "last_modifi": [1, 5, 7], "date_cr": [1, 5, 7], "channel_st": [1, 5, 7], "channel_flag": [1, 5, 7], "channel_flags_manu": [1, 5, 7], "channel_flags_auto": [1, 5, 7], "confid": [1, 5, 7], "confidence_manu": [1, 5, 7], "confidence_auto": [1, 5, 7], "thingspeak_fields_file_nam": [1, 9], "thingspeak_field": [1, 5, 7], "thingspeak_fields_head": [1, 9], "primary_id_a": [1, 5, 7], "primary_key_a": [1, 5, 7], "secondary_id_a": [1, 5, 7], "secondary_key_a": [1, 5, 7], "primary_id_b": [1, 5, 7], "primary_key_b": [1, 5, 7], "secondary_id_b": [1, 5, 7], "secondary_key_b": [1, 5, 7], "object": [2, 3], "Will": 2, "defin": [2, 5, 7], "common": [2, 3], "other": 2, "exampl": 2, "psqldatalogg": 2, "sqlitedatalogg": 2, "inheritor": 2, "need": [2, 3], "own": 2, "properti": [2, 4], "send_request_every_x_second": [2, 9], "return": [2, 3, 4], "current": 2, "valu": [2, 3, 5, 7], "how": [2, 5], "often": 2, "purpl": 2, "air": 2, "paa": 2, "databas": [2, 4, 5, 6, 7], "validate_parameters_and_run": [2, 9], "paa_multiple_sensor_request_json_fil": 2, "paa_single_sensor_request_json_fil": 2, "paa_group_sensor_request_json_fil": 2, "paa_local_sensor_request_json_fil": 2, "choos": 2, "what": [2, 3, 5, 7], "run": [2, 3], "execut": 2, "config": [2, 3], "being": [2, 3], "shall": 2, "consid": 2, "main": 2, "entri": 2, "point": 2, "str": [2, 3], "path": 2, "json": [2, 3], "group": [2, 3, 5], "local": [2, 3], "except": 2, "purpleairdataloggererror": [2, 9], "message_str": 2, "custom": 2, "our": [2, 3, 5, 7], "helper": 3, "function": 3, "construct_store_sensor_data_typ": [3, 9], "raw_data": 3, "list": [3, 4], "build": 3, "expect": 3, "either": 3, "padl_obj": 3, "request_members_data": 3, "request_multiple_sensors_data": 3, "full": 3, "flatten_single_sensor_data": [3, 9], "flatten": 3, "raw": 3, "logic": 3, "downstream": 3, "easier": 3, "request_sensor_data": 3, "level": 3, "generate_common_arg_pars": [3, 9], "argparse_descript": 3, "gener": [3, 5, 7], "argument": 3, "descript": 3, "parser": 3, "an": 3, "instanc": 3, "argpars": 3, "ad": 3, "logic_for_storing_group_sensors_data": [3, 9], "group_id_to_us": 3, "json_config_fil": 3, "int": [3, 5], "loop": 3, "valid": 3, "id": 3, "start": [3, 5, 7], "out": 3, "get": [3, 5, 7], "fill": 3, "load": 3, "group_id": 3, "logic_for_storing_local_sensors_data": [3, 9], "logic_for_storing_multiple_sensors_data": [3, 9], "logic_for_storing_single_sensor_data": [3, 9], "validate_sensor_data_before_insert": [3, 9], "the_modified_sensor_data": 3, "befor": 3, "store": 3, "must": 3, "have": [3, 5, 7], "been": 3, "includ": 3, "psql": [3, 4, 5], "sqlite": [3, 7], "statement": [3, 5, 7], "regardless": 3, "layer": 3, "psql_db_conn": 4, "ingest": 4, "timescaledb": [4, 5], "postgresql": 4, "Then": 4, "grafana": 4, "visual": 4, "said": 4, "get_acceptable_table_names_string_list": [4, 9], "getter": 4, "simpli": 4, "content": 4, "acceptable_table_names_string_list": 4, "tabl": [4, 5, 7], "datalogg": 4, "know": 4, "about": [4, 5], "queri": [5, 7], "string": [5, 7], "simplic": [5, 7], "match": [5, 7], "document": [5, 7], "sai": [5, 7], "do": [5, 7], "same": [5, 7], "column": [5, 7], "create_environmental_fields_t": [5, 7, 9], "n": [5, 7], "creat": [5, 7], "IF": [5, 7], "NOT": [5, 7], "exist": [5, 7], "timestamptz": 5, "float": 5, "primari": [5, 7], "kei": [5, 7], "create_miscellaneous_field": [5, 7, 9], "create_particle_count_field": [5, 7, 9], "um_count_0_3": [5, 7], "um_count_a_0_3": [5, 7], "um_count_b_0_3": [5, 7], "um_count_0_5": [5, 7], "um_count_a_0_5": [5, 7], "um_count_b_0_5": [5, 7], "um_count_1_0": [5, 7], "um_count_a_1_0": [5, 7], "um_count_b_1_0": [5, 7], "um_count_2_5": [5, 7], "um_count_a_2_5": [5, 7], "um_count_b_2_5": [5, 7], "um_count_5_0": [5, 7], "um_count_a_5_0": [5, 7], "um_count_b_5_0": [5, 7], "um_count_10_0": [5, 7], "um_count_a_10_0": [5, 7], "um_count_b_10_0": [5, 7], "note": [5, 7], "can": [5, 7], "so": [5, 7], "0_3_um_count": [5, 7], "becom": [5, 7], "create_pm10_0_field": [5, 7, 9], "pm10_0_field": [5, 7], "pm10_0": [5, 7], "pm10_0_a": [5, 7], "pm10_0_b": [5, 7], "pm10_0_atm": [5, 7], "pm10_0_atm_a": [5, 7], "pm10_0_atm_b": [5, 7], "pm10_0_cf_1": [5, 7], "pm10_0_cf_1_a": [5, 7], "pm10_0_cf_1_b": [5, 7], "sinc": [5, 7], "decim": [5, 7], "variabl": [5, 7], "instead": [5, 7], "create_pm1_0_field": [5, 7, 9], "pm1_0_field": [5, 7], "pm1_0": [5, 7], "pm1_0_a": [5, 7], "pm1_0_b": [5, 7], "pm1_0_atm": [5, 7], "pm1_0_atm_a": [5, 7], "pm1_0_atm_b": [5, 7], "pm1_0_cf_1": [5, 7], "pm1_0_cf_1_a": [5, 7], "pm1_0_cf_1_b": [5, 7], "create_pm2_5_field": [5, 7, 9], "pm2_5_field": [5, 7], "pm2_5_alt": [5, 7], "pm2_5_alt_a": [5, 7], "pm2_5_alt_b": [5, 7], "pm2_5": [5, 7], "pm2_5_a": [5, 7], "pm2_5_b": [5, 7], "pm2_5_atm": [5, 7], "pm2_5_atm_a": [5, 7], "pm2_5_atm_b": [5, 7], "pm2_5_cf_1": [5, 7], "pm2_5_cf_1_a": [5, 7], "pm2_5_cf_1_b": [5, 7], "create_pm2_5_pseudo_average_field": [5, 7, 9], "pm2_5_pseudo_average_field": [5, 7], "pm2_5_10minut": [5, 7], "pm2_5_10minute_a": [5, 7], "pm2_5_10minute_b": [5, 7], "pm2_5_30minut": [5, 7], "pm2_5_30minute_a": [5, 7], "pm2_5_30minute_b": [5, 7], "pm2_5_60minut": [5, 7], "pm2_5_60minute_a": [5, 7], "pm2_5_60minute_b": [5, 7], "pm2_5_6hour": [5, 7], "pm2_5_6hour_a": [5, 7], "pm2_5_6hour_b": [5, 7], "pm2_5_24hour": [5, 7], "pm2_5_24hour_a": [5, 7], "pm2_5_24hour_b": [5, 7], "pm2_5_1week": [5, 7], "pm2_5_1week_a": [5, 7], "pm2_5_1week_b": [5, 7], "create_station_information_and_status_fields_t": [5, 7, 9], "text": [5, 7], "create_thingspeak_field": [5, 7, 9], "TO": [5, 7], "self": [5, 7], "mai": [5, 7], "end": [5, 7], "up": [5, 7], "rid": [5, 7], "OF": [5, 7], "see": [5, 7], "NO": [5, 7], "FOR": [5, 7], "IT": [5, 7], "psql_create_continuous_aggregate_policy_on_sensor_index_and_name_1hour_aggreg": [5, 9], "select": 5, "add_continuous_aggregate_polici": 5, "sensor_index_and_name_1hour_aggreg": 5, "start_offset": 5, "interv": 5, "3": 5, "h": 5, "end_offset": 5, "schedule_interv": 5, "if_not_exist": 5, "true": 5, "add": 5, "continu": 5, "refresh": 5, "polici": 5, "materi": 5, "view": 5, "found": 5, "here": 5, "http": 5, "doc": 5, "timescal": 5, "com": 5, "latest": 5, "guid": 5, "aggreg": 5, "psql_create_data_retention_policy_on_sensor_index_and_name_1hour_aggreg": [5, 9], "add_retention_polici": 5, "8": 5, "hour": 5, "retent": 5, "psql_create_materialized_view_sensor_index_and_name_1hour_aggreg": [5, 9], "WITH": 5, "AS": 5, "time_bucket": 5, "1h": 5, "BY": 5, "create_materialized_view": 5, "psql_drop_all_t": [5, 9], "drop": [5, 7], "cascad": 5, "psql_get_list_of_active_compression_polici": [5, 9], "hypertable_nam": 5, "timescaledb_inform": 5, "job": 5, "where": 5, "proc_nam": 5, "policy_compress": 5, "activ": 5, "compress": 5, "psql_insert_statement_environmental_field": [5, 9], "INTO": [5, 7], "cast": 5, "psql_insert_statement_miscellaneous_field": [5, 9], "psql_insert_statement_particle_count_field": [5, 9], "psql_insert_statement_pm10_0_field": [5, 9], "psql_insert_statement_pm1_0_field": [5, 9], "psql_insert_statement_pm2_5_field": [5, 9], "psql_insert_statement_pm2_5_pseudo_average_field": [5, 9], "psql_insert_statement_station_information_and_status_field": [5, 9], "psql_insert_statement_thingspeak_field": [5, 9], "sqlite3": 6, "sqlite_data_base_nam": 6, "integ": 7, "real": 7, "sqlite_drop_all_t": [7, 9], "sqlite_insert_statement_environmental_field": [7, 9], "sqlite_insert_statement_miscellaneous_field": [7, 9], "sqlite_insert_statement_particle_count_field": [7, 9], "sqlite_insert_statement_pm10_0_field": [7, 9], "sqlite_insert_statement_pm1_0_field": [7, 9], "sqlite_insert_statement_pm2_5_field": [7, 9], "sqlite_insert_statement_pm2_5_pseudo_average_field": [7, 9], "sqlite_insert_statement_station_information_and_status_field": [7, 9], "sqlite_insert_statement_thingspeak_field": [7, 9], "purpleair_data_logg": 8, "purpleaircsvdatalogg": [8, 9], "modul": [8, 9], "purpleaircsvdataloggerconst": [8, 9], "purpleairdataloggerhelp": [8, 9], "purpleairpsqldatalogg": [8, 9], "purpleairpsqlquerystat": [8, 9], "purpleairsqlitedatalogg": [8, 9], "purpleairsqlitequerystat": [8, 9], "index": 8, "search": 8, "page": 8}, "objects": {"": [[0, 0, 0, "-", "PurpleAirCSVDataLogger"], [1, 0, 0, "-", "PurpleAirCSVDataLoggerConstants"], [2, 0, 0, "-", "PurpleAirDataLogger"], [3, 0, 0, "-", "PurpleAirDataLoggerHelpers"], [4, 0, 0, "-", "PurpleAirPSQLDataLogger"], [5, 0, 0, "-", "PurpleAirPSQLQueryStatements"], [6, 0, 0, "-", "PurpleAirSQLiteDataLogger"], [7, 0, 0, "-", "PurpleAirSQLiteQueryStatements"]], "PurpleAirCSVDataLogger": [[0, 1, 1, "", "PurpleAirCSVDataLogger"]], "PurpleAirCSVDataLogger.PurpleAirCSVDataLogger": [[0, 2, 1, "", "store_sensor_data"]], "PurpleAirCSVDataLoggerConstants": [[1, 3, 1, "", "ENVIRONMENTAL_FIELDS_FILE_NAME"], [1, 3, 1, "", "ENVIRONMENTAL_FIELDS_HEADER"], [1, 3, 1, "", "MISCELLANEOUS_FIELDS_FILE_NAME"], [1, 3, 1, "", "MISCELLANEOUS_FIELDS_HEADER"], [1, 3, 1, "", "PARTICLE_COUNT_FIELDS_FILE_NAME"], [1, 3, 1, "", "PARTICLE_COUNT_FIELDS_HEADER"], [1, 3, 1, "", "PM10_0_FIELDS_FILE_NAME"], [1, 3, 1, "", "PM10_0_FIELDS_HEADER"], [1, 3, 1, "", "PM1_0_FIELDS_FILE_NAME"], [1, 3, 1, "", "PM1_0_FIELDS_HEADER"], [1, 3, 1, "", "PM2_5_FIELDS_FILE_NAME"], [1, 3, 1, "", "PM2_5_FIELDS_HEADER"], [1, 3, 1, "", "PM2_5_PSEUDO_AVERAGE_FIELDS_FILE_NAME"], [1, 3, 1, "", "PM2_5_PSEUDO_AVERAGE_FIELDS_HEADER"], [1, 3, 1, "", "STATION_INFORMATION_AND_STATUS_FIELDS_FILE_NAME"], [1, 3, 1, "", "STATION_INFORMATION_AND_STATUS_FIELDS_HEADER"], [1, 3, 1, "", "THINGSPEAK_FIELDS_FILE_NAME"], [1, 3, 1, "", "THINGSPEAK_FIELDS_HEADER"]], "PurpleAirDataLogger": [[2, 1, 1, "", "PurpleAirDataLogger"], [2, 5, 1, "", "PurpleAirDataLoggerError"]], "PurpleAirDataLogger.PurpleAirDataLogger": [[2, 4, 1, "", "send_request_every_x_seconds"], [2, 2, 1, "", "store_sensor_data"], [2, 2, 1, "", "validate_parameters_and_run"]], "PurpleAirDataLoggerHelpers": [[3, 6, 1, "", "construct_store_sensor_data_type"], [3, 6, 1, "", "flatten_single_sensor_data"], [3, 6, 1, "", "generate_common_arg_parser"], [3, 6, 1, "", "logic_for_storing_group_sensors_data"], [3, 6, 1, "", "logic_for_storing_local_sensors_data"], [3, 6, 1, "", "logic_for_storing_multiple_sensors_data"], [3, 6, 1, "", "logic_for_storing_single_sensor_data"], [3, 6, 1, "", "validate_sensor_data_before_insert"]], "PurpleAirPSQLDataLogger": [[4, 1, 1, "", "PurpleAirPSQLDataLogger"]], "PurpleAirPSQLDataLogger.PurpleAirPSQLDataLogger": [[4, 4, 1, "", "get_acceptable_table_names_string_list"], [4, 2, 1, "", "store_sensor_data"]], "PurpleAirPSQLQueryStatements": [[5, 3, 1, "", "CREATE_ENVIRONMENTAL_FIELDS_TABLE"], [5, 3, 1, "", "CREATE_MISCELLANEOUS_FIELDS"], [5, 3, 1, "", "CREATE_PARTICLE_COUNT_FIELDS"], [5, 3, 1, "", "CREATE_PM10_0_FIELDS"], [5, 3, 1, "", "CREATE_PM1_0_FIELDS"], [5, 3, 1, "", "CREATE_PM2_5_FIELDS"], [5, 3, 1, "", "CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS"], [5, 3, 1, "", "CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE"], [5, 3, 1, "", "CREATE_THINGSPEAK_FIELDS"], [5, 3, 1, "", "PSQL_CREATE_CONTINUOUS_AGGREGATE_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"], [5, 3, 1, "", "PSQL_CREATE_DATA_RETENTION_POLICY_ON_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"], [5, 3, 1, "", "PSQL_CREATE_MATERIALIZED_VIEW_SENSOR_INDEX_AND_NAME_1HOUR_AGGREGATE"], [5, 3, 1, "", "PSQL_DROP_ALL_TABLES"], [5, 3, 1, "", "PSQL_GET_LIST_OF_ACTIVE_COMPRESSION_POLICIES"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_MISCELLANEOUS_FIELDS"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_PM10_0_FIELDS"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_PM1_0_FIELDS"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_PM2_5_FIELDS"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS"], [5, 3, 1, "", "PSQL_INSERT_STATEMENT_THINGSPEAK_FIELDS"]], "PurpleAirSQLiteDataLogger": [[6, 1, 1, "", "PurpleAirSQLiteDataLogger"]], "PurpleAirSQLiteDataLogger.PurpleAirSQLiteDataLogger": [[6, 2, 1, "", "store_sensor_data"]], "PurpleAirSQLiteQueryStatements": [[7, 3, 1, "", "CREATE_ENVIRONMENTAL_FIELDS_TABLE"], [7, 3, 1, "", "CREATE_MISCELLANEOUS_FIELDS"], [7, 3, 1, "", "CREATE_PARTICLE_COUNT_FIELDS"], [7, 3, 1, "", "CREATE_PM10_0_FIELDS"], [7, 3, 1, "", "CREATE_PM1_0_FIELDS"], [7, 3, 1, "", "CREATE_PM2_5_FIELDS"], [7, 3, 1, "", "CREATE_PM2_5_PSEUDO_AVERAGE_FIELDS"], [7, 3, 1, "", "CREATE_STATION_INFORMATION_AND_STATUS_FIELDS_TABLE"], [7, 3, 1, "", "CREATE_THINGSPEAK_FIELDS"], [7, 3, 1, "", "SQLITE_DROP_ALL_TABLES"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_MISCELLANEOUS_FIELDS"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_PARTICLE_COUNT_FIELDS"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_PM10_0_FIELDS"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_PM1_0_FIELDS"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_PM2_5_FIELDS"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_PM2_5_PSEUDO_AVERAGE_FIELDS"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_STATION_INFORMATION_AND_STATUS_FIELDS"], [7, 3, 1, "", "SQLITE_INSERT_STATEMENT_THINGSPEAK_FIELDS"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:data", "4": "py:property", "5": "py:exception", "6": "py:function"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "data", "Python data"], "4": ["py", "property", "Python property"], "5": ["py", "exception", "Python exception"], "6": ["py", "function", "Python function"]}, "titleterms": {"purpleaircsvdatalogg": 0, "modul": [0, 1, 2, 3, 4, 5, 6, 7], "purpleaircsvdataloggerconst": 1, "purpleairdatalogg": 2, "purpleairdataloggerhelp": 3, "purpleairpsqldatalogg": 4, "purpleairpsqlquerystat": 5, "purpleairsqlitedatalogg": 6, "purpleairsqlitequerystat": 7, "welcom": 8, "purpleair": 8, "data": 8, "logger": 8, "": 8, "document": 8, "content": 8, "indic": 8, "tabl": 8, "purpleair_data_logg": 9}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"PurpleAirCSVDataLoggerConstants module": [[1, "module-PurpleAirCSVDataLoggerConstants"]], "PurpleAirPSQLQueryStatements module": [[5, "module-PurpleAirPSQLQueryStatements"]], "PurpleAirSQLiteQueryStatements module": [[7, "module-PurpleAirSQLiteQueryStatements"]], "Welcome to PurpleAir Data Logger(s)\u2019s documentation!": [[8, "welcome-to-purpleair-data-logger-s-s-documentation"]], "Contents:": [[8, null]], "Indices and tables": [[8, "indices-and-tables"]], "purpleair_data_logger": [[9, "purpleair-data-logger"]], "PurpleAirDataLoggerHelpers module": [[3, "module-PurpleAirDataLoggerHelpers"]], "PurpleAirCSVDataLogger module": [[0, "module-PurpleAirCSVDataLogger"]], "PurpleAirDataLogger module": [[2, "module-PurpleAirDataLogger"]], "PurpleAirPSQLDataLogger module": [[4, "module-PurpleAirPSQLDataLogger"]], "PurpleAirSQLiteDataLogger module": [[6, "module-PurpleAirSQLiteDataLogger"]]}, "indexentries": {"purpleaircsvdatalogger": [[0, "module-PurpleAirCSVDataLogger"]], "purpleaircsvdatalogger (class in purpleaircsvdatalogger)": [[0, "PurpleAirCSVDataLogger.PurpleAirCSVDataLogger"]], "module": [[0, "module-PurpleAirCSVDataLogger"], [2, "module-PurpleAirDataLogger"], [4, "module-PurpleAirPSQLDataLogger"], [6, "module-PurpleAirSQLiteDataLogger"]], "store_sensor_data() (purpleaircsvdatalogger.purpleaircsvdatalogger method)": [[0, "PurpleAirCSVDataLogger.PurpleAirCSVDataLogger.store_sensor_data"]], "purpleairdatalogger": [[2, "module-PurpleAirDataLogger"]], "purpleairdatalogger (class in purpleairdatalogger)": [[2, "PurpleAirDataLogger.PurpleAirDataLogger"]], "purpleairdataloggererror": [[2, "PurpleAirDataLogger.PurpleAirDataLoggerError"]], "send_request_every_x_seconds (purpleairdatalogger.purpleairdatalogger property)": [[2, "PurpleAirDataLogger.PurpleAirDataLogger.send_request_every_x_seconds"]], "store_sensor_data() (purpleairdatalogger.purpleairdatalogger method)": [[2, "PurpleAirDataLogger.PurpleAirDataLogger.store_sensor_data"]], "validate_parameters_and_run() (purpleairdatalogger.purpleairdatalogger method)": [[2, "PurpleAirDataLogger.PurpleAirDataLogger.validate_parameters_and_run"]], "purpleairpsqldatalogger": [[4, "module-PurpleAirPSQLDataLogger"]], "purpleairpsqldatalogger (class in purpleairpsqldatalogger)": [[4, "PurpleAirPSQLDataLogger.PurpleAirPSQLDataLogger"]], "get_acceptable_table_names_string_list (purpleairpsqldatalogger.purpleairpsqldatalogger property)": [[4, "PurpleAirPSQLDataLogger.PurpleAirPSQLDataLogger.get_acceptable_table_names_string_list"]], "store_sensor_data() (purpleairpsqldatalogger.purpleairpsqldatalogger method)": [[4, "PurpleAirPSQLDataLogger.PurpleAirPSQLDataLogger.store_sensor_data"]], "purpleairsqlitedatalogger": [[6, "module-PurpleAirSQLiteDataLogger"]], "purpleairsqlitedatalogger (class in purpleairsqlitedatalogger)": [[6, "PurpleAirSQLiteDataLogger.PurpleAirSQLiteDataLogger"]], "store_sensor_data() (purpleairsqlitedatalogger.purpleairsqlitedatalogger method)": [[6, "PurpleAirSQLiteDataLogger.PurpleAirSQLiteDataLogger.store_sensor_data"]]}}) \ No newline at end of file diff --git a/external_network_hardware_variant_json_samples/2.0+OPENLOG+31037 MB+DS3231+BME280+PMSX003-A.json b/external_network_hardware_variant_json_samples/2.0+OPENLOG+31037 MB+DS3231+BME280+PMSX003-A.json index 1c5daec..7bc26db 100644 --- a/external_network_hardware_variant_json_samples/2.0+OPENLOG+31037 MB+DS3231+BME280+PMSX003-A.json +++ b/external_network_hardware_variant_json_samples/2.0+OPENLOG+31037 MB+DS3231+BME280+PMSX003-A.json @@ -65,7 +65,7 @@ "secondary_id_b": 1566366, "secondary_key_b": "8BZC8PVWJMB5CG20", "stats": { - "pm2.5": None, + "pm2.5": null, "pm2.5_10minute": 11.3, "pm2.5_30minute": 11.6, "pm2.5_60minute": 12.1, @@ -85,7 +85,7 @@ "time_stamp": 1675087378 }, "stats_b": { - "pm2.5": None, + "pm2.5": null, "pm2.5_10minute": 11.3, "pm2.5_30minute": 11.6, "pm2.5_60minute": 12.1, diff --git a/purpleair_data_logger/PurpleAirCSVDataLogger.py b/purpleair_data_logger/PurpleAirCSVDataLogger.py index 88ff357..7e9ac6e 100644 --- a/purpleair_data_logger/PurpleAirCSVDataLogger.py +++ b/purpleair_data_logger/PurpleAirCSVDataLogger.py @@ -15,8 +15,13 @@ from purpleair_data_logger.PurpleAirDataLogger import ( PurpleAirDataLogger, +) + +from purpleair_data_logger.PurpleAirDataLoggerHelpers import ( generate_common_arg_parser, ) + + from purpleair_data_logger.PurpleAirCSVDataLoggerConstants import ( STATION_INFORMATION_AND_STATUS_FIELDS_FILE_NAME, STATION_INFORMATION_AND_STATUS_FIELDS_HEADER, diff --git a/purpleair_data_logger/PurpleAirDataLogger.py b/purpleair_data_logger/PurpleAirDataLogger.py index 2de1bcb..d76386a 100644 --- a/purpleair_data_logger/PurpleAirDataLogger.py +++ b/purpleair_data_logger/PurpleAirDataLogger.py @@ -5,87 +5,15 @@ A python base Data Logger class. """ -from purpleair_api.PurpleAirAPI import PurpleAirAPI, debug_log, PurpleAirAPIError -from purpleair_api.PurpleAirAPIConstants import ACCEPTED_FIELD_NAMES_DICT +from purpleair_api.PurpleAirAPI import PurpleAirAPI +from purpleair_data_logger.PurpleAirDataLoggerHelpers import ( + logic_for_storing_single_sensor_data, + logic_for_storing_multiple_sensors_data, + logic_for_storing_group_sensors_data, + logic_for_storing_local_sensors_data, +) from time import sleep import json -import argparse - - -def generate_common_arg_parser(argparse_description=""): - """ - A function to generate the common arguments that all data loggers need - - :param str argparse_description: A description for the argument parser that will be return - - :return An instance of argparse with the common arguments added. - """ - - parser = argparse.ArgumentParser(description=argparse_description) - - parser.add_argument( - "-paa_read_key", - required=False, - default=None, - dest="paa_read_key", - type=str, - help="""The PurpleAirAPI Read key""", - ) - - parser.add_argument( - "-paa_write_key", - required=False, - default=None, - dest="paa_write_key", - type=str, - help="""The PurpleAirAPI write key""", - ) - - parser.add_argument( - "-paa_single_sensor_request_json_file", - required=False, - default=None, - dest="paa_single_sensor_request_json_file", - type=str, - help="""The - path to a json file containing the parameters to send a single - sensor request.""", - ) - - parser.add_argument( - "-paa_multiple_sensor_request_json_file", - required=False, - default=None, - dest="paa_multiple_sensor_request_json_file", - type=str, - help="""The - path to a json file containing the parameters to send a multiple - sensor request.""", - ) - - parser.add_argument( - "-paa_group_sensor_request_json_file", - required=False, - default=None, - dest="paa_group_sensor_request_json_file", - type=str, - help="""The - path to a json file containing the parameters to send a group - sensor request.""", - ) - - parser.add_argument( - "-paa_local_sensor_request_json_file", - required=False, - default=None, - dest="paa_local_sensor_request_json_file", - type=str, - help="""The - path to a json file containing the parameters to send a local - sensor request.""", - ) - - return parser class PurpleAirDataLoggerError(Exception): @@ -166,35 +94,12 @@ def store_sensor_data(self, single_sensor_data_dict): "Must be implemented by class that is inheriting PurpleAirDataLogger!" ) - def _validate_sensor_data_before_insert(self, the_modified_sensor_data) -> dict: - """ - Before we store the data, we must make sure all fields have been included. - Our psql/sqlite store statements expect all fields regardless of what we request. - - :param dict the_modified_sensor_data: A single layer dictionary containing a single sensors data. - - return A dictionary with all the data fields filled out. - """ - - # Make a copy first - temp_the_modified_sensor_data = the_modified_sensor_data - for field in ACCEPTED_FIELD_NAMES_DICT.keys(): - if field not in temp_the_modified_sensor_data.keys(): - temp_the_modified_sensor_data[str(field)] = ACCEPTED_FIELD_NAMES_DICT[ - field - ] - - # Delete some stuff - del the_modified_sensor_data - - # Then return the modified copy - return temp_the_modified_sensor_data - - def _run_loop_for_storing_single_sensor_data(self, json_config_file) -> dict: + def _run_loop_for_storing_single_sensor_data(self, json_config_file) -> None: """ A method containing the run loop for inserting a single sensors' data into the data logger. :param dict json_config_file: A dictionary object of the json config file using json load. + :return None """ # Set the polling interval @@ -204,85 +109,15 @@ def _run_loop_for_storing_single_sensor_data(self, json_config_file) -> dict: print( "_run_loop_for_storing_single_sensor_data - Beep boop I am alive...\n\n" ) - # We will request data once every 65 seconds. - debug_log( - f"""Requesting new data from a sensor with index - {json_config_file['sensor_index']}...""" - ) - - sensor_data = None - sensor_data = self._purpleair_api_obj.request_sensor_data( - json_config_file["sensor_index"], - json_config_file["read_key"], - json_config_file["fields"], - ) - - # Let's make it easier on ourselves by making the sensor data one level deep. - # Instead of json["sensor"]["KEYS..."] and json["sensor"]["stats_a"]["KEYS..."] etc - # We turn it into just json["KEYS..."]. - the_modified_sensor_data = {} - the_modified_sensor_data["data_time_stamp"] = sensor_data["data_time_stamp"] - for key, val in sensor_data["sensor"].items(): - if key == "stats": - # For now name this one stats_pm2.5 until I understand the difference - # between sensor_data["stats"]["pm2.5"] and sensor_data["pm2.5"]. - # Update 07/25/2022: Heard back from PurpleAir. They are the same. - the_modified_sensor_data["stats_pm2.5"] = val["pm2.5"] - the_modified_sensor_data["pm2.5_10minute"] = val["pm2.5_10minute"] - the_modified_sensor_data["pm2.5_30minute"] = val["pm2.5_30minute"] - the_modified_sensor_data["pm2.5_60minute"] = val["pm2.5_60minute"] - the_modified_sensor_data["pm2.5_6hour"] = val["pm2.5_6hour"] - the_modified_sensor_data["pm2.5_24hour"] = val["pm2.5_24hour"] - the_modified_sensor_data["pm2.5_1week"] = val["pm2.5_1week"] - the_modified_sensor_data["pm2.5_time_stamp"] = val["time_stamp"] - - elif key in ["stats_a", "stats_b"]: - the_modified_sensor_data[f"pm2.5_{key[-1]}"] = val["pm2.5"] - the_modified_sensor_data[f"pm2.5_10minute_{key[-1]}"] = val[ - "pm2.5_10minute" - ] - the_modified_sensor_data[f"pm2.5_30minute_{key[-1]}"] = val[ - "pm2.5_30minute" - ] - the_modified_sensor_data[f"pm2.5_60minute_{key[-1]}"] = val[ - "pm2.5_60minute" - ] - the_modified_sensor_data[f"pm2.5_6hour_{key[-1]}"] = val[ - "pm2.5_6hour" - ] - the_modified_sensor_data[f"pm2.5_24hour_{key[-1]}"] = val[ - "pm2.5_24hour" - ] - the_modified_sensor_data[f"pm2.5_1week_{key[-1]}"] = val[ - "pm2.5_1week" - ] - the_modified_sensor_data[f"time_stamp_{key[-1]}"] = val[ - "time_stamp" - ] - - else: - the_modified_sensor_data[key] = val - - the_modified_sensor_data = self._validate_sensor_data_before_insert( - the_modified_sensor_data - ) - self.store_sensor_data(the_modified_sensor_data) - debug_log( - f"""Waiting {self.send_request_every_x_seconds} seconds before - requesting new data again...""" - ) - - # Delete some stuff - del sensor_data - del the_modified_sensor_data - + logic_for_storing_single_sensor_data(self, json_config_file) sleep(self.send_request_every_x_seconds) - def _run_loop_for_storing_multiple_sensors_data(self, json_config_file) -> dict: + def _run_loop_for_storing_multiple_sensors_data(self, json_config_file) -> None: """ A method containing the run loop for inserting a multiple sensors' data into the data logger. :param dict json_config_file: A dictionary object of the json config file using json load. + :return None """ # Set the polling interval @@ -292,59 +127,15 @@ def _run_loop_for_storing_multiple_sensors_data(self, json_config_file) -> dict: print( "_run_loop_for_storing_multiple_sensors_data - Beep boop I am alive...\n\n" ) - # We will request data once every 65 seconds. - debug_log( - f"""Requesting new data from multiple sensors with fields - {json_config_file["fields"]}...""" - ) - - sensors_data = None - sensors_data = self._purpleair_api_obj.request_multiple_sensors_data( - fields=json_config_file["fields"], - location_type=json_config_file["location_type"], - read_keys=json_config_file["read_keys"], - show_only=json_config_file["show_only"], - modified_since=json_config_file["modified_since"], - max_age=json_config_file["max_age"], - nwlng=json_config_file["nwlng"], - nwlat=json_config_file["nwlat"], - selng=json_config_file["selng"], - selat=json_config_file["selat"], - ) - - # The sensors data will look something like this: - # {'api_version': 'V1.0.11-0.0.34', 'time_stamp': 1659710288, 'data_time_stamp': 1659710232, - # 'max_age': 604800, 'firmware_default_version': '7.00', 'fields': ['sensor_index', 'name'], - # 'data': [[131075, 'Mariners Bluff'], [131079, 'BRSKBV-outside'], [131077, 'BEE Patio'], - # ... ]} - # It is important to know that the order of 'fields' provided as an argument to request_multiple_sensors_data() - # will determine the order of data items. In a nutshell it is a 1:1 mapping from fields to data. - # Now lets build and feed what the store_sensor_data() method expects. - store_sensor_data_type_list = [] - store_sensor_data_type_list = self._construct_store_sensor_data_type( - sensors_data - ) - - for store_sensor_data_type in store_sensor_data_type_list: - # Store the current data - self.store_sensor_data(store_sensor_data_type) - - debug_log( - f"""Waiting {self.send_request_every_x_seconds} seconds before - requesting new data again...""" - ) - - # Delete some stuff - del sensors_data - del store_sensor_data_type_list - + logic_for_storing_multiple_sensors_data(self, json_config_file) sleep(self.send_request_every_x_seconds) - def _run_loop_for_storing_group_sensors_data(self, json_config_file) -> dict: + def _run_loop_for_storing_group_sensors_data(self, json_config_file) -> None: """ A method containing the run loop for inserting a group sensors' data into the data logger. :param dict json_config_file: A dictionary object of the json config file using json load. + :return None """ # Set the polling interval @@ -355,113 +146,9 @@ def _run_loop_for_storing_group_sensors_data(self, json_config_file) -> dict: print( "_run_loop_for_storing_group_sensors_data - Beep boop I am alive...\n\n" ) - - if group_id_to_use is None: - # Get a current list of sensors that the API key provided owns - group_dict_list_data = ( - self._purpleair_api_obj.request_group_list_data()["groups"] - ) - - # Now make the sensor_group_name if it doesn't already exist. - does_sensor_group_name_exist = False - for item in group_dict_list_data: - name = item["name"] - id = item["id"] - # Find the first name that matches our sensor_group_name. No use to continue further - if bool(name == json_config_file["sensor_group_name"]): - does_sensor_group_name_exist = True - group_id_to_use = id - break - - if bool(does_sensor_group_name_exist == False): - print( - f"Your provided `sensor_group_name` - `{json_config_file['sensor_group_name']}` doesn't exist. A new one will be created..." - ) - retval = self._purpleair_api_obj.post_create_group_data( - json_config_file["sensor_group_name"] - ) - group_id_to_use = retval["group_id"] - print( - f"Your provided `sensor_group_name` - `{json_config_file['sensor_group_name']}` has been created! Its `group_id` number is `{group_id_to_use}`..." - ) - print( - f"Waiting {self.send_request_every_x_seconds} seconds for group to be created on server..." - ) - sleep(self.send_request_every_x_seconds) - - else: - print( - f"Your provided `sensor_group_name` - `{json_config_file['sensor_group_name']}` already exists. A new one will not be created..." - ) - - # By now we have a group_id_to_use. Let see if the user wants us to add members - if bool(json_config_file["add_sensors_to_group"]): - print( - f"Attempting to add the sensors in `sensor_index_list` to the `group_id` - {group_id_to_use}..." - ) - for sensor_index_val in json_config_file["sensor_index_list"]: - try: - retval = self._purpleair_api_obj.post_create_member( - group_id=group_id_to_use, sensor_index=sensor_index_val - ) - print( - f"`sensor_index` - {sensor_index_val} successfully added to group..." - ) - - except PurpleAirAPIError as err: - if ( - "409: DuplicateGroupEntryError - This sensor already exists in this group." - in err.message - ): - print( - f"`sensor_index` - {sensor_index_val} already exists in group..." - ) - - else: - raise err - - else: - print( - f"No sensors will be added to the `group_id` - {group_id_to_use}..." - ) - - assert group_id_to_use is not None - members_data = self._purpleair_api_obj.request_members_data( - group_id=group_id_to_use, - fields=json_config_file["fields"], - location_type=json_config_file["location_type"], - read_keys=json_config_file["read_keys"], - show_only=json_config_file["show_only"], - modified_since=json_config_file["modified_since"], - max_age=json_config_file["max_age"], - nwlng=json_config_file["nwlng"], - nwlat=json_config_file["nwlat"], - selng=json_config_file["selng"], - selat=json_config_file["selat"], + group_id_to_use = logic_for_storing_group_sensors_data( + self, group_id_to_use, json_config_file ) - - assert group_id_to_use == members_data["group_id"] - # The sensors data will look something like this: - # {'api_version': 'V1.0.11-0.0.42', 'time_stamp': 1676784867, 'data_time_stamp': 1676784847, 'group_id': 1654, - # 'max_age': 604800, 'firmware_default_version': '7.02', 'fields': ['sensor_index', 'name'], 'data': [[77, 'Sunnyside'], - # [81, 'Sherwood Hills 2']]} - # It is important to know that the order of 'fields' provided as an argument to request_multiple_sensors_data() - # will determine the order of data items. In a nutshell it is a 1:1 mapping from fields to data. - # Now lets build and feed what the store_sensor_data() method expects. - - store_sensor_data_type_list = self._construct_store_sensor_data_type( - members_data - ) - - for store_sensor_data_type in store_sensor_data_type_list: - # Store the current data - self.store_sensor_data(store_sensor_data_type) - - debug_log( - f"""Waiting {self.send_request_every_x_seconds} seconds before - requesting new data again...""" - ) - sleep(self.send_request_every_x_seconds) def _run_loop_for_storing_local_sensors_data(self, json_config_file) -> dict: @@ -475,351 +162,9 @@ def _run_loop_for_storing_local_sensors_data(self, json_config_file) -> dict: print( "_run_loop_for_storing_local_sensors_data - Beep boop I am alive...\n\n" ) - - # Ask for our local sensor data - local_sensor_dict = self._purpleair_api_obj.request_local_sensor_data() - - # The data that is returned via an internal network API is different than the data returned via an external network API. - # With that in mind let's try to map internal network API values to external network API values. That way we don't have to - # write more code in the PADL's. - for ip, sensor_dict in local_sensor_dict.items(): - the_modified_sensor_data = {} - - # This timestamp appears to be a unix epoch timestamp (seconds) type. - the_modified_sensor_data["data_time_stamp"] = sensor_dict[ - "response_date" - ] - - # Since we want this to work for all loggers let's make an assumption. The 'SensorId' is the 'name' since it is just a MAC address. - # The 'Id' is not the `sensor_index` it increments when the data changes. It is more of a `sample_id`. Let's just use the mac as a base - # 10 number. That should be unique. - the_modified_sensor_data["sensor_index"] = int( - str(sensor_dict["SensorId"]).replace(":", ""), 16 - ) - - ###### Station information and status fields: ###### - the_modified_sensor_data["name"] = sensor_dict["SensorId"] - # "icon": 0, - # "model": "", - the_modified_sensor_data["hardware"] = sensor_dict["hardwarediscovered"] - if sensor_dict["place"] == "inside": - the_modified_sensor_data["location_type"] = 1 - - elif sensor_dict["place"] == "outside": - the_modified_sensor_data["location_type"] = 0 - - # "private": 0, - the_modified_sensor_data["latitude"] = sensor_dict["lat"] - the_modified_sensor_data["longitude"] = sensor_dict["lon"] - # "altitude": 0.0, - # "position_rating": 0, - # "led_brightness": 0, - the_modified_sensor_data["firmware_version"] = sensor_dict["version"] - # "firmware_upgrade": "", - the_modified_sensor_data["rssi"] = sensor_dict["rssi"] - the_modified_sensor_data["uptime"] = sensor_dict["uptime"] - the_modified_sensor_data["pa_latency"] = sensor_dict["latency"] - # "last_seen": 0, - # "last_modified": 0, - # "date_created": 0, - # "channel_state": 0, - # "channel_flags": 0, - # "channel_flags_manual": 0, - # "channel_flags_auto": 0, - # "confidence": 0, - # "confidence_manual": 0, - # "confidence_auto": 0, - - ###### Environmental fields: ###### - if "current_humidity_680" not in sensor_dict.keys(): - the_modified_sensor_data["humidity"] = sensor_dict[ - "current_humidity" - ] - the_modified_sensor_data["humidity_a"] = sensor_dict[ - "current_humidity" - ] - - else: - the_modified_sensor_data["humidity_a"] = sensor_dict[ - "current_humidity" - ] - the_modified_sensor_data["humidity_b"] = sensor_dict[ - "current_humidity_680" - ] - the_modified_sensor_data["humidity"] = float( - ( - sensor_dict["current_humidity"] - + sensor_dict["current_humidity_680"] - ) - / 2 - ) - - if "current_temp_f_680" not in sensor_dict.keys(): - the_modified_sensor_data["temperature"] = sensor_dict[ - "current_temp_f" - ] - the_modified_sensor_data["temperature_a"] = sensor_dict[ - "current_temp_f" - ] - - else: - the_modified_sensor_data["temperature_a"] = sensor_dict[ - "current_temp_f" - ] - the_modified_sensor_data["temperature_b"] = sensor_dict[ - "current_temp_f_680" - ] - the_modified_sensor_data["temperature"] = float( - ( - sensor_dict["current_temp_f"] - + sensor_dict["current_temp_f_680"] - ) - / 2 - ) - - if "pressure_680" not in sensor_dict.keys(): - the_modified_sensor_data["pressure"] = sensor_dict["pressure"] - the_modified_sensor_data["pressure_a"] = sensor_dict["pressure"] - - else: - the_modified_sensor_data["pressure_a"] = sensor_dict["pressure"] - the_modified_sensor_data["pressure_b"] = sensor_dict["pressure_680"] - the_modified_sensor_data["pressure"] = float( - (sensor_dict["pressure"] + sensor_dict["pressure_680"]) / 2 - ) - - ###### Miscellaneous fields: ###### - # "voc": 0.0, - # "voc_a": 0.0, - # "voc_b": 0.0, - # "ozone1": 0.0, - # "analog_input": 0.0, - - ###### PM1.0 fields: ###### - if "p_1_0_um_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm1.0"] = sensor_dict["p_1_0_um"] - the_modified_sensor_data["pm1.0_a"] = sensor_dict["p_1_0_um"] - - else: - the_modified_sensor_data["pm1.0"] = float( - (sensor_dict["p_1_0_um"] + sensor_dict["p_1_0_um_b"]) / 2 - ) - the_modified_sensor_data["pm1.0_a"] = sensor_dict["p_1_0_um"] - the_modified_sensor_data["pm1.0_b"] = sensor_dict["p_1_0_um_b"] - - if "pm1_0_atm_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm1.0_atm"] = sensor_dict["pm1_0_atm"] - the_modified_sensor_data["pm1.0_atm_a"] = sensor_dict["pm1_0_atm"] - - else: - the_modified_sensor_data["pm1.0_atm"] = float( - (sensor_dict["pm1_0_atm"] + sensor_dict["pm1_0_atm_b"]) / 2 - ) - the_modified_sensor_data["pm1.0_atm_a"] = sensor_dict["pm1_0_atm"] - the_modified_sensor_data["pm1.0_atm_b"] = sensor_dict["pm1_0_atm_b"] - - if "pm1_0_cf_1_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm1.0_cf_1"] = sensor_dict["pm1_0_cf_1"] - the_modified_sensor_data["pm1.0_cf_1_a"] = sensor_dict["pm1_0_cf_1"] - - else: - the_modified_sensor_data["pm1.0_cf_1"] = float( - (sensor_dict["pm1_0_cf_1"] + sensor_dict["pm1_0_cf_1_b"]) / 2 - ) - the_modified_sensor_data["pm1.0_cf_1_a"] = sensor_dict["pm1_0_cf_1"] - the_modified_sensor_data["pm1.0_cf_1_b"] = sensor_dict[ - "pm1_0_cf_1_b" - ] - - ###### PM2.5 fields: ###### - # "pm2.5_alt": 0.0, - # "pm2.5_alt_a": 0.0, - # "pm2.5_alt_b": 0.0, - if "p_2_5_um_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm2.5"] = sensor_dict["p_2_5_um"] - the_modified_sensor_data["pm2.5_a"] = sensor_dict["p_2_5_um"] - - else: - the_modified_sensor_data["pm2.5"] = float( - (sensor_dict["p_2_5_um"] + sensor_dict["p_2_5_um_b"]) / 2 - ) - the_modified_sensor_data["pm2.5_a"] = sensor_dict["p_2_5_um"] - the_modified_sensor_data["pm2.5_b"] = sensor_dict["p_2_5_um_b"] - - if "pm2_5_atm_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm2.5_atm"] = sensor_dict["pm2_5_atm"] - the_modified_sensor_data["pm2.5_atm_a"] = sensor_dict["pm2_5_atm"] - - else: - the_modified_sensor_data["pm2.5_atm"] = float( - (sensor_dict["pm2_5_atm"] + sensor_dict["pm2_5_atm_b"]) / 2 - ) - the_modified_sensor_data["pm2.5_atm_a"] = sensor_dict["pm2_5_atm"] - the_modified_sensor_data["pm2.5_atm_b"] = sensor_dict["pm2_5_atm_b"] - - if "pm2_5_cf_1_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm2.5_cf_1"] = sensor_dict["pm2_5_cf_1"] - the_modified_sensor_data["pm2.5_cf_1_a"] = sensor_dict["pm2_5_cf_1"] - - else: - the_modified_sensor_data["pm2.5_cf_1"] = float( - (sensor_dict["pm2_5_cf_1"] + sensor_dict["pm2_5_cf_1_b"]) / 2 - ) - the_modified_sensor_data["pm2.5_cf_1_a"] = sensor_dict["pm2_5_cf_1"] - the_modified_sensor_data["pm2.5_cf_1_b"] = sensor_dict[ - "pm2_5_cf_1_b" - ] - - # # PM2.5 pseudo (simple running) average fields: - # # Note: These are inside the return json as json["sensor"]["stats"]. They are averages of the two sensors. - # # sensor 'a' and sensor 'b'. Each sensors data is inside json["sensor"]["stats_a"] and json["sensor"]["stats_b"] - # "pm2.5_10minute": 0.0, - # "pm2.5_10minute_a": 0.0, - # "pm2.5_10minute_b": 0.0, - # "pm2.5_30minute": 0.0, - # "pm2.5_30minute_a": 0.0, - # "pm2.5_30minute_b": 0.0, - # "pm2.5_60minute": 0.0, - # "pm2.5_60minute_a": 0.0, - # "pm2.5_60minute_b": 0.0, - # "pm2.5_6hour": 0.0, - # "pm2.5_6hour_a": 0.0, - # "pm2.5_6hour_b": 0.0, - # "pm2.5_24hour": 0.0, - # "pm2.5_24hour_a": 0.0, - # "pm2.5_24hour_b": 0.0, - # "pm2.5_1week": 0.0, - # "pm2.5_1week_a": 0.0, - # "pm2.5_1week_b": 0.0, - - ###### PM10.0 fields: ###### - if "p_10_0_um_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm10.0"] = sensor_dict["p_10_0_um"] - the_modified_sensor_data["pm10.0_a"] = sensor_dict["p_10_0_um"] - - else: - the_modified_sensor_data["pm10.0"] = float( - (sensor_dict["p_10_0_um"] + sensor_dict["p_10_0_um_b"]) / 2 - ) - the_modified_sensor_data["pm10.0_a"] = sensor_dict["p_10_0_um"] - the_modified_sensor_data["pm10.0_b"] = sensor_dict["p_10_0_um_b"] - - if "pm10_0_atm_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm10.0_atm"] = sensor_dict["pm10_0_atm"] - the_modified_sensor_data["pm10.0_atm_a"] = sensor_dict["pm10_0_atm"] - - else: - the_modified_sensor_data["pm10.0_atm"] = float( - (sensor_dict["pm10_0_atm"] + sensor_dict["pm10_0_atm_b"]) / 2 - ) - the_modified_sensor_data["pm10.0_atm_a"] = sensor_dict["pm10_0_atm"] - the_modified_sensor_data["pm10.0_atm_b"] = sensor_dict[ - "pm10_0_atm_b" - ] - - if "pm10_0_cf_1_b" not in sensor_dict.keys(): - the_modified_sensor_data["pm10.0_cf_1"] = sensor_dict["pm10_0_cf_1"] - the_modified_sensor_data["pm10.0_cf_1_a"] = sensor_dict[ - "pm10_0_cf_1" - ] - - else: - the_modified_sensor_data["pm10.0_cf_1"] = float( - (sensor_dict["pm10_0_cf_1"] + sensor_dict["pm10_0_cf_1_b"]) / 2 - ) - the_modified_sensor_data["pm10.0_cf_1_a"] = sensor_dict[ - "pm10_0_cf_1" - ] - the_modified_sensor_data["pm10.0_cf_1_b"] = sensor_dict[ - "pm10_0_cf_1_b" - ] - - ###### Particle count fields: ##### - # "0.3_um_count": 0.0, - # "0.3_um_count_a": 0.0, - # "0.3_um_count_b": 0.0, - # "0.5_um_count": 0.0, - # "0.5_um_count_a": 0.0, - # "0.5_um_count_b": 0.0, - # "1.0_um_count": 0.0, - # "1.0_um_count_a": 0.0, - # "1.0_um_count_b": 0.0, - # "2.5_um_count": 0.0, - # "2.5_um_count_a": 0.0, - # "2.5_um_count_b": 0.0, - # "5.0_um_count": 0.0, - # "5.0_um_count_a": 0.0, - # "5.0_um_count_b": 0.0, - # "10.0_um_count": 0.0, - # "10.0_um_count_a": 0.0, - # "10.0_um_count_b": 0.0, - - ###### ThingSpeak fields, used to retrieve data from api.thingspeak.com: ##### - # "primary_id_a": 0, - # "primary_key_a": "", - # "secondary_id_a": 0, - # "secondary_key_a": "", - # "primary_id_b": 0, - # "primary_key_b": "", - # "secondary_id_b": 0, - # "secondary_key_b": "", - - the_modified_sensor_data = self._validate_sensor_data_before_insert( - the_modified_sensor_data - ) - self.store_sensor_data(the_modified_sensor_data) - - debug_log( - f"""Waiting {json_config_file["poll_interval_seconds"]} seconds before - requesting new data again...""" - ) - - del local_sensor_dict + logic_for_storing_local_sensors_data(self, json_config_file) sleep(json_config_file["poll_interval_seconds"]) - def _construct_store_sensor_data_type(self, raw_data) -> list: - """ - A method to build the dict data type that the store_sensor_data method expects. - - :param dict raw_data: The return value from either self._purpleair_api_obj.request_members_data or - self._purpleair_api_obj.request_multiple_sensors_data. - - :return A list full of the dict data type that the store_sensor_data method expects. - """ - - # Extract the 'fields' and 'data' parts to make it easier on ourselves - extracted_fields = None - extracted_data = None - extracted_fields = raw_data["fields"] - extracted_data = raw_data["data"] - store_sensor_data_type_list = [] - - # Grab each list of data items from extracted data - for data_list in extracted_data: - # Start making our modified sensor data object that will be passed to the - # self.store_sensor_data() method - the_modified_sensor_data_dict = {} - the_modified_sensor_data_dict["data_time_stamp"] = raw_data[ - "data_time_stamp" - ] - for data_index, data_item in enumerate(data_list): - the_modified_sensor_data_dict[ - str(extracted_fields[data_index]) - ] = data_item - - the_modified_sensor_data_dict = self._validate_sensor_data_before_insert( - the_modified_sensor_data_dict - ) - - store_sensor_data_type_list.append(the_modified_sensor_data_dict) - - # Delete some stuff - del extracted_fields - del extracted_data - del raw_data - - return store_sensor_data_type_list - def validate_parameters_and_run( self, paa_multiple_sensor_request_json_file=None, diff --git a/purpleair_data_logger/PurpleAirDataLoggerHelpers.py b/purpleair_data_logger/PurpleAirDataLoggerHelpers.py new file mode 100644 index 0000000..75e7880 --- /dev/null +++ b/purpleair_data_logger/PurpleAirDataLoggerHelpers.py @@ -0,0 +1,691 @@ +#!/usr/bin/env python3 + +""" + Copyright 2023 carlkidcrypto, All rights reserved. + A helper file that contains constants and functions for PurpleAirDataLogger* files. +""" + +from purpleair_api.PurpleAirAPIConstants import ACCEPTED_FIELD_NAMES_DICT +from purpleair_api.PurpleAirAPI import debug_log, PurpleAirAPIError +import argparse +import time + + +def generate_common_arg_parser(argparse_description=""): + """ + A function to generate the common arguments that all data loggers need + + :param str argparse_description: A description for the argument parser that will be return + + :return An instance of argparse with the common arguments added. + """ + + parser = argparse.ArgumentParser(description=argparse_description) + + parser.add_argument( + "-paa_read_key", + required=False, + default=None, + dest="paa_read_key", + type=str, + help="""The PurpleAirAPI Read key""", + ) + + parser.add_argument( + "-paa_write_key", + required=False, + default=None, + dest="paa_write_key", + type=str, + help="""The PurpleAirAPI write key""", + ) + + parser.add_argument( + "-paa_single_sensor_request_json_file", + required=False, + default=None, + dest="paa_single_sensor_request_json_file", + type=str, + help="""The + path to a json file containing the parameters to send a single + sensor request.""", + ) + + parser.add_argument( + "-paa_multiple_sensor_request_json_file", + required=False, + default=None, + dest="paa_multiple_sensor_request_json_file", + type=str, + help="""The + path to a json file containing the parameters to send a multiple + sensor request.""", + ) + + parser.add_argument( + "-paa_group_sensor_request_json_file", + required=False, + default=None, + dest="paa_group_sensor_request_json_file", + type=str, + help="""The + path to a json file containing the parameters to send a group + sensor request.""", + ) + + parser.add_argument( + "-paa_local_sensor_request_json_file", + required=False, + default=None, + dest="paa_local_sensor_request_json_file", + type=str, + help="""The + path to a json file containing the parameters to send a local + sensor request.""", + ) + + return parser + + +def validate_sensor_data_before_insert(the_modified_sensor_data) -> dict: + """ + Before we store the data, we must make sure all fields have been included. + Our psql/sqlite store statements expect all fields regardless of what we request. + + :param dict the_modified_sensor_data: A single layer dictionary containing a single sensors data. + + return A dictionary with all the data fields filled out. + """ + + # Make a copy first + temp_the_modified_sensor_data = the_modified_sensor_data + for field in ACCEPTED_FIELD_NAMES_DICT.keys(): + if field not in temp_the_modified_sensor_data.keys(): + temp_the_modified_sensor_data[str(field)] = ACCEPTED_FIELD_NAMES_DICT[field] + + # Delete some stuff + del the_modified_sensor_data + + # Then return the modified copy + return temp_the_modified_sensor_data + + +def construct_store_sensor_data_type(raw_data) -> list: + """ + A method to build the dict data type that the store_sensor_data method expects. + + :param dict raw_data: The return value from either padl_obj.request_members_data or + padl_obj.request_multiple_sensors_data. + + :return A list full of the dict data type that the store_sensor_data method expects. + """ + + # Extract the 'fields' and 'data' parts to make it easier on ourselves + extracted_fields = None + extracted_data = None + extracted_fields = raw_data["fields"] + extracted_data = raw_data["data"] + store_sensor_data_type_list = [] + + # Grab each list of data items from extracted data + for data_list in extracted_data: + # Start making our modified sensor data object that will be passed to the + # padl_obj.store_sensor_data() method + the_modified_sensor_data_dict = {} + the_modified_sensor_data_dict["data_time_stamp"] = raw_data["data_time_stamp"] + for data_index, data_item in enumerate(data_list): + the_modified_sensor_data_dict[str(extracted_fields[data_index])] = data_item + + the_modified_sensor_data_dict = validate_sensor_data_before_insert( + the_modified_sensor_data_dict + ) + + store_sensor_data_type_list.append(the_modified_sensor_data_dict) + + # Delete some stuff + del extracted_fields + del extracted_data + del raw_data + + return store_sensor_data_type_list + + +def flatten_single_sensor_data(raw_data) -> dict: + """ + A method to flatten the raw data from a single sensor request. This makes our logic downstream easier. + + :param dict raw_data: The return value from padl_obj.request_sensor_data. + + :return A single level dict full request_sensor_data data. + """ + + # Let's make it easier on ourselves by making the sensor data one level deep. + # Instead of json["sensor"]["KEYS..."] and json["sensor"]["stats_a"]["KEYS..."] etc + # We turn it into just json["KEYS..."]. + the_modified_sensor_data = {} + the_modified_sensor_data["data_time_stamp"] = raw_data["data_time_stamp"] + for key, val in raw_data["sensor"].items(): + if key == "stats": + # For now name this one stats_pm2.5 until I understand the difference + # between sensor_data["stats"]["pm2.5"] and sensor_data["pm2.5"]. + # Update 07/25/2022: Heard back from PurpleAir. They are the same. + the_modified_sensor_data["stats_pm2.5"] = val["pm2.5"] + the_modified_sensor_data["pm2.5_10minute"] = val["pm2.5_10minute"] + the_modified_sensor_data["pm2.5_30minute"] = val["pm2.5_30minute"] + the_modified_sensor_data["pm2.5_60minute"] = val["pm2.5_60minute"] + the_modified_sensor_data["pm2.5_6hour"] = val["pm2.5_6hour"] + the_modified_sensor_data["pm2.5_24hour"] = val["pm2.5_24hour"] + the_modified_sensor_data["pm2.5_1week"] = val["pm2.5_1week"] + the_modified_sensor_data["pm2.5_time_stamp"] = val["time_stamp"] + + elif key in ["stats_a", "stats_b"]: + the_modified_sensor_data[f"pm2.5_{key[-1]}"] = val["pm2.5"] + the_modified_sensor_data[f"pm2.5_10minute_{key[-1]}"] = val[ + "pm2.5_10minute" + ] + the_modified_sensor_data[f"pm2.5_30minute_{key[-1]}"] = val[ + "pm2.5_30minute" + ] + the_modified_sensor_data[f"pm2.5_60minute_{key[-1]}"] = val[ + "pm2.5_60minute" + ] + the_modified_sensor_data[f"pm2.5_6hour_{key[-1]}"] = val["pm2.5_6hour"] + the_modified_sensor_data[f"pm2.5_24hour_{key[-1]}"] = val["pm2.5_24hour"] + the_modified_sensor_data[f"pm2.5_1week_{key[-1]}"] = val["pm2.5_1week"] + the_modified_sensor_data[f"time_stamp_{key[-1]}"] = val["time_stamp"] + + else: + the_modified_sensor_data[key] = val + + return the_modified_sensor_data + + +def logic_for_storing_single_sensor_data(padl_obj, json_config_file) -> None: + """ + A method containing the run loop for inserting a single sensors' data into the data logger. + + :param PurpleAirDataLogger padl_obj: A valid instance of PurpleAirDataLogger. + + :param dict json_config_file: A dictionary object of the json config file using json load. + + :return None + """ + + # We will request data once every 65 seconds. + debug_log( + f"""Requesting new data from a sensor with index + {json_config_file['sensor_index']}...""" + ) + + sensor_data = None + sensor_data = padl_obj._purpleair_api_obj.request_sensor_data( + json_config_file["sensor_index"], + json_config_file["read_key"], + json_config_file["fields"], + ) + + the_modified_sensor_data = flatten_single_sensor_data(sensor_data) + the_modified_sensor_data = validate_sensor_data_before_insert( + the_modified_sensor_data + ) + padl_obj.store_sensor_data(the_modified_sensor_data) + debug_log( + f"""Waiting {padl_obj.send_request_every_x_seconds} seconds before + requesting new data again...""" + ) + + # Delete some stuff + del sensor_data + del the_modified_sensor_data + + +def logic_for_storing_multiple_sensors_data(padl_obj, json_config_file) -> None: + """ + A method containing the run loop for inserting a multiple sensors' data into the data logger. + + :param PurpleAirDataLogger padl_obj: A valid instance of PurpleAirDataLogger. + + :param dict json_config_file: A dictionary object of the json config file using json load. + + :return None + """ + + # We will request data once every 65 seconds. + debug_log( + f"""Requesting new data from multiple sensors with fields + {json_config_file["fields"]}...""" + ) + + sensors_data = None + sensors_data = padl_obj._purpleair_api_obj.request_multiple_sensors_data( + fields=json_config_file["fields"], + location_type=json_config_file["location_type"], + read_keys=json_config_file["read_keys"], + show_only=json_config_file["show_only"], + modified_since=json_config_file["modified_since"], + max_age=json_config_file["max_age"], + nwlng=json_config_file["nwlng"], + nwlat=json_config_file["nwlat"], + selng=json_config_file["selng"], + selat=json_config_file["selat"], + ) + + # The sensors data will look something like this: + # {'api_version': 'V1.0.11-0.0.34', 'time_stamp': 1659710288, 'data_time_stamp': 1659710232, + # 'max_age': 604800, 'firmware_default_version': '7.00', 'fields': ['sensor_index', 'name'], + # 'data': [[131075, 'Mariners Bluff'], [131079, 'BRSKBV-outside'], [131077, 'BEE Patio'], + # ... ]} + # It is important to know that the order of 'fields' provided as an argument to request_multiple_sensors_data() + # will determine the order of data items. In a nutshell it is a 1:1 mapping from fields to data. + # Now lets build and feed what the store_sensor_data() method expects. + store_sensor_data_type_list = construct_store_sensor_data_type(sensors_data) + + for store_sensor_data_type in store_sensor_data_type_list: + # Store the current data + padl_obj.store_sensor_data(store_sensor_data_type) + + debug_log( + f"""Waiting {padl_obj.send_request_every_x_seconds} seconds before + requesting new data again...""" + ) + + # Delete some stuff + del sensors_data + del store_sensor_data_type_list + + +def logic_for_storing_group_sensors_data( + padl_obj, group_id_to_use, json_config_file +) -> int: + """ + A method containing the run loop for inserting a group sensors' data into the data logger. + + :param PurpleAirDataLogger padl_obj: A valid instance of PurpleAirDataLogger. + + :param str: The group id to be used. Starts out being `None` then gets filled out. + + :param dict json_config_file: A dictionary object of the json config file using json load. + + :return The group_id int + """ + + if group_id_to_use is None: + # Get a current list of sensors that the API key provided owns + group_dict_list_data = padl_obj._purpleair_api_obj.request_group_list_data()[ + "groups" + ] + + # Now make the sensor_group_name if it doesn't already exist. + does_sensor_group_name_exist = False + for item in group_dict_list_data: + name = item["name"] + id = item["id"] + # Find the first name that matches our sensor_group_name. No use to continue further + if bool(name == json_config_file["sensor_group_name"]): + does_sensor_group_name_exist = True + group_id_to_use = id + break + + if bool(does_sensor_group_name_exist == False): + print( + f"Your provided `sensor_group_name` - `{json_config_file['sensor_group_name']}` doesn't exist. A new one will be created..." + ) + retval = padl_obj._purpleair_api_obj.post_create_group_data( + json_config_file["sensor_group_name"] + ) + group_id_to_use = retval["group_id"] + print( + f"Your provided `sensor_group_name` - `{json_config_file['sensor_group_name']}` has been created! Its `group_id` number is `{group_id_to_use}`..." + ) + print( + f"Waiting {padl_obj.send_request_every_x_seconds} seconds for group to be created on server..." + ) + time.sleep(padl_obj.send_request_every_x_seconds) + + else: + print( + f"Your provided `sensor_group_name` - `{json_config_file['sensor_group_name']}` already exists. A new one will not be created..." + ) + + # By now we have a group_id_to_use. Let see if the user wants us to add members + if bool(json_config_file["add_sensors_to_group"]): + print( + f"Attempting to add the sensors in `sensor_index_list` to the `group_id` - {group_id_to_use}..." + ) + for sensor_index_val in json_config_file["sensor_index_list"]: + try: + retval = padl_obj._purpleair_api_obj.post_create_member( + group_id=group_id_to_use, sensor_index=sensor_index_val + ) + print( + f"`sensor_index` - {sensor_index_val} successfully added to group..." + ) + + except PurpleAirAPIError as err: + if ( + "409: DuplicateGroupEntryError - This sensor already exists in this group." + in err.message + ): + print( + f"`sensor_index` - {sensor_index_val} already exists in group..." + ) + + else: + raise err + + else: + print(f"No sensors will be added to the `group_id` - {group_id_to_use}...") + + assert group_id_to_use is not None + members_data = padl_obj._purpleair_api_obj.request_members_data( + group_id=group_id_to_use, + fields=json_config_file["fields"], + location_type=json_config_file["location_type"], + read_keys=json_config_file["read_keys"], + show_only=json_config_file["show_only"], + modified_since=json_config_file["modified_since"], + max_age=json_config_file["max_age"], + nwlng=json_config_file["nwlng"], + nwlat=json_config_file["nwlat"], + selng=json_config_file["selng"], + selat=json_config_file["selat"], + ) + + assert int(group_id_to_use) == int(members_data["group_id"]) + + # The sensors data will look something like this: + # {'api_version': 'V1.0.11-0.0.42', 'time_stamp': 1676784867, 'data_time_stamp': 1676784847, 'group_id': 1654, + # 'max_age': 604800, 'firmware_default_version': '7.02', 'fields': ['sensor_index', 'name'], 'data': [[77, 'Sunnyside'], + # [81, 'Sherwood Hills 2']]} + # It is important to know that the order of 'fields' provided as an argument to request_multiple_sensors_data() + # will determine the order of data items. In a nutshell it is a 1:1 mapping from fields to data. + # Now lets build and feed what the store_sensor_data() method expects. + store_sensor_data_type_list = construct_store_sensor_data_type(members_data) + + for store_sensor_data_type in store_sensor_data_type_list: + # Store the current data + padl_obj.store_sensor_data(store_sensor_data_type) + + debug_log( + f"""Waiting {padl_obj.send_request_every_x_seconds} seconds before + requesting new data again...""" + ) + + return members_data["group_id"] + + +def logic_for_storing_local_sensors_data(padl_obj, json_config_file) -> None: + """ + A method containing the run loop for inserting a local sensors' data into the data logger. + + :param PurpleAirDataLogger padl_obj: A valid instance of PurpleAirDataLogger. + + :param dict json_config_file: A dictionary object of the json config file using json load. + + :return None + """ + + # Ask for our local sensor data + local_sensor_dict = padl_obj._purpleair_api_obj.request_local_sensor_data() + + # The data that is returned via an internal network API is different than the data returned via an external network API. + # With that in mind let's try to map internal network API values to external network API values. That way we don't have to + # write more code in the PADL's. + for ip, sensor_dict in local_sensor_dict.items(): + the_modified_sensor_data = {} + + # This timestamp appears to be a unix epoch timestamp (seconds) type. + the_modified_sensor_data["data_time_stamp"] = sensor_dict["response_date"] + + # Since we want this to work for all loggers let's make an assumption. The 'SensorId' is the 'name' since it is just a MAC address. + # The 'Id' is not the `sensor_index` it increments when the data changes. It is more of a `sample_id`. Let's just use the mac as a base + # 10 number. That should be unique. + the_modified_sensor_data["sensor_index"] = int( + str(sensor_dict["SensorId"]).replace(":", ""), 16 + ) + + ###### Station information and status fields: ###### + the_modified_sensor_data["name"] = sensor_dict["SensorId"] + # "icon": 0, + # "model": "", + the_modified_sensor_data["hardware"] = sensor_dict["hardwarediscovered"] + if sensor_dict["place"] == "inside": + the_modified_sensor_data["location_type"] = 1 + + elif sensor_dict["place"] == "outside": + the_modified_sensor_data["location_type"] = 0 + + # "private": 0, + the_modified_sensor_data["latitude"] = sensor_dict["lat"] + the_modified_sensor_data["longitude"] = sensor_dict["lon"] + # "altitude": 0.0, + # "position_rating": 0, + # "led_brightness": 0, + the_modified_sensor_data["firmware_version"] = sensor_dict["version"] + # "firmware_upgrade": "", + the_modified_sensor_data["rssi"] = sensor_dict["rssi"] + the_modified_sensor_data["uptime"] = sensor_dict["uptime"] + the_modified_sensor_data["pa_latency"] = sensor_dict["latency"] + # "last_seen": 0, + # "last_modified": 0, + # "date_created": 0, + # "channel_state": 0, + # "channel_flags": 0, + # "channel_flags_manual": 0, + # "channel_flags_auto": 0, + # "confidence": 0, + # "confidence_manual": 0, + # "confidence_auto": 0, + + ###### Environmental fields: ###### + if "current_humidity_680" not in sensor_dict.keys(): + the_modified_sensor_data["humidity"] = sensor_dict["current_humidity"] + the_modified_sensor_data["humidity_a"] = sensor_dict["current_humidity"] + + else: + the_modified_sensor_data["humidity_a"] = sensor_dict["current_humidity"] + the_modified_sensor_data["humidity_b"] = sensor_dict["current_humidity_680"] + the_modified_sensor_data["humidity"] = float( + (sensor_dict["current_humidity"] + sensor_dict["current_humidity_680"]) + / 2 + ) + + if "current_temp_f_680" not in sensor_dict.keys(): + the_modified_sensor_data["temperature"] = sensor_dict["current_temp_f"] + the_modified_sensor_data["temperature_a"] = sensor_dict["current_temp_f"] + + else: + the_modified_sensor_data["temperature_a"] = sensor_dict["current_temp_f"] + the_modified_sensor_data["temperature_b"] = sensor_dict[ + "current_temp_f_680" + ] + the_modified_sensor_data["temperature"] = float( + (sensor_dict["current_temp_f"] + sensor_dict["current_temp_f_680"]) / 2 + ) + + if "pressure_680" not in sensor_dict.keys(): + the_modified_sensor_data["pressure"] = sensor_dict["pressure"] + the_modified_sensor_data["pressure_a"] = sensor_dict["pressure"] + + else: + the_modified_sensor_data["pressure_a"] = sensor_dict["pressure"] + the_modified_sensor_data["pressure_b"] = sensor_dict["pressure_680"] + the_modified_sensor_data["pressure"] = float( + (sensor_dict["pressure"] + sensor_dict["pressure_680"]) / 2 + ) + + ###### Miscellaneous fields: ###### + # "voc": 0.0, + # "voc_a": 0.0, + # "voc_b": 0.0, + # "ozone1": 0.0, + # "analog_input": 0.0, + + ###### PM1.0 fields: ###### + if "p_1_0_um_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm1.0"] = sensor_dict["p_1_0_um"] + the_modified_sensor_data["pm1.0_a"] = sensor_dict["p_1_0_um"] + + else: + the_modified_sensor_data["pm1.0"] = float( + (sensor_dict["p_1_0_um"] + sensor_dict["p_1_0_um_b"]) / 2 + ) + the_modified_sensor_data["pm1.0_a"] = sensor_dict["p_1_0_um"] + the_modified_sensor_data["pm1.0_b"] = sensor_dict["p_1_0_um_b"] + + if "pm1_0_atm_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm1.0_atm"] = sensor_dict["pm1_0_atm"] + the_modified_sensor_data["pm1.0_atm_a"] = sensor_dict["pm1_0_atm"] + + else: + the_modified_sensor_data["pm1.0_atm"] = float( + (sensor_dict["pm1_0_atm"] + sensor_dict["pm1_0_atm_b"]) / 2 + ) + the_modified_sensor_data["pm1.0_atm_a"] = sensor_dict["pm1_0_atm"] + the_modified_sensor_data["pm1.0_atm_b"] = sensor_dict["pm1_0_atm_b"] + + if "pm1_0_cf_1_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm1.0_cf_1"] = sensor_dict["pm1_0_cf_1"] + the_modified_sensor_data["pm1.0_cf_1_a"] = sensor_dict["pm1_0_cf_1"] + + else: + the_modified_sensor_data["pm1.0_cf_1"] = float( + (sensor_dict["pm1_0_cf_1"] + sensor_dict["pm1_0_cf_1_b"]) / 2 + ) + the_modified_sensor_data["pm1.0_cf_1_a"] = sensor_dict["pm1_0_cf_1"] + the_modified_sensor_data["pm1.0_cf_1_b"] = sensor_dict["pm1_0_cf_1_b"] + + ###### PM2.5 fields: ###### + # "pm2.5_alt": 0.0, + # "pm2.5_alt_a": 0.0, + # "pm2.5_alt_b": 0.0, + if "p_2_5_um_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm2.5"] = sensor_dict["p_2_5_um"] + the_modified_sensor_data["pm2.5_a"] = sensor_dict["p_2_5_um"] + + else: + the_modified_sensor_data["pm2.5"] = float( + (sensor_dict["p_2_5_um"] + sensor_dict["p_2_5_um_b"]) / 2 + ) + the_modified_sensor_data["pm2.5_a"] = sensor_dict["p_2_5_um"] + the_modified_sensor_data["pm2.5_b"] = sensor_dict["p_2_5_um_b"] + + if "pm2_5_atm_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm2.5_atm"] = sensor_dict["pm2_5_atm"] + the_modified_sensor_data["pm2.5_atm_a"] = sensor_dict["pm2_5_atm"] + + else: + the_modified_sensor_data["pm2.5_atm"] = float( + (sensor_dict["pm2_5_atm"] + sensor_dict["pm2_5_atm_b"]) / 2 + ) + the_modified_sensor_data["pm2.5_atm_a"] = sensor_dict["pm2_5_atm"] + the_modified_sensor_data["pm2.5_atm_b"] = sensor_dict["pm2_5_atm_b"] + + if "pm2_5_cf_1_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm2.5_cf_1"] = sensor_dict["pm2_5_cf_1"] + the_modified_sensor_data["pm2.5_cf_1_a"] = sensor_dict["pm2_5_cf_1"] + + else: + the_modified_sensor_data["pm2.5_cf_1"] = float( + (sensor_dict["pm2_5_cf_1"] + sensor_dict["pm2_5_cf_1_b"]) / 2 + ) + the_modified_sensor_data["pm2.5_cf_1_a"] = sensor_dict["pm2_5_cf_1"] + the_modified_sensor_data["pm2.5_cf_1_b"] = sensor_dict["pm2_5_cf_1_b"] + + # # PM2.5 pseudo (simple running) average fields: + # # Note: These are inside the return json as json["sensor"]["stats"]. They are averages of the two sensors. + # # sensor 'a' and sensor 'b'. Each sensors data is inside json["sensor"]["stats_a"] and json["sensor"]["stats_b"] + # "pm2.5_10minute": 0.0, + # "pm2.5_10minute_a": 0.0, + # "pm2.5_10minute_b": 0.0, + # "pm2.5_30minute": 0.0, + # "pm2.5_30minute_a": 0.0, + # "pm2.5_30minute_b": 0.0, + # "pm2.5_60minute": 0.0, + # "pm2.5_60minute_a": 0.0, + # "pm2.5_60minute_b": 0.0, + # "pm2.5_6hour": 0.0, + # "pm2.5_6hour_a": 0.0, + # "pm2.5_6hour_b": 0.0, + # "pm2.5_24hour": 0.0, + # "pm2.5_24hour_a": 0.0, + # "pm2.5_24hour_b": 0.0, + # "pm2.5_1week": 0.0, + # "pm2.5_1week_a": 0.0, + # "pm2.5_1week_b": 0.0, + + ###### PM10.0 fields: ###### + if "p_10_0_um_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm10.0"] = sensor_dict["p_10_0_um"] + the_modified_sensor_data["pm10.0_a"] = sensor_dict["p_10_0_um"] + + else: + the_modified_sensor_data["pm10.0"] = float( + (sensor_dict["p_10_0_um"] + sensor_dict["p_10_0_um_b"]) / 2 + ) + the_modified_sensor_data["pm10.0_a"] = sensor_dict["p_10_0_um"] + the_modified_sensor_data["pm10.0_b"] = sensor_dict["p_10_0_um_b"] + + if "pm10_0_atm_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm10.0_atm"] = sensor_dict["pm10_0_atm"] + the_modified_sensor_data["pm10.0_atm_a"] = sensor_dict["pm10_0_atm"] + + else: + the_modified_sensor_data["pm10.0_atm"] = float( + (sensor_dict["pm10_0_atm"] + sensor_dict["pm10_0_atm_b"]) / 2 + ) + the_modified_sensor_data["pm10.0_atm_a"] = sensor_dict["pm10_0_atm"] + the_modified_sensor_data["pm10.0_atm_b"] = sensor_dict["pm10_0_atm_b"] + + if "pm10_0_cf_1_b" not in sensor_dict.keys(): + the_modified_sensor_data["pm10.0_cf_1"] = sensor_dict["pm10_0_cf_1"] + the_modified_sensor_data["pm10.0_cf_1_a"] = sensor_dict["pm10_0_cf_1"] + + else: + the_modified_sensor_data["pm10.0_cf_1"] = float( + (sensor_dict["pm10_0_cf_1"] + sensor_dict["pm10_0_cf_1_b"]) / 2 + ) + the_modified_sensor_data["pm10.0_cf_1_a"] = sensor_dict["pm10_0_cf_1"] + the_modified_sensor_data["pm10.0_cf_1_b"] = sensor_dict["pm10_0_cf_1_b"] + + ###### Particle count fields: ##### + # "0.3_um_count": 0.0, + # "0.3_um_count_a": 0.0, + # "0.3_um_count_b": 0.0, + # "0.5_um_count": 0.0, + # "0.5_um_count_a": 0.0, + # "0.5_um_count_b": 0.0, + # "1.0_um_count": 0.0, + # "1.0_um_count_a": 0.0, + # "1.0_um_count_b": 0.0, + # "2.5_um_count": 0.0, + # "2.5_um_count_a": 0.0, + # "2.5_um_count_b": 0.0, + # "5.0_um_count": 0.0, + # "5.0_um_count_a": 0.0, + # "5.0_um_count_b": 0.0, + # "10.0_um_count": 0.0, + # "10.0_um_count_a": 0.0, + # "10.0_um_count_b": 0.0, + + ###### ThingSpeak fields, used to retrieve data from api.thingspeak.com: ##### + # "primary_id_a": 0, + # "primary_key_a": "", + # "secondary_id_a": 0, + # "secondary_key_a": "", + # "primary_id_b": 0, + # "primary_key_b": "", + # "secondary_id_b": 0, + # "secondary_key_b": "", + + the_modified_sensor_data = validate_sensor_data_before_insert( + the_modified_sensor_data + ) + padl_obj.store_sensor_data(the_modified_sensor_data) + + debug_log( + f"""Waiting {json_config_file["poll_interval_seconds"]} seconds before + requesting new data again...""" + ) + + del local_sensor_dict diff --git a/purpleair_data_logger/PurpleAirPSQLDataLogger.py b/purpleair_data_logger/PurpleAirPSQLDataLogger.py index 431fdf8..96d9533 100644 --- a/purpleair_data_logger/PurpleAirPSQLDataLogger.py +++ b/purpleair_data_logger/PurpleAirPSQLDataLogger.py @@ -15,8 +15,13 @@ from purpleair_data_logger.PurpleAirDataLogger import ( PurpleAirDataLogger, +) + +from purpleair_data_logger.PurpleAirDataLoggerHelpers import ( generate_common_arg_parser, ) + + from purpleair_data_logger.PurpleAirPSQLQueryStatements import ( PSQL_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS, PSQL_INSERT_STATEMENT_MISCELLANEOUS_FIELDS, diff --git a/purpleair_data_logger/PurpleAirSQLiteDataLogger.py b/purpleair_data_logger/PurpleAirSQLiteDataLogger.py index 712c36d..99b7dfc 100644 --- a/purpleair_data_logger/PurpleAirSQLiteDataLogger.py +++ b/purpleair_data_logger/PurpleAirSQLiteDataLogger.py @@ -15,8 +15,12 @@ from purpleair_data_logger.PurpleAirDataLogger import ( PurpleAirDataLogger, +) + +from purpleair_data_logger.PurpleAirDataLoggerHelpers import ( generate_common_arg_parser, ) + from purpleair_data_logger.PurpleAirSQLiteQueryStatements import ( SQLITE_INSERT_STATEMENT_ENVIRONMENTAL_FIELDS, SQLITE_INSERT_STATEMENT_MISCELLANEOUS_FIELDS, diff --git a/setup.py b/setup.py index a4ca968..e9e1ac9 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ def read_file(filename): setup( name="purpleair_data_logger", - version="1.3.0a0", + version="1.3.0a1", license="MIT", author="Carlos Santos", author_email="dose.lucky.sake@cloak.id", @@ -29,6 +29,6 @@ def read_file(filename): "purple air api", "PurpleAirSQLiteDataLogger", ], - install_requires=["pg8000==1.30.1", "requests", "purpleair_api==1.1.1"], + install_requires=["pg8000==1.30.1", "requests", "purpleair_api==1.1.2"], platforms=["Windows 32/64", "Linux 32/64", "MacOS 32/64"], ) diff --git a/sphinx_docs_build/requirements.txt b/sphinx_docs_build/requirements.txt new file mode 100644 index 0000000..62ebdeb --- /dev/null +++ b/sphinx_docs_build/requirements.txt @@ -0,0 +1,2 @@ +sphinx-rtd-theme==1.3.0 +purpleair-data-logger==1.3.0a1 \ No newline at end of file diff --git a/sphinx_docs_build/source/PurpleAirDataLoggerHelpers.rst b/sphinx_docs_build/source/PurpleAirDataLoggerHelpers.rst new file mode 100644 index 0000000..e99789c --- /dev/null +++ b/sphinx_docs_build/source/PurpleAirDataLoggerHelpers.rst @@ -0,0 +1,7 @@ +PurpleAirDataLoggerHelpers module +================================= + +.. automodule:: PurpleAirDataLoggerHelpers + :members: + :undoc-members: + :show-inheritance: diff --git a/sphinx_docs_build/source/conf.py b/sphinx_docs_build/source/conf.py index cb74df8..eeec063 100644 --- a/sphinx_docs_build/source/conf.py +++ b/sphinx_docs_build/source/conf.py @@ -23,7 +23,7 @@ author = "carlkidcrypto" # The full version, including alpha/beta/rc tags -release = "V1.3.0a0" +release = "V1.3.0a1" # -- General configuration --------------------------------------------------- diff --git a/sphinx_docs_build/source/modules.rst b/sphinx_docs_build/source/modules.rst index 5ed34f7..30813b6 100644 --- a/sphinx_docs_build/source/modules.rst +++ b/sphinx_docs_build/source/modules.rst @@ -7,6 +7,7 @@ purpleair_data_logger PurpleAirCSVDataLogger PurpleAirCSVDataLoggerConstants PurpleAirDataLogger + PurpleAirDataLoggerHelpers PurpleAirPSQLDataLogger PurpleAirPSQLQueryStatements PurpleAirSQLiteDataLogger diff --git a/tests/.coveragerc b/tests/.coveragerc new file mode 100644 index 0000000..14a9e46 --- /dev/null +++ b/tests/.coveragerc @@ -0,0 +1,5 @@ +[run] +omit = */tests/* + +[report] +omit = */tests/* \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..f9be714 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,27 @@ +# test + +## Setup + +1. Install python coverage + +```bash +python3 -m pip install coverage +``` + +2. Install mock requests + +```bash +python3 -m pip install requests-mock +``` + +3. Remove any currenlty installed versions of PurpleAirDataLogger + +```bash +python3 -m pip uninstall purpleair_data_logger +``` + +## Running tests + +```bash +python3 -m unittest && coverage html -d purpleair_data_logger_coverage_reports +``` diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 0000000..f3bdc80 --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,2197 @@ +#!/usr/bin/env python3 + +""" + Copyright 2023 carlkidcrypto, All rights reserved. + Helpers and constants for the test files. +""" + +DATA_IN_1 = { + "api_version": "V1.0.11-0.0.34", + "time_stamp": 1659710288, + "data_time_stamp": 1659710232, + "max_age": 604800, + "firmware_default_version": "7.00", + "fields": ["sensor_index", "name"], + "data": [[1, "TEST1"], [2, "TEST2"], [3, "TEST3"]], +} + +DATA_IN_2 = { + "api_version": "V1.0.11-0.0.42", + "time_stamp": 1676263167, + "data_time_stamp": 1676263122, + "sensor": { + "sensor_index": 131190, + "last_modified": 1646085907, + "date_created": 1633034575, + "last_seen": 1676263011, + }, +} + +LOCAL_API_DATA_IN_1 = { + "SensorId": "de:ad:be:ef:12:34", + "DateTime": "2023/09/22T16:04:07z", + "Geo": "PurpleAir-1234", + "Mem": 15920, + "memfrag": 20, + "memfb": 12792, + "memcs": 704, + "Id": 67045, + "lat": 123.123, + "lon": -123.123, + "Adc": 0.0, + "loggingrate": 15, + "place": "inside", + "version": "7.02", + "uptime": 4023791, + "rssi": -62, + "period": 120, + "httpsuccess": 68136, + "httpsends": 68163, + "hardwareversion": "2.0", + "hardwarediscovered": "2.0+BME280+PMSX003-A", + "current_temp_f": 79, + "current_humidity": 31, + "current_dewpoint_f": 46, + "pressure": 924.57, + "p25aqic": "rgb(0,228,0)", + "pm2.5_aqi": 0, + "pm1_0_cf_1": 0.0, + "p_0_3_um": 147.71, + "pm2_5_cf_1": 0.04, + "p_0_5_um": 39.61, + "pm10_0_cf_1": 0.44, + "p_1_0_um": 5.63, + "pm1_0_atm": 0.0, + "p_2_5_um": 0.81, + "pm2_5_atm": 0.04, + "p_5_0_um": 0.43, + "pm10_0_atm": 0.44, + "p_10_0_um": 0.43, + "pa_latency": 1112, + "response": 401, + "response_date": 1695398528, + "latency": 289, + "wlstate": "Connected", + "status_0": 2, + "status_1": 2, + "status_2": 2, + "status_3": 2, + "status_4": 0, + "status_5": 0, + "status_6": 3, + "status_7": 0, + "status_8": 0, + "status_9": 0, + "ssid": "WIFI-NAME-GOES-HERE", +} + +LOCAL_API_DATA_IN_2 = { + "SensorId": "de:ad:be:ef:12:34", + "DateTime": "2023/09/22T16:04:16z", + "Geo": "PurpleAir-1234", + "Mem": 10016, + "memfrag": 21, + "memfb": 7848, + "memcs": 352, + "Id": 3380, + "lat": 123.123, + "lon": -123.123, + "Adc": 0.06, + "loggingrate": 15, + "place": "outside", + "version": "7.04", + "uptime": 404647, + "rssi": -57, + "period": 120, + "httpsuccess": 6854, + "httpsends": 6855, + "hardwareversion": "3.0", + "hardwarediscovered": "3.0+OPENLOG+31037 MB+DS3231+BME280+BME68X+PMSX003-A+PMSX003-B", + "current_temp_f": 59, + "current_humidity": 58, + "current_dewpoint_f": 44, + "pressure": 926.79, + "current_temp_f_680": 57, + "current_humidity_680": 64, + "current_dewpoint_f_680": 45, + "pressure_680": 925.21, + "gas_680": 282.68, + "p25aqic_b": "rgb(151,244,0)", + "pm2.5_aqi_b": 42, + "pm1_0_cf_1_b": 6.9, + "p_0_3_um_b": 1261.03, + "pm2_5_cf_1_b": 10.07, + "p_0_5_um_b": 356.72, + "pm10_0_cf_1_b": 10.78, + "p_1_0_um_b": 71.19, + "pm1_0_atm_b": 6.9, + "p_2_5_um_b": 3.76, + "pm2_5_atm_b": 10.07, + "p_5_0_um_b": 0.38, + "pm10_0_atm_b": 10.78, + "p_10_0_um_b": 0.38, + "p25aqic": "rgb(199,249,0)", + "pm2.5_aqi": 46, + "pm1_0_cf_1": 6.55, + "p_0_3_um": 1310.69, + "pm2_5_cf_1": 10.98, + "p_0_5_um": 378.66, + "pm10_0_cf_1": 12.52, + "p_1_0_um": 80.79, + "pm1_0_atm": 6.55, + "p_2_5_um": 7.24, + "pm2_5_atm": 10.98, + "p_5_0_um": 1.62, + "pm10_0_atm": 12.52, + "p_10_0_um": 0.84, + "pa_latency": 777, + "response": 401, + "response_date": 1695398572, + "latency": 751, + "wlstate": "Connected", + "status_0": 2, + "status_1": 2, + "status_2": 2, + "status_3": 2, + "status_4": 2, + "status_5": 2, + "status_6": 3, + "status_7": 0, + "status_8": 2, + "status_9": 2, + "ssid": "WIFI-NAME-GOES-HERE", +} + +DATA_OUT_1 = [ + { + "data_time_stamp": 1659710232, + "sensor_index": 1, + "name": "TEST1", + "icon": 0, + "model": "", + "hardware": "", + "location_type": 0, + "private": 0, + "latitude": 0.0, + "longitude": 0.0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_version": "", + "firmware_upgrade": "", + "rssi": 0, + "uptime": 0, + "pa_latency": 0, + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity": 0, + "humidity_a": 0, + "humidity_b": 0, + "temperature": 0, + "temperature_a": 0, + "temperature_b": 0, + "pressure": 0.0, + "pressure_a": 0.0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", + }, + { + "data_time_stamp": 1659710232, + "sensor_index": 2, + "name": "TEST2", + "icon": 0, + "model": "", + "hardware": "", + "location_type": 0, + "private": 0, + "latitude": 0.0, + "longitude": 0.0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_version": "", + "firmware_upgrade": "", + "rssi": 0, + "uptime": 0, + "pa_latency": 0, + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity": 0, + "humidity_a": 0, + "humidity_b": 0, + "temperature": 0, + "temperature_a": 0, + "temperature_b": 0, + "pressure": 0.0, + "pressure_a": 0.0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", + }, + { + "data_time_stamp": 1659710232, + "sensor_index": 3, + "name": "TEST3", + "icon": 0, + "model": "", + "hardware": "", + "location_type": 0, + "private": 0, + "latitude": 0.0, + "longitude": 0.0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_version": "", + "firmware_upgrade": "", + "rssi": 0, + "uptime": 0, + "pa_latency": 0, + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity": 0, + "humidity_a": 0, + "humidity_b": 0, + "temperature": 0, + "temperature_a": 0, + "temperature_b": 0, + "pressure": 0.0, + "pressure_a": 0.0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", + }, +] + +DATA_OUT_2 = { + "data_time_stamp": 1676263122, + "sensor_index": 131190, + "last_modified": 1646085907, + "date_created": 1633034575, + "last_seen": 1676263011, + "name": "", + "icon": 0, + "model": "", + "hardware": "", + "location_type": 0, + "private": 0, + "latitude": 0.0, + "longitude": 0.0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_version": "", + "firmware_upgrade": "", + "rssi": 0, + "uptime": 0, + "pa_latency": 0, + "memory": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity": 0, + "humidity_a": 0, + "humidity_b": 0, + "temperature": 0, + "temperature_a": 0, + "temperature_b": 0, + "pressure": 0.0, + "pressure_a": 0.0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", +} + +DATA_OUT_3 = { + "data_time_stamp": 1659710232, + "sensor_index": 1, + "name": "TEST1", + "icon": 0, + "model": "", + "hardware": "", + "location_type": 0, + "private": 0, + "latitude": 0.0, + "longitude": 0.0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_version": "", + "firmware_upgrade": "", + "rssi": 0, + "uptime": 0, + "pa_latency": 0, + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity": 0, + "humidity_a": 0, + "humidity_b": 0, + "temperature": 0, + "temperature_a": 0, + "temperature_b": 0, + "pressure": 0.0, + "pressure_a": 0.0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", +} +DATA_OUT_4 = { + "data_time_stamp": 1659710232, + "sensor_index": 2, + "name": "TEST2", + "icon": 0, + "model": "", + "hardware": "", + "location_type": 0, + "private": 0, + "latitude": 0.0, + "longitude": 0.0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_version": "", + "firmware_upgrade": "", + "rssi": 0, + "uptime": 0, + "pa_latency": 0, + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity": 0, + "humidity_a": 0, + "humidity_b": 0, + "temperature": 0, + "temperature_a": 0, + "temperature_b": 0, + "pressure": 0.0, + "pressure_a": 0.0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", +} +DATA_OUT_5 = { + "data_time_stamp": 1659710232, + "sensor_index": 3, + "name": "TEST3", + "icon": 0, + "model": "", + "hardware": "", + "location_type": 0, + "private": 0, + "latitude": 0.0, + "longitude": 0.0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_version": "", + "firmware_upgrade": "", + "rssi": 0, + "uptime": 0, + "pa_latency": 0, + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity": 0, + "humidity_a": 0, + "humidity_b": 0, + "temperature": 0, + "temperature_a": 0, + "temperature_b": 0, + "pressure": 0.0, + "pressure_a": 0.0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", +} + +LOCAL_API_DATA_OUT_1 = { + "data_time_stamp": 1695398528, + "sensor_index": 244837814047284, + "name": "de:ad:be:ef:12:34", + "hardware": "2.0+BME280+PMSX003-A", + "location_type": 1, + "latitude": 123.123, + "longitude": -123.123, + "firmware_version": "7.02", + "rssi": -62, + "uptime": 4023791, + "pa_latency": 289, + "humidity": 31, + "humidity_a": 31, + "temperature": 79, + "temperature_a": 79, + "pressure": 924.57, + "pressure_a": 924.57, + "pm1.0": 5.63, + "pm1.0_a": 5.63, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm2.5": 0.81, + "pm2.5_a": 0.81, + "pm2.5_atm": 0.04, + "pm2.5_atm_a": 0.04, + "pm2.5_cf_1": 0.04, + "pm2.5_cf_1_a": 0.04, + "pm10.0": 0.43, + "pm10.0_a": 0.43, + "pm10.0_atm": 0.44, + "pm10.0_atm_a": 0.44, + "pm10.0_cf_1": 0.44, + "pm10.0_cf_1_a": 0.44, + "icon": 0, + "model": "", + "private": 0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_upgrade": "", + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity_b": 0, + "temperature_b": 0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", +} + +LOCAL_API_DATA_OUT_2 = { + "data_time_stamp": 1695398572, + "sensor_index": 244837814047284, + "name": "de:ad:be:ef:12:34", + "hardware": "3.0+OPENLOG+31037 MB+DS3231+BME280+BME68X+PMSX003-A+PMSX003-B", + "location_type": 0, + "latitude": 123.123, + "longitude": -123.123, + "firmware_version": "7.04", + "rssi": -57, + "uptime": 404647, + "pa_latency": 751, + "humidity_a": 58, + "humidity_b": 64, + "humidity": 61.0, + "temperature_a": 59, + "temperature_b": 57, + "temperature": 58.0, + "pressure_a": 926.79, + "pressure_b": 925.21, + "pressure": 926.0, + "pm1.0": 75.99000000000001, + "pm1.0_a": 80.79, + "pm1.0_b": 71.19, + "pm1.0_atm": 6.725, + "pm1.0_atm_a": 6.55, + "pm1.0_atm_b": 6.9, + "pm1.0_cf_1": 6.725, + "pm1.0_cf_1_a": 6.55, + "pm1.0_cf_1_b": 6.9, + "pm2.5": 5.5, + "pm2.5_a": 7.24, + "pm2.5_b": 3.76, + "pm2.5_atm": 10.525, + "pm2.5_atm_a": 10.98, + "pm2.5_atm_b": 10.07, + "pm2.5_cf_1": 10.525, + "pm2.5_cf_1_a": 10.98, + "pm2.5_cf_1_b": 10.07, + "pm10.0": 0.61, + "pm10.0_a": 0.84, + "pm10.0_b": 0.38, + "pm10.0_atm": 11.649999999999999, + "pm10.0_atm_a": 12.52, + "pm10.0_atm_b": 10.78, + "pm10.0_cf_1": 11.649999999999999, + "pm10.0_cf_1_a": 12.52, + "pm10.0_cf_1_b": 10.78, + "icon": 0, + "model": "", + "private": 0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_upgrade": "", + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", +} + +EXPECTED_VALUE_1 = { + "name": "", + "icon": 0, + "model": "", + "hardware": "", + "location_type": 0, + "private": 0, + "latitude": 0.0, + "longitude": 0.0, + "altitude": 0.0, + "position_rating": 0, + "led_brightness": 0, + "firmware_version": "", + "firmware_upgrade": "", + "rssi": 0, + "uptime": 0, + "pa_latency": 0, + "memory": 0, + "last_seen": 0, + "last_modified": 0, + "date_created": 0, + "channel_state": 0, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 0, + "confidence_manual": 0, + "confidence_auto": 0, + "humidity": 0, + "humidity_a": 0, + "humidity_b": 0, + "temperature": 0, + "temperature_a": 0, + "temperature_b": 0, + "pressure": 0.0, + "pressure_a": 0.0, + "pressure_b": 0.0, + "voc": 0.0, + "voc_a": 0.0, + "voc_b": 0.0, + "ozone1": 0.0, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm2.5_alt": 0.0, + "pm2.5_alt_a": 0.0, + "pm2.5_alt_b": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_b": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_atm_b": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm2.5_cf_1_b": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_10minute_a": 0.0, + "pm2.5_10minute_b": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_30minute_b": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_60minute_b": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_6hour_b": 0.0, + "pm2.5_24hour": 0.0, + "pm2.5_24hour_a": 0.0, + "pm2.5_24hour_b": 0.0, + "pm2.5_1week": 0.0, + "pm2.5_1week_a": 0.0, + "pm2.5_1week_b": 0.0, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "pm10.0_b": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_atm_b": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "pm10.0_cf_1_b": 0.0, + "0.3_um_count": 0.0, + "0.3_um_count_a": 0.0, + "0.3_um_count_b": 0.0, + "0.5_um_count": 0.0, + "0.5_um_count_a": 0.0, + "0.5_um_count_b": 0.0, + "1.0_um_count": 0.0, + "1.0_um_count_a": 0.0, + "1.0_um_count_b": 0.0, + "2.5_um_count": 0.0, + "2.5_um_count_a": 0.0, + "2.5_um_count_b": 0.0, + "5.0_um_count": 0.0, + "5.0_um_count_a": 0.0, + "5.0_um_count_b": 0.0, + "10.0_um_count": 0.0, + "10.0_um_count_a": 0.0, + "10.0_um_count_b": 0.0, + "primary_id_a": 0, + "primary_key_a": "", + "secondary_id_a": 0, + "secondary_key_a": "", + "primary_id_b": 0, + "primary_key_b": "", + "secondary_id_b": 0, + "secondary_key_b": "", +} + +EXPECTED_FILE_CONTENTS_1 = { + "data_time_stamp": 1663733772, + "sensor_index": 53, + "last_modified": 1520025982, + "date_created": 1454548891, + "last_seen": 1663733769, + "private": 0, + "is_owner": 0, + "name": "Lakeshore", + "icon": 0, + "location_type": 0, + "model": "UNKNOWN", + "hardware": "1.0+1M+PMSX003-O", + "led_brightness": 25, + "firmware_version": "6.06a", + "rssi": -33, + "uptime": 7567, + "pa_latency": 336, + "memory": 15640, + "position_rating": 5, + "latitude": 40.246742, + "longitude": -111.7048, + "channel_state": 1, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 30, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm2.5": 0.0, + "pm2.5_a": 0.0, + "pm2.5_alt": 1.4, + "pm2.5_alt_a": 1.4, + "pm10.0": 0.0, + "pm10.0_a": 0.0, + "scattering_coefficient": 14.9, + "scattering_coefficient_a": 14.9, + "deciviews": 10.2, + "deciviews_a": 10.2, + "visual_range": 140.1, + "visual_range_a": 140.1, + "0.3_um_count": 991, + "0.3_um_count_a": 991, + "0.5_um_count": 64, + "0.5_um_count_a": 64, + "1.0_um_count": 3, + "1.0_um_count_a": 3, + "2.5_um_count": 0, + "2.5_um_count_a": 0, + "5.0_um_count": 0, + "5.0_um_count_a": 0, + "10.0_um_count": 0, + "10.0_um_count_a": 0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm2.5_atm": 0.0, + "pm2.5_atm_a": 0.0, + "pm2.5_cf_1": 0.0, + "pm2.5_cf_1_a": 0.0, + "pm10.0_atm": 0.0, + "pm10.0_atm_a": 0.0, + "pm10.0_cf_1": 0.0, + "pm10.0_cf_1_a": 0.0, + "primary_id_a": 84144, + "primary_key_a": "DYBYPI2LSK4QGFNM", + "primary_id_b": 725391, + "primary_key_b": "DGNHAFD7NZV9H2RZ", + "secondary_id_a": 84145, + "secondary_key_a": "ITY12CRN3H8KPBJY", + "secondary_id_b": 725392, + "secondary_key_b": "EB6SJL8KOR7K7WCA", + "stats_pm2.5": 0.0, + "pm2.5_10minute": 0.0, + "pm2.5_30minute": 0.0, + "pm2.5_60minute": 0.0, + "pm2.5_6hour": 0.0, + "pm2.5_24hour": 0.2, + "pm2.5_1week": 1.5, + "pm2.5_time_stamp": 1663733769, + "pm2.5_10minute_a": 0.0, + "pm2.5_30minute_a": 0.0, + "pm2.5_60minute_a": 0.0, + "pm2.5_6hour_a": 0.0, + "pm2.5_24hour_a": 0.2, + "pm2.5_1week_a": 1.5, + "time_stamp_a": 1663733769, +} +EXPECTED_FILE_CONTENTS_2 = { + "data_time_stamp": 1676262962, + "sensor_index": 77, + "last_modified": 1575074907, + "date_created": 1456896339, + "last_seen": 1676262862, + "private": 0, + "is_owner": 0, + "name": "Sunnyside", + "icon": 0, + "location_type": 0, + "model": "PA-I", + "hardware": "2.0+1M+PMSX003-A", + "led_brightness": 25, + "firmware_version": "6.06b", + "rssi": -61, + "uptime": 12824, + "pa_latency": 328, + "memory": 16656, + "position_rating": 5, + "latitude": 40.750816, + "longitude": -111.82529, + "channel_state": 1, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 30, + "analog_input": 0.01, + "pm1.0": 43.6, + "pm1.0_a": 43.6, + "pm2.5": 57.7, + "pm2.5_a": 57.7, + "pm2.5_alt": 35.2, + "pm2.5_alt_a": 35.2, + "pm10.0": 73.3, + "pm10.0_a": 73.3, + "scattering_coefficient": 241.1, + "scattering_coefficient_a": 241.1, + "deciviews": 34.0, + "deciviews_a": 34.0, + "visual_range": 13.0, + "visual_range_a": 13.0, + "0.3_um_count": 16072, + "0.3_um_count_a": 16072, + "0.5_um_count": 2507, + "0.5_um_count_a": 2507, + "1.0_um_count": 177, + "1.0_um_count_a": 177, + "2.5_um_count": 18, + "2.5_um_count_a": 18, + "5.0_um_count": 6, + "5.0_um_count_a": 6, + "10.0_um_count": 2, + "10.0_um_count_a": 2, + "pm1.0_cf_1": 61.0, + "pm1.0_cf_1_a": 60.96, + "pm1.0_atm": 43.6, + "pm1.0_atm_a": 43.61, + "pm2.5_atm": 57.7, + "pm2.5_atm_a": 57.74, + "pm2.5_cf_1": 71.6, + "pm2.5_cf_1_a": 71.59, + "pm10.0_atm": 73.3, + "pm10.0_atm_a": 73.28, + "pm10.0_cf_1": 81.6, + "pm10.0_cf_1_a": 81.55, + "primary_id_a": 92577, + "primary_key_a": "8FBX16XKWTHHXMSP", + "primary_id_b": 725363, + "primary_key_b": "YWFLDV8KI4KHR6K6", + "secondary_id_a": 92578, + "secondary_key_a": "QX6J1413BUOIWJF0", + "secondary_id_b": 725364, + "secondary_key_b": "RGGZKX2J6N4YUYYA", + "stats_pm2.5": 57.7, + "pm2.5_10minute": 77.9, + "pm2.5_30minute": 73.4, + "pm2.5_60minute": 67.5, + "pm2.5_6hour": 56.7, + "pm2.5_24hour": 45.1, + "pm2.5_1week": 41.5, + "pm2.5_time_stamp": 1676262862, + "pm2.5_10minute_a": 77.9, + "pm2.5_30minute_a": 73.4, + "pm2.5_60minute_a": 67.5, + "pm2.5_6hour_a": 56.7, + "pm2.5_24hour_a": 45.1, + "pm2.5_1week_a": 41.5, + "time_stamp_a": 1676262862, +} +EXPECTED_FILE_CONTENTS_3 = { + "data_time_stamp": 1695569064, + "sensor_index": 163965, + "last_modified": 1664215336, + "date_created": 1663784441, + "last_seen": 1695568955, + "private": 0, + "is_owner": 0, + "name": "carlkidcrypto-purpleair2", + "icon": 0, + "location_type": 1, + "model": "PA-I", + "hardware": "2.0+BME280+PMSX003-A", + "led_brightness": 100, + "firmware_version": "7.02", + "rssi": -75, + "uptime": 69901, + "pa_latency": 943, + "memory": 15944, + "position_rating": 5, + "latitude": 46.74709, + "longitude": -116.998924, + "altitude": 2647, + "channel_state": 1, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 30, + "humidity": 31, + "humidity_a": 31, + "temperature": 79, + "temperature_a": 79, + "pressure": 923.43, + "pressure_a": 923.43, + "analog_input": 0.0, + "pm1.0": 2.6, + "pm1.0_a": 2.6, + "pm2.5": 4.4, + "pm2.5_a": 4.4, + "pm2.5_alt": 3.4, + "pm2.5_alt_a": 3.4, + "pm10.0": 4.4, + "pm10.0_a": 4.4, + "scattering_coefficient": 10.4, + "scattering_coefficient_a": 10.4, + "deciviews": 8.1, + "deciviews_a": 8.1, + "visual_range": 173.8, + "visual_range_a": 173.8, + "0.3_um_count": 691, + "0.3_um_count_a": 691, + "0.5_um_count": 178, + "0.5_um_count_a": 178, + "1.0_um_count": 35, + "1.0_um_count_a": 35, + "2.5_um_count": 0, + "2.5_um_count_a": 0, + "5.0_um_count": 0, + "5.0_um_count_a": 0, + "10.0_um_count": 0, + "10.0_um_count_a": 0, + "pm1.0_cf_1": 2.6, + "pm1.0_cf_1_a": 2.61, + "pm1.0_atm": 2.6, + "pm1.0_atm_a": 2.61, + "pm2.5_atm": 4.4, + "pm2.5_atm_a": 4.35, + "pm2.5_cf_1": 4.4, + "pm2.5_cf_1_a": 4.35, + "pm10.0_atm": 4.4, + "pm10.0_atm_a": 4.35, + "pm10.0_cf_1": 4.4, + "pm10.0_cf_1_a": 4.35, + "primary_id_a": 1868567, + "primary_key_a": "3XJ73Z60EX66XSHH", + "primary_id_b": 1868571, + "primary_key_b": "4TZ413W55H4ZCXMB", + "secondary_id_a": 1868569, + "secondary_key_a": "H8X58IEXEOBAPMQY", + "secondary_id_b": 1868573, + "secondary_key_b": "TWNBKVZZDWFMWSPV", + "stats_pm2.5": 4.4, + "pm2.5_10minute": 4.2, + "pm2.5_30minute": 4.2, + "pm2.5_60minute": 4.1, + "pm2.5_6hour": 4.2, + "pm2.5_24hour": 2.5, + "pm2.5_1week": 3.5, + "pm2.5_time_stamp": 1695568955, + "pm2.5_10minute_a": 4.2, + "pm2.5_30minute_a": 4.2, + "pm2.5_60minute_a": 4.1, + "pm2.5_6hour_a": 4.2, + "pm2.5_24hour_a": 2.5, + "pm2.5_1week_a": 3.5, + "time_stamp_a": 1695568955, +} +EXPECTED_FILE_CONTENTS_4 = { + "data_time_stamp": 1658588700, + "sensor_index": 14867, + "last_modified": 1561137879, + "date_created": 1536015895, + "last_seen": 1658588620, + "private": 0, + "is_owner": 0, + "name": "Kangerlussuaq, Greenland - University of Michigan Climate and Space", + "icon": 0, + "location_type": 0, + "model": "PA-II", + "hardware": "2.0+BME280+PMSX003-B+PMSX003-A", + "led_brightness": 35, + "firmware_version": "7.00", + "rssi": -60, + "uptime": 40557, + "pa_latency": 442, + "memory": 15672, + "position_rating": 0, + "latitude": 66.996, + "longitude": -50.6215, + "altitude": 971, + "channel_state": 3, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 100, + "confidence_auto": 100, + "confidence_manual": 100, + "humidity": 55, + "humidity_a": 55, + "temperature": 60, + "temperature_a": 60, + "pressure": 982.39, + "pressure_a": 982.39, + "analog_input": 0.02, + "pm1.0": 1.1, + "pm1.0_a": 1.0, + "pm1.0_b": 1.2, + "pm2.5": 1.7, + "pm2.5_a": 1.1, + "pm2.5_b": 2.2, + "pm2.5_alt": 1.3, + "pm2.5_alt_a": 1.0, + "pm2.5_alt_b": 1.7, + "pm10.0": 1.8, + "pm10.0_a": 1.3, + "pm10.0_b": 2.3, + "scattering_coefficient": 4.6, + "scattering_coefficient_a": 4.0, + "scattering_coefficient_b": 5.2, + "deciviews": 4.4, + "deciviews_a": 3.9, + "deciviews_b": 4.8, + "visual_range": 252.1, + "visual_range_a": 263.4, + "visual_range_b": 240.9, + "0.3_um_count": 305, + "0.3_um_count_a": 267, + "0.3_um_count_b": 344, + "0.5_um_count": 97, + "0.5_um_count_a": 84, + "0.5_um_count_b": 110, + "1.0_um_count": 11, + "1.0_um_count_a": 7, + "1.0_um_count_b": 16, + "2.5_um_count": 0, + "2.5_um_count_a": 0, + "2.5_um_count_b": 1, + "5.0_um_count": 0, + "5.0_um_count_a": 0, + "5.0_um_count_b": 0, + "10.0_um_count": 0, + "10.0_um_count_a": 0, + "10.0_um_count_b": 0, + "pm1.0_cf_1": 1.1, + "pm1.0_cf_1_a": 0.95, + "pm1.0_cf_1_b": 1.16, + "pm1.0_atm": 1.1, + "pm1.0_atm_a": 0.95, + "pm1.0_atm_b": 1.16, + "pm2.5_atm": 1.7, + "pm2.5_atm_a": 1.08, + "pm2.5_atm_b": 2.22, + "pm2.5_cf_1": 1.7, + "pm2.5_cf_1_a": 1.08, + "pm2.5_cf_1_b": 2.22, + "pm10.0_atm": 1.8, + "pm10.0_atm_a": 1.28, + "pm10.0_atm_b": 2.31, + "pm10.0_cf_1": 1.8, + "pm10.0_cf_1_a": 1.28, + "pm10.0_cf_1_b": 2.31, + "primary_id_a": 568591, + "primary_key_a": "FD9JF1Q2HM8Z1ZER", + "primary_id_b": 568593, + "primary_key_b": "CNQTXQCBZJ3F762X", + "secondary_id_a": 568592, + "secondary_key_a": "PJWR908B6ONVFZGF", + "secondary_id_b": 568594, + "secondary_key_b": "HL3MVK8ZU7D0BZRN", + "stats_pm2.5": 1.7, + "pm2.5_10minute": 2.2, + "pm2.5_30minute": 2.2, + "pm2.5_60minute": 2.2, + "pm2.5_6hour": 2.3, + "pm2.5_24hour": 2.0, + "pm2.5_1week": 0.8, + "pm2.5_time_stamp": 1658588620, + "pm2.5_10minute_a": 2.0, + "pm2.5_30minute_a": 2.1, + "pm2.5_60minute_a": 2.1, + "pm2.5_6hour_a": 2.2, + "pm2.5_24hour_a": 1.9, + "pm2.5_1week_a": 0.7, + "time_stamp_a": 1658588620, + "pm2.5_10minute_b": 2.3, + "pm2.5_30minute_b": 2.3, + "pm2.5_60minute_b": 2.3, + "pm2.5_6hour_b": 2.4, + "pm2.5_24hour_b": 2.1, + "pm2.5_1week_b": 0.8, + "time_stamp_b": 1658588620, +} +EXPECTED_FILE_CONTENTS_5 = { + "data_time_stamp": 1676263264, + "sensor_index": 137236, + "last_modified": 1666627408, + "date_created": 1636572956, + "last_seen": 1675087378, + "private": 0, + "is_owner": 0, + "name": "Idaea_2", + "icon": 0, + "location_type": 0, + "model": "PA-I-SD", + "hardware": "2.0+OPENLOG+31037 MB+DS3231+BME280+PMSX003-A", + "led_brightness": 35, + "firmware_version": "7.02", + "rssi": -45, + "uptime": 4, + "memory": 16728, + "position_rating": 5, + "latitude": 41.38754, + "longitude": 2.1154, + "altitude": 261, + "channel_state": 3, + "channel_flags": 1, + "channel_flags_manual": 0, + "channel_flags_auto": 1, + "confidence": 0, + "confidence_auto": 0, + "confidence_manual": 0, + "humidity": 22, + "humidity_a": 22, + "temperature": 75, + "temperature_a": 75, + "pressure": 1013.55, + "pressure_a": 1013.55, + "analog_input": 0.06, + "pm1.0_a": 2020.0, + "pm2.5_a": 2020.0, + "pm2.5_alt_a": 0.1, + "pm10.0_a": 2020.0, + "scattering_coefficient_a": 0.5, + "deciviews_a": 0.6, + "visual_range_a": 368.8, + "0.3_um_count_a": 32, + "0.5_um_count_a": 10, + "1.0_um_count_a": 0, + "2.5_um_count_a": 0, + "5.0_um_count_a": 0, + "10.0_um_count_a": 0, + "pm1.0_cf_1_a": 3030.34, + "pm1.0_atm_a": 2020.0, + "pm2.5_atm_a": 2020.0, + "pm2.5_cf_1_a": 3030.34, + "pm10.0_atm_a": 2020.0, + "pm10.0_cf_1_a": 3030.34, + "primary_id_a": 1566363, + "primary_key_a": "WC9GBNIBV6EA19ZJ", + "primary_id_b": 1566365, + "primary_key_b": "HVKTP5FAY62ZQGR6", + "secondary_id_a": 1566364, + "secondary_key_a": "63FXIGMH8F7JLETX", + "secondary_id_b": 1566366, + "secondary_key_b": "8BZC8PVWJMB5CG20", + "stats_pm2.5": None, + "pm2.5_10minute": 11.3, + "pm2.5_30minute": 11.6, + "pm2.5_60minute": 12.1, + "pm2.5_6hour": 7.8, + "pm2.5_24hour": 4.7, + "pm2.5_1week": 6.2, + "pm2.5_time_stamp": 1675087378, + "pm2.5_10minute_a": 10.5, + "pm2.5_30minute_a": 11.3, + "pm2.5_60minute_a": 12.2, + "pm2.5_6hour_a": 8.2, + "pm2.5_24hour_a": 4.9, + "pm2.5_1week_a": 6.3, + "time_stamp_a": 1675087378, + "pm2.5_b": None, + "pm2.5_10minute_b": 11.3, + "pm2.5_30minute_b": 11.6, + "pm2.5_60minute_b": 12.1, + "pm2.5_6hour_b": 7.8, + "pm2.5_24hour_b": 4.7, + "pm2.5_1week_b": 6.2, + "time_stamp_b": 1675087378, +} +EXPECTED_FILE_CONTENTS_6 = { + "data_time_stamp": 1676263122, + "sensor_index": 131190, + "last_modified": 1646085907, + "date_created": 1633034575, + "last_seen": 1676263011, + "private": 0, + "is_owner": 0, + "name": "Lanterns at Rock Creek", + "icon": 0, + "location_type": 0, + "model": "PA-II-SD", + "hardware": "2.0+OPENLOG+31037 MB+DS3231+BME280+PMSX003-B+PMSX003-A", + "led_brightness": 35, + "firmware_version": "7.02", + "rssi": -69, + "uptime": 66182, + "pa_latency": 239, + "memory": 16240, + "position_rating": 5, + "latitude": 39.94589, + "longitude": -105.14925, + "altitude": 5471, + "channel_state": 3, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 100, + "confidence_auto": 100, + "confidence_manual": 100, + "humidity": 38, + "humidity_a": 38, + "temperature": 41, + "temperature_a": 41, + "pressure": 832.34, + "pressure_a": 832.34, + "analog_input": 0.0, + "pm1.0": 0.0, + "pm1.0_a": 0.0, + "pm1.0_b": 0.0, + "pm2.5": 1.8, + "pm2.5_a": 1.9, + "pm2.5_b": 1.6, + "pm2.5_alt": 1.3, + "pm2.5_alt_a": 1.4, + "pm2.5_alt_b": 1.2, + "pm10.0": 2.1, + "pm10.0_a": 2.6, + "pm10.0_b": 1.6, + "0.3_um_count": 228, + "0.3_um_count_a": 240, + "0.3_um_count_b": 216, + "0.5_um_count": 219, + "0.5_um_count_a": 232, + "0.5_um_count_b": 206, + "1.0_um_count": 1, + "1.0_um_count_a": 1, + "1.0_um_count_b": 1, + "2.5_um_count": 0, + "2.5_um_count_a": 0, + "2.5_um_count_b": 0, + "5.0_um_count": 0, + "5.0_um_count_a": 0, + "5.0_um_count_b": 0, + "10.0_um_count": 0, + "10.0_um_count_a": 0, + "10.0_um_count_b": 0, + "pm1.0_cf_1": 0.0, + "pm1.0_cf_1_a": 0.0, + "pm1.0_cf_1_b": 0.0, + "pm1.0_atm": 0.0, + "pm1.0_atm_a": 0.0, + "pm1.0_atm_b": 0.0, + "pm2.5_atm": 1.8, + "pm2.5_atm_a": 1.91, + "pm2.5_atm_b": 1.64, + "pm2.5_cf_1": 1.8, + "pm2.5_cf_1_a": 1.91, + "pm2.5_cf_1_b": 1.64, + "pm10.0_atm": 2.1, + "pm10.0_atm_a": 2.64, + "pm10.0_atm_b": 1.64, + "pm10.0_cf_1": 2.1, + "pm10.0_cf_1_a": 2.64, + "pm10.0_cf_1_b": 1.64, + "primary_id_a": 1523479, + "primary_key_a": "XJD1ZRIXS13NQI06", + "primary_id_b": 1664817, + "primary_key_b": "LTKCX2OQJVSON7RS", + "secondary_id_a": 1523480, + "secondary_key_a": "SEV906OKLV2D1Q3S", + "secondary_id_b": 1664818, + "secondary_key_b": "G3OJQEJG0DRMLRZV", + "stats_pm2.5": 1.8, + "pm2.5_10minute": 1.9, + "pm2.5_30minute": 1.6, + "pm2.5_60minute": 1.4, + "pm2.5_6hour": 1.5, + "pm2.5_24hour": 1.5, + "pm2.5_1week": 3.2, + "pm2.5_time_stamp": 1676263011, + "pm2.5_10minute_a": 2.1, + "pm2.5_30minute_a": 1.9, + "pm2.5_60minute_a": 1.6, + "pm2.5_6hour_a": 1.6, + "pm2.5_24hour_a": 1.6, + "pm2.5_1week_a": 3.5, + "time_stamp_a": 1676263011, + "pm2.5_10minute_b": 1.7, + "pm2.5_30minute_b": 1.4, + "pm2.5_60minute_b": 1.2, + "pm2.5_6hour_b": 1.3, + "pm2.5_24hour_b": 1.3, + "pm2.5_1week_b": 2.9, + "time_stamp_b": 1676263011, +} +EXPECTED_FILE_CONTENTS_7 = { + "data_time_stamp": 1676263211, + "sensor_index": 146330, + "last_modified": 1654484887, + "date_created": 1648763897, + "last_seen": 1676263160, + "private": 0, + "is_owner": 0, + "name": "indoor", + "icon": 0, + "location_type": 1, + "model": "PA-I-LED", + "hardware": "3.0+BME280+BME680+PMSX003-A", + "led_brightness": 20, + "firmware_version": "7.02", + "rssi": -67, + "uptime": 58166, + "pa_latency": 278, + "memory": 16568, + "position_rating": 1, + "latitude": 40.077816, + "longitude": -111.57601, + "altitude": 5154, + "channel_state": 1, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 30, + "humidity": 17, + "humidity_a": 18, + "humidity_b": 17, + "temperature": 82, + "temperature_a": 82, + "temperature_b": 83, + "pressure": 857.92, + "pressure_a": 858.0, + "pressure_b": 857.85, + "voc": 246.8, + "voc_b": 246.75, + "analog_input": 0.58, + "pm1.0": 33.6, + "pm1.0_a": 33.6, + "pm2.5": 55.0, + "pm2.5_a": 55.0, + "pm2.5_alt": 31.8, + "pm2.5_alt_a": 31.8, + "pm10.0": 59.5, + "pm10.0_a": 59.5, + "scattering_coefficient": 90.8, + "scattering_coefficient_a": 90.8, + "deciviews": 24.8, + "deciviews_a": 24.8, + "visual_range": 32.8, + "visual_range_a": 32.8, + "0.3_um_count": 6054, + "0.3_um_count_a": 6054, + "0.5_um_count": 1715, + "0.5_um_count_a": 1715, + "1.0_um_count": 349, + "1.0_um_count_a": 349, + "2.5_um_count": 23, + "2.5_um_count_a": 23, + "5.0_um_count": 5, + "5.0_um_count_a": 5, + "10.0_um_count": 1, + "10.0_um_count_a": 1, + "pm1.0_cf_1": 33.6, + "pm1.0_cf_1_a": 33.59, + "pm1.0_atm": 27.4, + "pm1.0_atm_a": 27.43, + "pm2.5_atm": 43.5, + "pm2.5_atm_a": 43.51, + "pm2.5_cf_1": 55.0, + "pm2.5_cf_1_a": 54.97, + "pm10.0_atm": 53.7, + "pm10.0_atm_a": 53.68, + "pm10.0_cf_1": 59.5, + "pm10.0_cf_1_a": 59.49, + "primary_id_a": 1692079, + "primary_key_a": "R27RO2JSMWZKDIVW", + "primary_id_b": 1692081, + "primary_key_b": "RE0WK8H3GM79STTL", + "secondary_id_a": 1692080, + "secondary_key_a": "2QQFR5Q50EQC5LWK", + "secondary_id_b": 1692082, + "secondary_key_b": "DQS4JXPQYN7O6TK5", + "stats_pm2.5": 55.0, + "pm2.5_10minute": 59.3, + "pm2.5_30minute": 74.7, + "pm2.5_60minute": 102.8, + "pm2.5_6hour": 103.9, + "pm2.5_24hour": 41.7, + "pm2.5_1week": 8.5, + "pm2.5_time_stamp": 1676263160, + "pm2.5_10minute_a": 59.3, + "pm2.5_30minute_a": 74.7, + "pm2.5_60minute_a": 102.8, + "pm2.5_6hour_a": 103.9, + "pm2.5_24hour_a": 41.7, + "pm2.5_1week_a": 8.5, + "time_stamp_a": 1676263160, +} +EXPECTED_FILE_CONTENTS_8 = { + "data_time_stamp": 1695569365, + "sensor_index": 123456, + "last_modified": 1656033161, + "date_created": 1653666655, + "last_seen": 1695569319, + "private": 0, + "is_owner": 0, + "name": "A NAME GOES HERE", + "icon": 0, + "location_type": 0, + "model": "PA-II-FLEX", + "hardware": "3.0+OPENLOG+31037 MB+DS3231+BME280+BME68X+PMSX003-A+PMSX003-B", + "led_brightness": 100, + "firmware_version": "7.04", + "rssi": -62, + "uptime": 137, + "pa_latency": 732, + "memory": 5672, + "position_rating": 5, + "latitude": 123.123, + "longitude": -123.123, + "altitude": 123, + "channel_state": 3, + "channel_flags": 0, + "channel_flags_manual": 0, + "channel_flags_auto": 0, + "confidence": 100, + "confidence_auto": 100, + "confidence_manual": 100, + "humidity": 46, + "humidity_a": 44, + "humidity_b": 49, + "temperature": 63, + "temperature_a": 64, + "temperature_b": 62, + "pressure": 924.6, + "pressure_a": 925.33, + "pressure_b": 923.87, + "voc": 598.9, + "voc_b": 598.89, + "analog_input": 0.06, + "pm1.0": 23.7, + "pm1.0_a": 23.3, + "pm1.0_b": 24.0, + "pm2.5": 34.7, + "pm2.5_a": 35.0, + "pm2.5_b": 34.5, + "pm2.5_alt": 22.1, + "pm2.5_alt_a": 23.2, + "pm2.5_alt_b": 21.1, + "pm10.0": 44.4, + "pm10.0_a": 46.7, + "pm10.0_b": 42.0, + "scattering_coefficient": 70.1, + "scattering_coefficient_a": 70.1, + "scattering_coefficient_b": 70.1, + "deciviews": 22.4, + "deciviews_a": 22.4, + "deciviews_b": 22.4, + "visual_range": 41.5, + "visual_range_a": 41.5, + "visual_range_b": 41.4, + "0.3_um_count": 4671, + "0.3_um_count_a": 4671, + "0.3_um_count_b": 4672, + "0.5_um_count": 1330, + "0.5_um_count_a": 1330, + "0.5_um_count_b": 1330, + "1.0_um_count": 230, + "1.0_um_count_a": 253, + "1.0_um_count_b": 208, + "2.5_um_count": 21, + "2.5_um_count_a": 25, + "2.5_um_count_b": 18, + "5.0_um_count": 9, + "5.0_um_count_a": 11, + "5.0_um_count_b": 7, + "10.0_um_count": 4, + "10.0_um_count_a": 9, + "10.0_um_count_b": 0, + "pm1.0_cf_1": 27.0, + "pm1.0_cf_1_a": 26.48, + "pm1.0_cf_1_b": 27.53, + "pm1.0_atm": 23.7, + "pm1.0_atm_a": 23.34, + "pm1.0_atm_b": 23.98, + "pm2.5_atm": 34.7, + "pm2.5_atm_a": 34.95, + "pm2.5_atm_b": 34.46, + "pm2.5_cf_1": 38.1, + "pm2.5_cf_1_a": 38.61, + "pm2.5_cf_1_b": 37.56, + "pm10.0_atm": 44.4, + "pm10.0_atm_a": 46.68, + "pm10.0_atm_b": 42.05, + "pm10.0_cf_1": 45.2, + "pm10.0_cf_1_a": 48.2, + "pm10.0_cf_1_b": 42.2, + "primary_id_a": 1750199, + "primary_key_a": "M9M66UHOIS8YIAGH", + "primary_id_b": 1750201, + "primary_key_b": "D9M2N0ZO81PTA210", + "secondary_id_a": 1750200, + "secondary_key_a": "O4T8GWNDUROR5EP6", + "secondary_id_b": 1750202, + "secondary_key_b": "2YTJ6VA9HS3T8K07", + "stats_pm2.5": 34.7, + "pm2.5_10minute": 31.6, + "pm2.5_30minute": 32.2, + "pm2.5_60minute": 32.5, + "pm2.5_6hour": 22.2, + "pm2.5_24hour": 13.6, + "pm2.5_1week": 9.9, + "pm2.5_time_stamp": 1695569319, + "pm2.5_10minute_a": 32.0, + "pm2.5_30minute_a": 32.5, + "pm2.5_60minute_a": 32.8, + "pm2.5_6hour_a": 22.3, + "pm2.5_24hour_a": 13.6, + "pm2.5_1week_a": 9.8, + "time_stamp_a": 1695569319, + "pm2.5_10minute_b": 31.3, + "pm2.5_30minute_b": 31.8, + "pm2.5_60minute_b": 32.2, + "pm2.5_6hour_b": 22.1, + "pm2.5_24hour_b": 13.5, + "pm2.5_1week_b": 9.9, + "time_stamp_b": 1695569319, +} diff --git a/tests/test_purpleair_data_logger.py b/tests/test_purpleair_data_logger.py new file mode 100644 index 0000000..c43325d --- /dev/null +++ b/tests/test_purpleair_data_logger.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +""" + Copyright 2023 carlkidcrypto, All rights reserved. +""" + + +import unittest +import requests_mock +import sys + +sys.path.append("../") + +from purpleair_data_logger.PurpleAirDataLogger import ( + PurpleAirDataLoggerError, + PurpleAirDataLogger, +) + + +class PurpleAirDataLoggerTest(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_purpleair_data_logger_error(self): + """ + Test that PurpleAirDataLoggerError is the right type. + """ + + # Setup + self.error_msg_str = "This is a test!" + + # Action + retval = PurpleAirDataLoggerError(self.error_msg_str) + + # Expected Result + self.assertEqual(retval.message, self.error_msg_str) + self.assertEqual(retval.__class__, PurpleAirDataLoggerError) + + def test_purpleair_data_logger_getters_setters(self): + """ + Test PurpleAirDataLogger getters/setters + """ + + # Setup + expected_url_request = "https://api.purpleair.com/v1/keys" + + # Action and Expected Result + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text='{"api_version" : "1.1.1", "time_stamp": 123456789, "api_key_type": "READ"}', + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiReadKey="123456789") + + padl.send_request_every_x_seconds = 100 + self.assertEqual(padl.send_request_every_x_seconds, 100) + + def test_purpleair_data_logger_store_sensor_data(self): + """ + Test PurpleAirDataLogger store_sensor_data method raises, since inherting classes + must implment it. + """ + + # Setup + expected_url_request = "https://api.purpleair.com/v1/keys" + + # Action and Expected Result + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text='{"api_version" : "1.1.1", "time_stamp": 123456789, "api_key_type": "READ"}', + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiReadKey="123456789") + + with self.assertRaises(NotImplementedError): + padl.store_sensor_data({}) diff --git a/tests/test_purpleair_data_logger_helpers.py b/tests/test_purpleair_data_logger_helpers.py new file mode 100644 index 0000000..35ce192 --- /dev/null +++ b/tests/test_purpleair_data_logger_helpers.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python3 + +""" + Copyright 2023 carlkidcrypto, All rights reserved. +""" + + +import unittest +from unittest.mock import MagicMock, patch +import requests_mock +import sys +from json import load, dumps + +sys.path.append("../") + +from purpleair_data_logger.PurpleAirDataLoggerHelpers import ( + generate_common_arg_parser, + validate_sensor_data_before_insert, + construct_store_sensor_data_type, + flatten_single_sensor_data, + logic_for_storing_single_sensor_data, + logic_for_storing_multiple_sensors_data, + logic_for_storing_group_sensors_data, + logic_for_storing_local_sensors_data, +) + +from purpleair_data_logger.PurpleAirDataLogger import PurpleAirDataLogger + +from helpers import ( + DATA_IN_1, + DATA_IN_2, + LOCAL_API_DATA_IN_1, + LOCAL_API_DATA_IN_2, + DATA_OUT_1, + DATA_OUT_2, + DATA_OUT_3, + DATA_OUT_4, + DATA_OUT_5, + LOCAL_API_DATA_OUT_1, + LOCAL_API_DATA_OUT_2, + EXPECTED_VALUE_1, + EXPECTED_FILE_CONTENTS_1, + EXPECTED_FILE_CONTENTS_2, + EXPECTED_FILE_CONTENTS_3, + EXPECTED_FILE_CONTENTS_4, + EXPECTED_FILE_CONTENTS_5, + EXPECTED_FILE_CONTENTS_6, + EXPECTED_FILE_CONTENTS_7, + EXPECTED_FILE_CONTENTS_8, +) + + +class PurpleAirDataLoggerHelpersTest(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_generate_common_arg_parser_with_valid_args(self): + """ + Test that our generator makes the common arguments that all PADLS use. + """ + + retval = generate_common_arg_parser("TEST") + self.assertEqual(retval.description, "TEST") + self.assertTrue(retval.parse_args(["-paa_read_key", "2"])) + self.assertTrue(retval.parse_args(["-paa_write_key", "2"])) + self.assertTrue( + retval.parse_args(["-paa_single_sensor_request_json_file", "2"]) + ) + self.assertTrue( + retval.parse_args(["-paa_multiple_sensor_request_json_file", "2"]) + ) + self.assertTrue(retval.parse_args(["-paa_group_sensor_request_json_file", "2"])) + self.assertTrue(retval.parse_args(["-paa_local_sensor_request_json_file", "2"])) + + def test_validate_sensor_data_before_insert(self): + """ + Test that our validator makes any missing fields their defaults + """ + + expected_value = EXPECTED_VALUE_1 + self.assertEqual(validate_sensor_data_before_insert({}), expected_value) + + def test_construct_store_sensor_data_type(self): + """ + Test that our contructor makes the dict data type that the PurpleAirDataLogger.store_sensor_data method expects. + """ + + # The sensors data will look something like this: + # {'api_version': 'V1.0.11-0.0.34', 'time_stamp': 1659710288, 'data_time_stamp': 1659710232, + # 'max_age': 604800, 'firmware_default_version': '7.00', 'fields': ['sensor_index', 'name'], + # 'data': [[131075, 'Mariners Bluff'], [131079, 'BRSKBV-outside'], [131077, 'BEE Patio'], + # ... ]} + data_in = DATA_IN_1 + data_out = DATA_OUT_1 + + retval = construct_store_sensor_data_type(data_in) + self.assertEqual(retval, data_out) + + def test_flatten_single_sensor_data(self): + """ + Test that the flatten_single_sensor_data can handle all the sample responses under ../external_network_hardware_variant_json_samples/*.json + """ + + file_obj = open( + "../external_network_hardware_variant_json_samples/1.0+1M+PMSX003-O.json", + "r", + ) + file_data = load(file_obj) + file_obj.close() + retval = flatten_single_sensor_data(file_data) + expected_value = EXPECTED_FILE_CONTENTS_1 + self.assertEqual(retval, expected_value) + + file_obj = open( + "../external_network_hardware_variant_json_samples/2.0+1M+PMSX003-A.json", + "r", + ) + file_data = load(file_obj) + file_obj.close() + retval = flatten_single_sensor_data(file_data) + expected_value = EXPECTED_FILE_CONTENTS_2 + self.assertEqual(retval, expected_value) + + file_obj = open( + "../external_network_hardware_variant_json_samples/2.0+BME280+PMSX003-A.json", + "r", + ) + file_data = load(file_obj) + file_obj.close() + retval = flatten_single_sensor_data(file_data) + expected_value = EXPECTED_FILE_CONTENTS_3 + self.assertEqual(retval, expected_value) + + file_obj = open( + "../external_network_hardware_variant_json_samples/2.0+BME280+PMSX003-B+PMSX003-A.json", + "r", + ) + file_data = load(file_obj) + file_obj.close() + retval = flatten_single_sensor_data(file_data) + expected_value = EXPECTED_FILE_CONTENTS_4 + self.assertEqual(retval, expected_value) + + file_obj = open( + "../external_network_hardware_variant_json_samples/2.0+OPENLOG+31037 MB+DS3231+BME280+PMSX003-A.json", + "r", + ) + file_data = load(file_obj) + file_obj.close() + retval = flatten_single_sensor_data(file_data) + expected_value = EXPECTED_FILE_CONTENTS_5 + self.assertEqual(retval, expected_value) + + file_obj = open( + "../external_network_hardware_variant_json_samples/2.0+OPENLOG+31037 MB+DS3231+BME280+PMSX003-B+PMSX003-A.json", + "r", + ) + file_data = load(file_obj) + file_obj.close() + retval = flatten_single_sensor_data(file_data) + expected_value = EXPECTED_FILE_CONTENTS_6 + self.assertEqual(retval, expected_value) + + file_obj = open( + "../external_network_hardware_variant_json_samples/3.0+BME280+BME680+PMSX003-A.json", + "r", + ) + file_data = load(file_obj) + file_obj.close() + retval = flatten_single_sensor_data(file_data) + expected_value = EXPECTED_FILE_CONTENTS_7 + self.assertEqual(retval, expected_value) + + file_obj = open( + "../external_network_hardware_variant_json_samples/3.0+OPENLOG+31037 MB+DS3231+BME280+BME68X+PMSX003-A+PMSX003-B.json", + "r", + ) + file_data = load(file_obj) + file_obj.close() + retval = flatten_single_sensor_data(file_data) + expected_value = EXPECTED_FILE_CONTENTS_8 + self.assertEqual(retval, expected_value) + + def test_logic_for_storing_single_sensor_data(self): + """ + Test the main logic for the PurpleAirDataLogger.`_run_loop_for_storing_single_sensor_data` method. + """ + + # Setup + expected_url_request = "https://api.purpleair.com/v1/keys" + padl = None + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text='{"api_version" : "1.1.1", "time_stamp": 123456789, "api_key_type": "READ"}', + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiReadKey="123456789") + padl.store_sensor_data = MagicMock(name="store_sensor_data") + json_config_file = { + "sensor_index": "1111", + "read_key": None, + "fields": ["name", "icon", "model", "hardware", "location_type"], + } + + # Action & Expected Result + expected_url_request = "https://api.purpleair.com/v1/sensors/1111?fields=%5B'name','icon','model','hardware','location_type'%5D" + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text=f"{dumps(DATA_IN_2)}", + status_code=200, + ) + logic_for_storing_single_sensor_data(padl, json_config_file) + padl.store_sensor_data.return_value = None + padl.store_sensor_data.assert_called_once_with(DATA_OUT_2) + + def test_logic_for_storing_multiple_sensors_data(self): + """ + Test the main logic for the PurpleAirDataLogger.`_run_loop_for_storing_multiple_sensors_data` method. + """ + + # Setup + expected_url_request = "https://api.purpleair.com/v1/keys" + padl = None + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text='{"api_version" : "1.1.1", "time_stamp": 123456789, "api_key_type": "READ"}', + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiReadKey="123456789") + padl.store_sensor_data = MagicMock(name="store_sensor_data") + json_config_file = { + "poll_interval_seconds": 60, + "fields": "name, icon, model, hardware, location_type, private, latitude, longitude, altitude, position_rating, led_brightness, firmware_version, firmware_upgrade, rssi, uptime, pa_latency, memory, last_seen, last_modified, date_created, channel_state, channel_flags, channel_flags_manual, channel_flags_auto, confidence, confidence_manual, confidence_auto,humidity, humidity_a, humidity_b, temperature, temperature_a, temperature_b, pressure, pressure_a, pressure_b,voc, voc_a, voc_b, ozone1, analog_input,pm1.0, pm1.0_a, pm1.0_b, pm1.0_atm, pm1.0_atm_a, pm1.0_atm_b, pm1.0_cf_1, pm1.0_cf_1_a,pm1.0_cf_1_b,pm2.5_alt, pm2.5_alt_a, pm2.5_alt_b, pm2.5, pm2.5_a, pm2.5_b, pm2.5_atm, pm2.5_atm_a, pm2.5_atm_b, pm2.5_cf_1, pm2.5_cf_1_a, pm2.5_cf_1_b,pm2.5_10minute, pm2.5_10minute_a, pm2.5_10minute_b, pm2.5_30minute, pm2.5_30minute_a, pm2.5_30minute_b, pm2.5_60minute, pm2.5_60minute_a, pm2.5_60minute_b, pm2.5_6hour, pm2.5_6hour_a, pm2.5_6hour_b,pm2.5_24hour, pm2.5_24hour_a, pm2.5_24hour_b, pm2.5_1week, pm2.5_1week_a, pm2.5_1week_b,pm10.0, pm10.0_a, pm10.0_b, pm10.0_atm, pm10.0_atm_a, pm10.0_atm_b, pm10.0_cf_1, pm10.0_cf_1_a, pm10.0_cf_1_b,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,5.0_um_count_a,5.0_um_count_b,10.0_um_count,10.0_um_count_a,10.0_um_count_b,primary_id_a, primary_key_a, secondary_id_a, secondary_key_a, primary_id_b, primary_key_b, secondary_id_b, secondary_key_b", + "location_type": None, + "read_keys": None, + "show_only": None, + "modified_since": None, + "max_age": None, + "nwlng": None, + "nwlat": None, + "selng": None, + "selat": None, + } + + # Action & Expected Result + expected_url_request = "https://api.purpleair.com/v1/sensors/?fields=name,icon,model,hardware,location_type,private,latitude,longitude,altitude,position_rating,led_brightness,firmware_version,firmware_upgrade,rssi,uptime,pa_latency,memory,last_seen,last_modified,date_created,channel_state,channel_flags,channel_flags_manual,channel_flags_auto,confidence,confidence_manual,confidence_auto,humidity,humidity_a,humidity_b,temperature,temperature_a,temperature_b,pressure,pressure_a,pressure_b,voc,voc_a,voc_b,ozone1,analog_input,pm1.0,pm1.0_a,pm1.0_b,pm1.0_atm,pm1.0_atm_a,pm1.0_atm_b,pm1.0_cf_1,pm1.0_cf_1_a,pm1.0_cf_1_b,pm2.5_alt,pm2.5_alt_a,pm2.5_alt_b,pm2.5,pm2.5_a,pm2.5_b,pm2.5_atm,pm2.5_atm_a,pm2.5_atm_b,pm2.5_cf_1,pm2.5_cf_1_a,pm2.5_cf_1_b,pm2.5_10minute,pm2.5_10minute_a,pm2.5_10minute_b,pm2.5_30minute,pm2.5_30minute_a,pm2.5_30minute_b,pm2.5_60minute,pm2.5_60minute_a,pm2.5_60minute_b,pm2.5_6hour,pm2.5_6hour_a,pm2.5_6hour_b,pm2.5_24hour,pm2.5_24hour_a,pm2.5_24hour_b,pm2.5_1week,pm2.5_1week_a,pm2.5_1week_b,pm10.0,pm10.0_a,pm10.0_b,pm10.0_atm,pm10.0_atm_a,pm10.0_atm_b,pm10.0_cf_1,pm10.0_cf_1_a,pm10.0_cf_1_b,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,5.0_um_count_a,5.0_um_count_b,10.0_um_count,10.0_um_count_a,10.0_um_count_b,primary_id_a,primary_key_a,secondary_id_a,secondary_key_a,primary_id_b,primary_key_b,secondary_id_b,secondary_key_b" + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text=f"{dumps(DATA_IN_1)}", + status_code=200, + ) + logic_for_storing_multiple_sensors_data(padl, json_config_file) + padl.store_sensor_data.return_value = None + padl.store_sensor_data.side_effect = [DATA_OUT_3, DATA_OUT_4, DATA_OUT_5] + self.assertEqual(padl.store_sensor_data.call_count, 3) + + @patch("time.sleep", return_value=None) + def test_logic_for_storing_group_sensors_data_with_group_id_none( + self, patched_time_sleep + ): + """ + Test the main logic for the PurpleAirDataLogger.`_run_loop_for_storing_group_sensors_data` method. + """ + + # Setup + expected_url_request = "https://api.purpleair.com/v1/keys" + padl = None + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text='{"api_version" : "1.1.1", "time_stamp": 123456789, "api_key_type": "READ"}', + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiReadKey="123456789") + padl.store_sensor_data = MagicMock(name="store_sensor_data") + json_config_file = { + "sensor_group_name": "A Name Goes Here", + "add_sensors_to_group": True, + "sensor_index_list": [77, 81, 95079, 167897], + "poll_interval_seconds": 60, + "fields": "name, icon, model, hardware, location_type, private, latitude, longitude, altitude, position_rating, led_brightness, firmware_version, firmware_upgrade, rssi, uptime, pa_latency, memory, last_seen, last_modified, date_created, channel_state, channel_flags, channel_flags_manual, channel_flags_auto, confidence, confidence_manual, confidence_auto,humidity, humidity_a, humidity_b, temperature, temperature_a, temperature_b, pressure, pressure_a, pressure_b,voc, voc_a, voc_b, ozone1, analog_input,pm1.0, pm1.0_a, pm1.0_b, pm1.0_atm, pm1.0_atm_a, pm1.0_atm_b, pm1.0_cf_1, pm1.0_cf_1_a,pm1.0_cf_1_b,pm2.5_alt, pm2.5_alt_a, pm2.5_alt_b, pm2.5, pm2.5_a, pm2.5_b, pm2.5_atm, pm2.5_atm_a, pm2.5_atm_b, pm2.5_cf_1, pm2.5_cf_1_a, pm2.5_cf_1_b,pm2.5_10minute, pm2.5_10minute_a, pm2.5_10minute_b, pm2.5_30minute, pm2.5_30minute_a, pm2.5_30minute_b, pm2.5_60minute, pm2.5_60minute_a, pm2.5_60minute_b, pm2.5_6hour, pm2.5_6hour_a, pm2.5_6hour_b,pm2.5_24hour, pm2.5_24hour_a, pm2.5_24hour_b, pm2.5_1week, pm2.5_1week_a, pm2.5_1week_b,pm10.0, pm10.0_a, pm10.0_b, pm10.0_atm, pm10.0_atm_a, pm10.0_atm_b, pm10.0_cf_1, pm10.0_cf_1_a, pm10.0_cf_1_b,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,5.0_um_count_a,5.0_um_count_b,10.0_um_count,10.0_um_count_a,10.0_um_count_b,primary_id_a, primary_key_a, secondary_id_a, secondary_key_a, primary_id_b, primary_key_b, secondary_id_b, secondary_key_b", + "location_type": None, + "read_keys": None, + "show_only": None, + "modified_since": None, + "max_age": None, + "nwlng": None, + "nwlat": None, + "selng": None, + "selat": None, + } + + expected_return_data_1 = { + "groups": [ + {"name": "A Name Goes Here!", "id": 12345}, + {"name": "A Name Goes Here!!", "id": 123456}, + ] + } + + expected_return_data_2 = {"group_id": 1234} + expected_return_data_3 = {} + expected_return_data_4 = { + "api_version": "V1.0.11-0.0.42", + "time_stamp": 1676784867, + "data_time_stamp": 1676784847, + "group_id": 1234, + "max_age": 604800, + "firmware_default_version": "7.02", + "fields": ["sensor_index", "name"], + "data": [[77, "Sunnyside"], [81, "Sherwood Hills 2"]], + } + + # Action & Expected Result + expected_url_request_1 = "https://api.purpleair.com/v1/groups/" + expected_url_request_2 = "https://api.purpleair.com/v1/groups" + expected_url_request_3 = "https://api.purpleair.com/v1/groups/1234/members" + expected_url_request_4 = "https://api.purpleair.com/v1/groups/1234/members?fields=name,icon,model,hardware,location_type,private,latitude,longitude,altitude,position_rating,led_brightness,firmware_version,firmware_upgrade,rssi,uptime,pa_latency,memory,last_seen,last_modified,date_created,channel_state,channel_flags,channel_flags_manual,channel_flags_auto,confidence,confidence_manual,confidence_auto,humidity,humidity_a,humidity_b,temperature,temperature_a,temperature_b,pressure,pressure_a,pressure_b,voc,voc_a,voc_b,ozone1,analog_input,pm1.0,pm1.0_a,pm1.0_b,pm1.0_atm,pm1.0_atm_a,pm1.0_atm_b,pm1.0_cf_1,pm1.0_cf_1_a,pm1.0_cf_1_b,pm2.5_alt,pm2.5_alt_a,pm2.5_alt_b,pm2.5,pm2.5_a,pm2.5_b,pm2.5_atm,pm2.5_atm_a,pm2.5_atm_b,pm2.5_cf_1,pm2.5_cf_1_a,pm2.5_cf_1_b,pm2.5_10minute,pm2.5_10minute_a,pm2.5_10minute_b,pm2.5_30minute,pm2.5_30minute_a,pm2.5_30minute_b,pm2.5_60minute,pm2.5_60minute_a,pm2.5_60minute_b,pm2.5_6hour,pm2.5_6hour_a,pm2.5_6hour_b,pm2.5_24hour,pm2.5_24hour_a,pm2.5_24hour_b,pm2.5_1week,pm2.5_1week_a,pm2.5_1week_b,pm10.0,pm10.0_a,pm10.0_b,pm10.0_atm,pm10.0_atm_a,pm10.0_atm_b,pm10.0_cf_1,pm10.0_cf_1_a,pm10.0_cf_1_b,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,5.0_um_count_a,5.0_um_count_b,10.0_um_count,10.0_um_count_a,10.0_um_count_b,primary_id_a,primary_key_a,secondary_id_a,secondary_key_a,primary_id_b,primary_key_b,secondary_id_b,secondary_key_b" + + with requests_mock.Mocker() as m1: + m1.get( + expected_url_request_1, + text=f"{dumps(expected_return_data_1)}", + status_code=200, + ) + m1.post( + expected_url_request_2, + text=f"{dumps(expected_return_data_2)}", + status_code=200, + ) + m1.post( + expected_url_request_3, + text=f"{dumps(expected_return_data_3)}", + status_code=200, + ) + m1.get( + expected_url_request_4, + text=f"{dumps(expected_return_data_4)}", + status_code=200, + ) + self.assertEqual( + logic_for_storing_group_sensors_data(padl, None, json_config_file), 1234 + ) + + def test_logic_for_storing_group_sensors_data_without_group_id_1234(self): + """ + Test the main logic for the PurpleAirDataLogger.`_run_loop_for_storing_group_sensors_data` method. + """ + + # Setup + expected_url_request = "https://api.purpleair.com/v1/keys" + padl = None + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text='{"api_version" : "1.1.1", "time_stamp": 123456789, "api_key_type": "READ"}', + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiReadKey="123456789") + padl.store_sensor_data = MagicMock(name="store_sensor_data") + json_config_file = { + "sensor_group_name": "A Name Goes Here", + "add_sensors_to_group": False, + "sensor_index_list": [77, 81, 95079, 167897], + "poll_interval_seconds": 60, + "fields": "name, icon, model, hardware, location_type, rssi, uptime, pa_latency, memory, last_seen, last_modified, date_created, channel_state, confidence, confidence_manual, confidence_auto,humidity, humidity_a, humidity_b, temperature, temperature_a, temperature_b, pressure, pressure_a, pressure_b,voc, voc_a, voc_b, ozone1, analog_input,pm1.0, pm1.0_a, pm1.0_b, pm1.0_atm, pm1.0_atm_a, pm1.0_atm_b, pm1.0_cf_1, pm1.0_cf_1_a,pm1.0_cf_1_b,pm2.5_alt, pm2.5_alt_a, pm2.5_alt_b, pm2.5, pm2.5_a, pm2.5_b, pm2.5_atm, pm2.5_atm_a, pm2.5_atm_b, pm2.5_cf_1, pm2.5_cf_1_a, pm2.5_cf_1_b,pm2.5_10minute, pm2.5_10minute_a, pm2.5_10minute_b, pm2.5_30minute, pm2.5_30minute_a, pm2.5_30minute_b, pm2.5_60minute, pm2.5_60minute_a, pm2.5_60minute_b, pm2.5_6hour, pm2.5_6hour_a, pm2.5_6hour_b,pm2.5_24hour, pm2.5_24hour_a, pm2.5_24hour_b, pm2.5_1week, pm2.5_1week_a, pm2.5_1week_b,pm10.0, pm10.0_a, pm10.0_b, pm10.0_atm, pm10.0_atm_a, pm10.0_atm_b, pm10.0_cf_1, pm10.0_cf_1_a, pm10.0_cf_1_b,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,10.0_um_count_a,10.0_um_count_b,primary_id_a, primary_key_a, secondary_id_a, secondary_key_a, secondary_id_b, secondary_key_b", + "location_type": None, + "read_keys": None, + "show_only": None, + "modified_since": None, + "max_age": None, + "nwlng": None, + "nwlat": None, + "selng": None, + "selat": None, + } + + expected_return_data = { + "api_version": "V1.0.11-0.0.42", + "time_stamp": 1676784999, + "data_time_stamp": 1676784999, + "group_id": 1234, + "max_age": 604800, + "firmware_default_version": "7.02", + "fields": ["sensor_index", "name"], + "data": [[77, "Sunnyside"], [81, "Sherwood Hills 2"]], + } + + # Action & Expected Result + expected_url_request = "https://api.purpleair.com/v1/groups/1234/members?fields=name,icon,model,hardware,location_type,rssi,uptime,pa_latency,memory,last_seen,last_modified,date_created,channel_state,confidence,confidence_manual,confidence_auto,humidity,humidity_a,humidity_b,temperature,temperature_a,temperature_b,pressure,pressure_a,pressure_b,voc,voc_a,voc_b,ozone1,analog_input,pm1.0,pm1.0_a,pm1.0_b,pm1.0_atm,pm1.0_atm_a,pm1.0_atm_b,pm1.0_cf_1,pm1.0_cf_1_a,pm1.0_cf_1_b,pm2.5_alt,pm2.5_alt_a,pm2.5_alt_b,pm2.5,pm2.5_a,pm2.5_b,pm2.5_atm,pm2.5_atm_a,pm2.5_atm_b,pm2.5_cf_1,pm2.5_cf_1_a,pm2.5_cf_1_b,pm2.5_10minute,pm2.5_10minute_a,pm2.5_10minute_b,pm2.5_30minute,pm2.5_30minute_a,pm2.5_30minute_b,pm2.5_60minute,pm2.5_60minute_a,pm2.5_60minute_b,pm2.5_6hour,pm2.5_6hour_a,pm2.5_6hour_b,pm2.5_24hour,pm2.5_24hour_a,pm2.5_24hour_b,pm2.5_1week,pm2.5_1week_a,pm2.5_1week_b,pm10.0,pm10.0_a,pm10.0_b,pm10.0_atm,pm10.0_atm_a,pm10.0_atm_b,pm10.0_cf_1,pm10.0_cf_1_a,pm10.0_cf_1_b,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,10.0_um_count_a,10.0_um_count_b,primary_id_a,primary_key_a,secondary_id_a,secondary_key_a,secondary_id_b,secondary_key_b" + with requests_mock.Mocker() as m1: + m1.get( + expected_url_request, + text=f"{dumps(expected_return_data)}", + status_code=200, + ) + self.assertEqual( + logic_for_storing_group_sensors_data(padl, 1234, json_config_file), 1234 + ) + + def test_logic_for_storing_group_sensors_data_with_adding_group_id_4321_duplicate( + self, + ): + """ + Test the main logic for the PurpleAirDataLogger.`_run_loop_for_storing_group_sensors_data` method. + """ + + # Setup + expected_url_request = "https://api.purpleair.com/v1/keys" + padl = None + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text='{"api_version" : "1.1.1", "time_stamp": 123456789, "api_key_type": "READ"}', + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiReadKey="123456789") + padl.store_sensor_data = MagicMock(name="store_sensor_data") + json_config_file = { + "sensor_group_name": "A Name Goes Here", + "add_sensors_to_group": True, + "sensor_index_list": [77, 81, 95079, 167897], + "poll_interval_seconds": 60, + "fields": "name, icon, model, hardware, location_type, private, latitude, longitude, altitude, position_rating, led_brightness, firmware_version, firmware_upgrade, rssi, uptime, pa_latency, memory, last_seen, last_modified, date_created, channel_state, channel_flags, channel_flags_manual, channel_flags_auto, confidence, confidence_manual, confidence_auto,humidity, humidity_a, humidity_b, temperature, temperature_a, temperature_b, pressure, pressure_a, pressure_b,voc, voc_a, voc_b, ozone1, analog_input,pm1.0, pm1.0_a, pm1.0_b, pm1.0_atm, pm1.0_atm_a, pm1.0_atm_b, pm1.0_cf_1, pm1.0_cf_1_a,pm1.0_cf_1_b,pm2.5_alt, pm2.5_alt_a, pm2.5_alt_b, pm2.5, pm2.5_a, pm2.5_b, pm2.5_atm, pm2.5_atm_a, pm2.5_atm_b, pm2.5_cf_1, pm2.5_cf_1_a, pm2.5_cf_1_b,pm2.5_10minute, pm2.5_10minute_a, pm2.5_10minute_b, pm2.5_30minute, pm2.5_30minute_a, pm2.5_30minute_b, pm2.5_60minute, pm2.5_60minute_a, pm2.5_60minute_b, pm2.5_6hour, pm2.5_6hour_a, pm2.5_6hour_b,pm2.5_24hour, pm2.5_24hour_a, pm2.5_24hour_b, pm2.5_1week, pm2.5_1week_a, pm2.5_1week_b,pm10.0, pm10.0_a, pm10.0_b, pm10.0_atm, pm10.0_atm_a, pm10.0_atm_b, pm10.0_cf_1, pm10.0_cf_1_a, pm10.0_cf_1_b,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,5.0_um_count_a,5.0_um_count_b,10.0_um_count,10.0_um_count_a,10.0_um_count_b,primary_id_a, primary_key_a, secondary_id_a, secondary_key_a, primary_id_b, primary_key_b, secondary_id_b, secondary_key_b", + "location_type": None, + "read_keys": None, + "show_only": None, + "modified_since": None, + "max_age": None, + "nwlng": None, + "nwlat": None, + "selng": None, + "selat": None, + } + + expected_return_data_1 = { + "groups": [ + {"name": "A Name Goes Here!", "id": 20}, + {"name": "A Name Goes Here", "id": 4321}, + ] + } + expected_return_data_2 = {} + expected_return_data_3 = { + "api_version": "V1.0.11-0.0.42", + "time_stamp": 1676784867, + "data_time_stamp": 1676784847, + "group_id": 4321, + "max_age": 604800, + "firmware_default_version": "7.02", + "fields": ["sensor_index", "name"], + "data": [[77, "Sunnyside"], [81, "Sherwood Hills 2"]], + } + + # Action & Expected Result + expected_url_request_1 = "https://api.purpleair.com/v1/groups/" + expected_url_request_2 = "https://api.purpleair.com/v1/groups/4321/members" + expected_url_request_3 = "https://api.purpleair.com/v1/groups/4321/members?fields=name,icon,model,hardware,location_type,private,latitude,longitude,altitude,position_rating,led_brightness,firmware_version,firmware_upgrade,rssi,uptime,pa_latency,memory,last_seen,last_modified,date_created,channel_state,channel_flags,channel_flags_manual,channel_flags_auto,confidence,confidence_manual,confidence_auto,humidity,humidity_a,humidity_b,temperature,temperature_a,temperature_b,pressure,pressure_a,pressure_b,voc,voc_a,voc_b,ozone1,analog_input,pm1.0,pm1.0_a,pm1.0_b,pm1.0_atm,pm1.0_atm_a,pm1.0_atm_b,pm1.0_cf_1,pm1.0_cf_1_a,pm1.0_cf_1_b,pm2.5_alt,pm2.5_alt_a,pm2.5_alt_b,pm2.5,pm2.5_a,pm2.5_b,pm2.5_atm,pm2.5_atm_a,pm2.5_atm_b,pm2.5_cf_1,pm2.5_cf_1_a,pm2.5_cf_1_b,pm2.5_10minute,pm2.5_10minute_a,pm2.5_10minute_b,pm2.5_30minute,pm2.5_30minute_a,pm2.5_30minute_b,pm2.5_60minute,pm2.5_60minute_a,pm2.5_60minute_b,pm2.5_6hour,pm2.5_6hour_a,pm2.5_6hour_b,pm2.5_24hour,pm2.5_24hour_a,pm2.5_24hour_b,pm2.5_1week,pm2.5_1week_a,pm2.5_1week_b,pm10.0,pm10.0_a,pm10.0_b,pm10.0_atm,pm10.0_atm_a,pm10.0_atm_b,pm10.0_cf_1,pm10.0_cf_1_a,pm10.0_cf_1_b,0.3_um_count,0.3_um_count_a,0.3_um_count_b,0.5_um_count,0.5_um_count_a,0.5_um_count_b,1.0_um_count,1.0_um_count_a,1.0_um_count_b,2.5_um_count,2.5_um_count_a,2.5_um_count_b,5.0_um_count,5.0_um_count_a,5.0_um_count_b,10.0_um_count,10.0_um_count_a,10.0_um_count_b,primary_id_a,primary_key_a,secondary_id_a,secondary_key_a,primary_id_b,primary_key_b,secondary_id_b,secondary_key_b" + + with requests_mock.Mocker() as m1: + m1.get( + expected_url_request_1, + text=f"{dumps(expected_return_data_1)}", + status_code=200, + ) + m1.post( + expected_url_request_2, + text=f"{dumps(expected_return_data_2)}", + status_code=200, + ) + m1.get( + expected_url_request_3, + text=f"{dumps(expected_return_data_3)}", + status_code=200, + ) + self.assertEqual( + logic_for_storing_group_sensors_data(padl, None, json_config_file), 4321 + ) + + def test_logic_for_storing_group_sensors_data_with_adding_group_id_4567_duplicate_and_members_duplicate( + self, + ): + """ + Test the main logic for the PurpleAirDataLogger.`_run_loop_for_storing_group_sensors_data` method. + """ + + # Setup + expected_url_request = "https://api.purpleair.com/v1/keys" + padl = None + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text='{"api_version" : "1.1.1", "time_stamp": 123456789, "api_key_type": "READ"}', + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiReadKey="123456789") + padl.store_sensor_data = MagicMock(name="store_sensor_data") + json_config_file = { + "sensor_group_name": "A Name Goes Here", + "add_sensors_to_group": True, + "sensor_index_list": [77, 81, 95079, 167897], + "poll_interval_seconds": 60, + "fields": "name, fields", + "location_type": None, + "read_keys": None, + "show_only": None, + "modified_since": None, + "max_age": None, + "nwlng": None, + "nwlat": None, + "selng": None, + "selat": None, + } + + expected_return_data_1 = { + "groups": [ + {"name": "A Name Goes Here!", "id": 20}, + {"name": "A Name Goes Here", "id": 4567}, + ] + } + expected_return_data_2 = { + "error": 4567, + "description": "409: DuplicateGroupEntryError - This sensor already exists in this group.", + } + + expected_return_data_3 = { + "api_version": "V1.0.11-0.0.42", + "time_stamp": 1676784867, + "data_time_stamp": 1676784847, + "group_id": 4567, + "max_age": 604800, + "firmware_default_version": "7.02", + "fields": ["sensor_index", "name"], + "data": [[77, "Sunnyside"], [81, "Sherwood Hills 2"]], + } + + # Action & Expected Result + expected_url_request_1 = "https://api.purpleair.com/v1/groups/" + expected_url_request_2 = "https://api.purpleair.com/v1/groups/4567/members" + expected_url_request_3 = ( + "https://api.purpleair.com/v1/groups/4567/members?fields=name,fields" + ) + + with requests_mock.Mocker() as m1: + m1.get( + expected_url_request_1, + text=f"{dumps(expected_return_data_1)}", + status_code=200, + ) + m1.post( + expected_url_request_2, + text=f"{dumps(expected_return_data_2)}", + status_code=409, + ) + m1.get( + expected_url_request_3, + text=f"{dumps(expected_return_data_3)}", + status_code=200, + ) + self.assertEqual( + logic_for_storing_group_sensors_data(padl, None, json_config_file), 4567 + ) + + def test_logic_for_storing_local_sensors_data(self): + """ + Test the main logic for the PurpleAirDataLogger.`_run_loop_for_storing_local_sensors_data` method. + """ + + test_data_in = [LOCAL_API_DATA_IN_1, LOCAL_API_DATA_IN_2] + test_data_out = [LOCAL_API_DATA_OUT_1, LOCAL_API_DATA_OUT_2] + for ( + index, + test_data, + ) in enumerate(test_data_in): + # Setup + expected_url_request = "http://192.168.1.2/json" + padl = None + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text="{}", + status_code=200, + ) + padl = PurpleAirDataLogger(PurpleAirApiIpv4Address=["192.168.1.2"]) + padl.store_sensor_data = MagicMock(name="store_sensor_data") + json_config_file = { + "sensor_ip_list": ["192.168.1.2"], + "poll_interval_seconds": 1, + } + + # Action & Expected Result + expected_url_request = "http://192.168.1.2/json" + + with requests_mock.Mocker() as m: + m.get( + expected_url_request, + text=f"{dumps(test_data)}", + status_code=200, + ) + logic_for_storing_local_sensors_data(padl, json_config_file) + padl.store_sensor_data.return_value = None + padl.store_sensor_data.assert_called_once_with(test_data_out[index])
- PurpleAirSQLiteDataLogger module
+PurpleAirSQLiteDataLogger module
Copyright 2023 carlkidcrypto, All rights reserved. A python class designed to use the PurpleAirAPI for requesting sensor(s) data. Data will be inserted into a SQLite3 database file.
@@ -100,12 +101,12 @@ single request rather than individual requests in succession.”
- PurpleAirPSQLQueryStatements module
+PurpleAirPSQLQueryStatements module
Copyright 2022 carlkidcrypto, All rights reserved. A file containing PSQL statements defined as constants. Generate the PSQL query strings. For simplicity our table names will match what the PurpleAir documentation says. We will do the same for table column names.
PurpleAirSQLiteDataLogger module — PurpleAir Data Logger(s) V1.2.1 documentation +PurpleAirSQLiteDataLogger module — PurpleAir Data Logger(s) V1.3.0a1 documentation - - - - - + + + + + @@ -48,6 +48,7 @@